Time Zones
Pre-requisite:
- The user has to be signed in to perform the following operations.
- The camera has to be paired to the space.
A camera stores its time zone as two values that are always written together:
| Value | Format | Example |
|---|---|---|
| Identifier | IANA time zone id | Asia/Kolkata |
| TZ format | POSIX TZ string | IST-5:30 |
The identifier is what the backend reports back and what the app renders; the TZ format is what the camera programs into its own clock. Writing one without the other leaves the camera and the backend disagreeing about the time, so both are always sent in a single call.
The TZ format string is produced by the SDK helper TZ — never assemble it by hand.
Working with the TZ format
Listing the available time zones
The SDK bundles the time zones the cameras understand. To load them, The following method can be used:
assetManager(required): TheAssetManagerof the app, usuallycontext.assets.
val timeZones = TZ.loadTimeZoneDetails(context.assets)
Each entry is a TimeZoneDetail:
| Field | Type | Meaning |
|---|---|---|
id | String | The IANA time zone id, e.g. America/New_York |
standardName | String | The abbreviation used outside daylight saving, e.g. EST |
daylightName | String? | The abbreviation used during daylight saving, e.g. EDT |
This is the list a time zone picker should render. A zone that is not in it cannot be programmed into a camera.
Building the TZ format string
To convert a time zone into the POSIX string the camera expects, The following method can be used:
timeZoneDetail(required): The entry chosen fromloadTimeZoneDetails.
val detail = TZ.loadTimeZoneDetails(context.assets).first { it.id == "Asia/Kolkata" }
val tzFormat = TZ.getTzFormat(detail) // "IST-5:30"
There is also an overload that takes the id directly, which is convenient when the time zone comes from the phone rather than from a picker:
assetManager(required): TheAssetManagerof the app.timeZone(optional): The IANA time zone id. Defaults tonull, which uses the phone’s current time zone.
val timezoneId = TimeZone.getDefault().id // "Asia/Kolkata"
val tzFormat = TZ.getTzFormat(context.assets, timezoneId) // "IST-5:30"
The string takes one of three shapes, depending on the zone:
| Zone | Shape | Example |
|---|---|---|
| No daylight saving | <standard><offset> | Asia/Kolkata → IST-5:30 |
| Id ending in a numeric offset | <standard> only | Etc/GMT+5 → UTC+5 |
| Daylight saving | <standard><offset><daylight>,<start>,<end> | America/New_York → EST+5EDT,M3.2.0/2,M11.1.0/2 |
The two transition rules are POSIX M<month>.<week>.<day>/<hour> values, so M3.2.0/2 reads as “the second Sunday of March at 02:00”.
The offset sign is inverted compared to the UTC offset. POSIX counts hours west of Greenwich as positive, so
Asia/Kolkata(UTC+05:30) becomes-5:30, andAmerica/New_York(UTC−05:00) becomes+5. This is intentional, not a bug.An unknown id returns an empty string.
getTzFormatreturns""when the id is not in the bundled list, and the call that follows will happily write that blank value. Check the result before sending it.
Setting the time zone on a camera
Which call to use depends on the settings model the camera uses — device.supportsCluster() is the branch, as described in Cameras:
if (device.supportsCluster()) {
// Cluster camera, use updateCluster
} else {
// Flat settings camera, use updateDeviceSetting
}
Setting the time zone on a standalone camera
On the flat settings model both values travel as one TimezoneSettings object. To set it, The following method can be used:
device(required): The device object on which the operation has to be performed.deviceSetting(required):UpdateDeviceSettingRequestwithtimezoneSettingsset.
val detail = TZ.loadTimeZoneDetails(context.assets).first { it.id == "Asia/Kolkata" }
InstaVision.deviceServices.updateDeviceSetting(
device = device,
deviceSetting = UpdateDeviceSettingRequest(
timezoneSettings = TimezoneSettings(
detail.id, // "Asia/Kolkata"
TZ.getTzFormat(detail) // "IST-5:30"
)
),
onSuccess = { deviceSetting ->
// deviceSetting.timezoneSettings holds the newly applied time zone
},
onError = { error ->
// The error object contains the error code and message
},
)
Setting the time zone on a cluster based device
A cluster camera carries the same two values as two attributes of the time zone cluster:
| Constant | Id | Value |
|---|---|---|
DeviceClusterTypes.TimeZone | 0xFC04 | The time zone cluster |
ClusterAttributeTypes.TimeZoneIdentifier | 0xFC04:0x00 | The IANA id |
ClusterAttributeTypes.TimeZoneOffset | 0xFC04:0x01 | The TZ format string |
Not every cluster camera advertises the cluster, so check before offering the setting:
val supportsTimeZone = cluster.supportsTimeZone()
To write both attributes, The following method can be used:
device(required): The device object on which the operation has to be performed.clusterId(required): The id of the cluster to be update. UseDeviceClusterTypes.TimeZone.id.request(required): The request object containing the list of attributes in the cluster.
val detail = TZ.loadTimeZoneDetails(context.assets).first { it.id == "Asia/Kolkata" }
val tzFormat = TZ.getTzFormat(detail)
val request = UpdateClusterRequest(
attributes = listOf(
UpdateClusterAttribute(
id = ClusterAttributeTypes.TimeZoneIdentifier.id,
value = detail.id // "Asia/Kolkata"
),
UpdateClusterAttribute(
id = ClusterAttributeTypes.TimeZoneOffset.id,
value = tzFormat // "IST-5:30"
)
)
)
InstaVision.deviceServices.updateCluster(
device = device,
clusterId = DeviceClusterTypes.TimeZone.id,
request = request,
onSuccess = { response ->
// response.cluster is the updated Cluster
},
onError = { error ->
// The error object contains the error code and message
},
)
✅ Best Practice: Send both attributes in one
updateClustercall rather than two separateupdateClusterAttributecalls. A camera that receives only one of them ends up running a clock that disagrees with the identifier the app displays.
Reading the current time zone
The read branches the same way as the write:
val timeZoneId = if (device.supportsCluster()) {
cluster.timeZone()
} else {
deviceSetting.timezoneSettings.id
}
cluster.timeZone() returns the literal string "null" when the camera has no time zone attribute, so guard the value before handing it to ZoneId.of:
val zoneId = runCatching { ZoneId.of(timeZoneId) }.getOrDefault(ZoneId.of("UTC"))
val cameraTime = ZonedDateTime.now(zoneId)
.format(DateTimeFormatter.ofPattern("MM-dd-yyyy hh:mm:ss a"))
Setting the time zone while pairing
A camera is given its first time zone at pairing time, before any of the calls above apply — the PairingSessionRequest carries the same TimezoneSettings pair. See Pairing a Camera.
Time zones elsewhere in the SDK
The TimezoneSettings pair belongs to a camera’s own clock. Other parts of the SDK carry a time zone as a plain string, and the vocabularies are not interchangeable:
| Where | Format |
|---|---|
UpdateSecurityScheduleRequest.timezone, SecuritySchedule.timezone | IANA id, e.g. America/Los_Angeles |
EmbargoSettingResponse.timezone | IANA id, e.g. America/Los_Angeles |
SecurityProfileRequest.timezone, NvrSecurityProfileRequest.timezone | The code of a TimeZone returned by getUsaTimeZones — not an IANA id |
The security profile time zone comes from the server rather than from the phone. Fetch the list with getUsaTimeZones, or resolve one from coordinates with getTimeZone, both described in Address Management. Do not populate it from a TimeZoneDetail.id.
Timestamps carry no time zone
Every timestamp the SDK returns — event startTime and endTime, subscription dates, alarm and security logs, realtime createdAt — is a zoneless epoch Long. Render it in whichever zone the screen calls for. Time(hour, minute), used by schedules and event windows, is likewise a wall clock reading with no zone of its own; the camera interprets it in the time zone configured above.
SD card playback window boundaries are computed in the phone’s time zone, not the camera’s. A camera in a different time zone than the viewer returns hour and day buckets shifted by the difference between the two. See SD Card Playback.