Three different things get called "time zone":
- Instant: a moment on the UTC timeline (
2026-03-08T12:00:00Z). It is the same everywhere. - Offset: a fixed difference from UTC at one moment (
-04:00,+05:30,+05:45). It says nothing about what it will be next month. - Zone: an IANA identifier (
America/New_York,Asia/Kolkata) naming a history of offset rules, including DST. The IANA database says to prefer such identifiers over abbreviations, becauseCSTorISTare ambiguous.
Rules of thumb:
- Store instants in UTC (ISO string with
Z, or epoch milliseconds) for past events: logs, payments,createdAt. Convert to a zone only for display, with an explicittimeZone. - Future local events (a 09:00 meeting in London) need the wall time plus the zone id. A UTC instant computed today is wrong if the government changes the zone's rules before the event.
- Wall times are not always unique. When clocks go forward, a range of local times is skipped (a gap). When they go back, a range happens twice (an overlap). You must pick a policy.
- Never assume 24 hours per day, whole-hour offsets, or the host zone. Offsets such as
+05:30and+05:45exist, southern-hemisphere zones move the other way, and servers should not depend on their ownTZ.
Date has only two zones: UTC and the host's. For a gap it moves the time forward by the gap, and for an overlap it picks the earlier instant. It cannot hold "9:00 in Tokyo". Use Intl.DateTimeFormat with timeZone to display, and Temporal's ZonedDateTime to compute. Its disambiguation option ('compatible' default, 'earlier', 'later', 'reject') makes the gap and overlap policy explicit.