Skip to content
JavaScript Basics: Display Dates in Explicit Time Zones

JavaScript Basics: Display Dates in Explicit Time Zones

October 18, 2026

A Date represents an instant, while a displayed calendar date depends on the chosen time zone and locale. Intl.DateTimeFormat separates that display decision from the stored instant. These examples pin the locale, calendar, numbering system, and time zone, then inspect meaningful parts instead of depending on locale-specific punctuation.

These examples were tested on Linux with Node.js v24.19.0. They use built-in APIs only, need no network access, and include assertions that check the results. See the ECMAScript internationalization specification for the API contract.

Prepare a scratch directory

You need Bash, a text editor, Node.js 24 on your PATH, and the mktemp, rm, and rmdir utilities. Keep using the same terminal. The guarded commands stop the shell if setup fails. Save each named source file as UTF-8; .mjs selects ES modules.

node --version
start_dir="$PWD"
work_dir="$(mktemp -d)" || exit 1
cd -- "$work_dir" || exit 1

The tested runtime prints:

v24.19.0

1. Show one instant in two calendar dates

Save this as compare-time-zone-dates.mjs:

import assert from 'node:assert/strict';

const instant = new Date('2026-10-18T00:30:00Z');
function calendarDate(timeZone) {
  const formatter = new Intl.DateTimeFormat('en-US', {
    timeZone, calendar: 'gregory', numberingSystem: 'latn',
    year: 'numeric', month: '2-digit', day: '2-digit',
  });
  const parts = Object.fromEntries(
    formatter.formatToParts(instant).map(part => [part.type, part.value]),
  );
  return `${parts.year}-${parts.month}-${parts.day}`;
}
assert.equal(calendarDate('UTC'), '2026-10-18');
assert.equal(calendarDate('America/Los_Angeles'), '2026-10-17');
console.log(`UTC: ${calendarDate('UTC')}`);
console.log(`Los Angeles: ${calendarDate('America/Los_Angeles')}`);

Run it:

node compare-time-zone-dates.mjs

Output:

UTC: 2026-10-18
Los Angeles: 2026-10-17

Both displays describe the same instant. Choosing a time zone changes the calendar fields shown; it does not modify the Date. formatToParts() labels the pieces so you do not need to split a formatted string on assumed punctuation. The simple map above is appropriate for these unique date fields; repeated literal separators are intentionally ignored. For ordinary localized display, use formatter.format(instant) directly instead of rebuilding the order yourself.

2. Reuse a formatter for a schedule

Save this as format-seoul-schedule.mjs:

import assert from 'node:assert/strict';

const formatter = new Intl.DateTimeFormat('en-US', {
  timeZone: 'Asia/Seoul', calendar: 'gregory', numberingSystem: 'latn',
  hour: '2-digit', minute: '2-digit', hourCycle: 'h23',
});
const meetings = ['2026-10-18T00:00:00Z', '2026-10-18T06:30:00Z'];
const labels = meetings.map(value => {
  const parts = formatter.formatToParts(new Date(value));
  const hour = parts.find(part => part.type === 'hour').value;
  const minute = parts.find(part => part.type === 'minute').value;
  return `${hour}:${minute}`;
});
assert.deepEqual(labels, ['09:00', '15:30']);
assert.equal(formatter.resolvedOptions().timeZone, 'Asia/Seoul');
console.log(`Seoul schedule: ${labels.join(', ')}`);

Run it:

node format-seoul-schedule.mjs

Output:

Seoul schedule: 09:00, 15:30

Build a formatter once when many values share the same display settings. h23 requests a 00–23 hour cycle; it avoids relying on a locale’s default 12-hour or 24-hour preference. Each input includes Z, so its UTC interpretation is explicit. A display formatter does not parse localized date strings or decide how to resolve ambiguous local appointment times.

3. Validate a time-zone setting

Save this as validate-display-time-zone.mjs:

import assert from 'node:assert/strict';

function makeFormatter(timeZone) {
  return new Intl.DateTimeFormat('en-US', {
    timeZone, year: 'numeric', month: '2-digit', day: '2-digit',
  });
}
assert.throws(() => makeFormatter('Not/A_Zone'), RangeError);
const formatter = makeFormatter('UTC');
assert.equal(formatter.resolvedOptions().timeZone, 'UTC');
assert.throws(() => formatter.format(new Date(NaN)), RangeError);
console.log('Invalid time zone rejected');
console.log('Invalid date rejected');

Run it:

node validate-display-time-zone.mjs

Output:

Invalid time zone rejected
Invalid date rejected

An unsupported time-zone identifier fails at formatter construction. An invalid Date fails when formatted. Validate these at the boundary where settings and date values enter your program, and report a useful message rather than silently switching to the computer’s local time zone. Localized text and time-zone rules come from runtime data and can vary across versions. Keep ISO timestamps for interchange; localized labels are intended for people.

Clean up

In the same terminal, remove only the example files, return to your original directory, and remove the now-empty scratch directory:

rm -- compare-time-zone-dates.mjs format-seoul-schedule.mjs validate-display-time-zone.mjs
cd -- "$start_dir" || exit 1
rmdir -- "$work_dir"
unset start_dir work_dir

Acknowledgements

This post was created with assistance from AI.

Last updated on