<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
<title>Time zones | Sample | ArcGIS Maps SDK for JavaScript</title>
<!-- Load the ArcGIS Maps SDK for JavaScript from CDN -->
<script type="module" src="https://js.arcgis.com/5.2/"></script>
top: var(--calcite-space-lg);
left: calc(4 * var(--calcite-space-lg));
box-shadow: var(--calcite-shadow-1);
top: var(--calcite-space-lg);
right: var(--calcite-space-lg);
--time-zone-panel-width: min(320px, calc(100vw - 2 * var(--calcite-space-lg)));
width: var(--time-zone-panel-width);
box-shadow: var(--calcite-shadow-1);
padding: var(--calcite-space-md-plus) var(--calcite-space-xl) var(--calcite-space-xl);
background: var(--calcite-color-surface-2);
margin-bottom: var(--calcite-space-md);
font-size: var(--calcite-font-size-2);
font-weight: var(--calcite-font-weight-medium);
margin-inline-start: var(--calcite-space-2xs);
#time-zone-options > calcite-radio-button {
margin-block: calc(var(--calcite-space-base) + var(--calcite-space-px));
#specific-time-zone-container {
width: calc(var(--time-zone-panel-width) - 2 * var(--calcite-space-xl));
max-width: calc(100vw - 2 * var(--calcite-space-lg) - 2 * var(--calcite-space-xl));
padding-block: var(--calcite-space-2xs) var(--calcite-space-sm);
<arcgis-map id="map-view" item-id="fec8369a77d34d04a2e5368ff8522cb8" popup-disabled>
<arcgis-zoom slot="top-left"></arcgis-zoom>
<arcgis-compass slot="top-left"></arcgis-compass>
include-default-actions-disabled
<arcgis-scene id="scene-view" class="visible" item-id="b3ed2991ffa142c587c68eddc3e38979" popup-disabled>
<arcgis-zoom slot="top-left"></arcgis-zoom>
<arcgis-navigation-toggle slot="top-left"></arcgis-navigation-toggle>
<arcgis-compass slot="top-left"></arcgis-compass>
include-default-actions-disabled
<calcite-segmented-control id="view-switch" aria-label="View dimension" scale="l">
<calcite-segmented-control-item value="2d">2D</calcite-segmented-control-item>
<calcite-segmented-control-item value="3d" checked>3D</calcite-segmented-control-item>
</calcite-segmented-control>
<div id="time-zone-block" class="time-zone-content">
<div class="time-zone-heading">
<calcite-tooltip reference-element="time-zone-info">
Controls how dates and times are displayed in the view.
<calcite-radio-button-group
<calcite-radio-button label-text="Device time zone" value="system" checked></calcite-radio-button>
<calcite-radio-button label-text="Specific time zone" value="specific"></calcite-radio-button>
<div id="specific-time-zone-container" hidden>
aria-label="Specific time zone"
overlay-positioning="fixed"
></calcite-input-time-zone>
<calcite-radio-button label-text="Data's time zone (no adjustments)" value="unknown"></calcite-radio-button>
</calcite-radio-button-group>
// === DOM REFERENCES ===
const mapElement = document.querySelector("arcgis-map");
const sceneElement = document.querySelector("arcgis-scene");
const viewSwitch = document.getElementById("view-switch");
const options = document.getElementById("time-zone-options");
const specificContainer = document.getElementById("specific-time-zone-container");
const specificInput = document.getElementById("specific-time-zone");
// === INITIALIZATION ===
// Wait for both views before accessing their layers and setting the time zone.
await Promise.all([mapElement.viewOnReady(), sceneElement.viewOnReady()]);
// The scene is visible initially. Keep the hidden map out of keyboard navigation.
// The web map and web scene contain the same weather station layer.
const views = [mapElement, sceneElement];
const stations = views.map((element) =>
element.map.allLayers.find(
(layer) => layer.type === "feature" && layer.portalItem.id === "e997041e5b144a4cb98bc2d51145b5e2",
// Query the same station in each view to compare its observation time.
const results = await Promise.all(
where: "stationId = 'SMA'",
const features = results.map((result) => result.features[0]);
// Position the scene popup at the station's ground elevation.
const { geometry: sceneLocation } = await sceneElement.map.ground.queryElevation(features[1].geometry);
// === POPUP CONFIGURATION ===
const popups = views.map((element) => element.querySelector("arcgis-popup"));
await Promise.all(popups.map((popup) => popup.componentOnReady()));
// Format the observation date using the view's time zone and show the source UTC timestamp for comparison.
for (const [index, popup] of popups.entries()) {
popup.location = index === 0 ? features[0].geometry : sceneLocation;
features[index].popupTemplate = {
title: "Weather station {stationName} (sample data)",
{ fieldName: "observedAt", label: "Observed at", format: { dateFormat: "short-date-short-time" } },
{ fieldName: "expression/display-time-zone", label: "Time zone" },
{ fieldName: "temperature", label: "Temperature (°C)" },
{ fieldName: "sourceTimestamp", label: "Source timestamp (UTC)" },
popup.dockOptions = { position: "bottom-right", buttonEnabled: false, breakpoint: false };
features[index].layer = stations[index];
popup.features = [features[index]];
let activeElement = sceneElement;
// Remember the selected region when switching to another time zone mode.
let lastSpecificTimeZone = "Europe/Zurich";
popups[views.indexOf(activeElement)].open = true;
// === EVENT LISTENERS ===
// Use the device time zone, a selected region, or no time zone adjustments.
options.addEventListener("calciteRadioButtonGroupChange", () => {
const mode = options.selectedItem.value;
setTimeZone(mode === "specific" ? lastSpecificTimeZone : mode);
// Apply the chosen IANA time zone while the specific time zone mode is active.
specificInput.addEventListener("calciteInputTimeZoneChange", () => {
lastSpecificTimeZone = specificInput.value;
if (options.selectedItem.value === "specific") {
setTimeZone(lastSpecificTimeZone);
// Switch views while preserving the selected display time zone.
viewSwitch.addEventListener("calciteSegmentedControlChange", () => {
const target = viewSwitch.selectedItem.value === "2d" ? mapElement : sceneElement;
target.timeZone = activeElement.timeZone;
popups[views.indexOf(activeElement)].open = false;
activeElement.classList.remove("visible");
activeElement.inert = true;
target.classList.add("visible");
popups[views.indexOf(target)].open = true;
// === HELPER FUNCTIONS ===
// Apply the same display time zone in 2D and 3D without modifying the observation data.
function setTimeZone(timeZone) {
specificContainer.hidden = timeZone === "system" || timeZone === "unknown";
? Intl.DateTimeFormat().resolvedOptions().timeZone
for (const [index, viewElement] of views.entries()) {
viewElement.timeZone = timeZone;
const graphic = features[index].clone();
graphic.layer = stations[index];
graphic.popupTemplate = features[index].popupTemplate.clone();
graphic.popupTemplate.expressionInfos = [
name: "display-time-zone",
expression: `return ${JSON.stringify(displayTimeZone)};`,
popups[index].features = [graphic];
popups[index].open = viewElement === activeElement;