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): The AssetManager of the app, usually context.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 from loadTimeZoneDetails.
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): The AssetManager of the app.
  • timeZone (optional): The IANA time zone id. Defaults to null, 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/KolkataIST-5:30
Id ending in a numeric offset <standard> only Etc/GMT+5UTC+5
Daylight saving <standard><offset><daylight>,<start>,<end> America/New_YorkEST+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, and America/New_York (UTC−05:00) becomes +5. This is intentional, not a bug.

An unknown id returns an empty string. getTzFormat returns "" 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): UpdateDeviceSettingRequest with timezoneSettings set.
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. Use DeviceClusterTypes.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 updateCluster call rather than two separate updateClusterAttribute calls. 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 getUsaTimeZonesnot 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.


This site uses Just the Docs, a documentation theme for Jekyll.