Purpose
Every local-to-UTC conversion in this KB — timestamp-to-slot, broadcast day, daypart-to-slots — ultimately depends on one shared resource: the IANA time zone database (also called tzdata or the Olson database). It is the versioned record of every zone's current and historical UTC offset, DST start/end rules, and transition history. This page states what the database actually is, why "an offset" and "a zone" are different things, and why the database's own version number is provenance data that belongs in every conversion's output.
Zones are Area/Location, not offsets
An IANA zone id has the form Area/Location — America/New_York,
Europe/London, Asia/Kolkata, Pacific/Auckland — named after a
representative location, not a fixed offset. A zone id encodes a complete
history of offsets and DST rules for that location, including every past
change, so that resolving a timestamp from 1985 or a projected timestamp from
2030 both use the correct rules for that instant. This KB's tz engine is
luxon, which reads its rules from the runtime's ICU/tzdata bundle.
An offset is not a zone
Storing "UTC+2" instead of "Europe/Kyiv" looks equivalent for a single
instant but is not, because a fixed offset has no DST rule and no future.
Europe/Kyiv observed UTC+2 in January and UTC+3 in July prior to Ukraine's
2024 DST discontinuation; a system that persisted "UTC+2" at any point
would resolve every subsequent summer timestamp one hour wrong. The rule:
persist the zone id, derive the offset per instant — never the reverse,
and never treat an offset as a substitute for a zone in storage.
import { DateTime } from "luxon";
// WRONG: a fixed offset has no DST rule and silently drifts across a transition.
const bad = DateTime.fromObject({ year: 2026, month: 7, day: 15, hour: 9 }, { zone: "UTC+2" });
// RIGHT: the IANA zone carries the correct offset for whichever date is given.
const good = DateTime.fromObject({ year: 2026, month: 7, day: 15, hour: 9 }, { zone: "Europe/Kyiv" });
Abbreviations are ambiguous
Three-letter zone abbreviations do not uniquely identify a zone: IST is
India Standard Time, Irish Standard Time, or Israel Standard Time; CST is
US Central, China Standard Time, or Cuba Standard Time; EST is used by both
the US and parts of Australia. None of these carry DST rules, and several
collide across completely unrelated regions. This KB's
timestamp-to-slot conversion requires a canonical
IANA zone id and rejects bare abbreviations or numeric offsets at the input
boundary rather than guessing.
Windows zone ids are a different vocabulary
Windows identifies zones by display name — "Eastern Standard Time",
"Pacific Standard Time" — which do not match IANA ids 1:1 and, confusingly,
Windows' "Eastern Standard Time" actually covers the same DST-observing
region as IANA's America/New_York, not literally standard-time-only.
Translating between the two vocabularies requires the CLDR windowsZones
mapping table (a many-to-one map, since several IANA zones can share one
Windows display name); never attempt a string-similarity guess between them.
The database changes — pin and record the version
The tz database is not static: the IANA maintainers cut a new release roughly
ten times a year, almost always in response to a government changing an
offset, DST rule, or zone boundary with real-world effective dates
(historically: Lebanon's abrupt 2023 DST delay, Ramadan-linked DST pauses in
Egypt and Morocco, Chile and Fiji adjusting DST windows). A political change
frequently arrives with only days of public notice, so there is always a
window where the deployed tzdb has not yet caught up to reality — an
unavoidable lag, not a bug, but one that must be surfaced rather than hidden.
Two systems pinned to different tzdb releases can resolve the identical
(wall time, zone) pair to two different UTC instants near any changed
transition — the direct temporal analog of a stale administrative-boundary
vintage in the geo KB. The mitigation is the same pattern used throughout
this KB's provenance model: record the tzdb version actually used
(ConversionProvenance.tzdbVersion in lib/time/provenance.ts) on every
conversion, and re-resolve any wall time near a known transition once the
runtime's tzdb is bumped.
Historical offsets are not today's offset
Zones changed their base offset long before modern DST existed, and some still do: Samoa moved its date-line side in 2011, skipping December 30 entirely to switch from UTC-11 to UTC+13; Venezuela shifted by 30 minutes in 2007 and reverted in 2016; North Korea briefly ran 30 minutes off its neighbors from 2015-2018. A conversion for a historical instant must use the offset that was actually in force then, not the zone's current offset — luxon (via the full tzdata history) resolves this correctly by construction as long as the zone id, not a cached offset, is what was stored.
Edge cases
Tzdb vintage mismatch, political change with short notice, ambiguous zone abbreviations, offset is not a zone, Windows vs IANA ids, and historical offset changes are all direct instances of the hazards above; see Timestamp to Slot for how they surface in the core local-to-UTC conversion, and DST Handling for the gap/fold behavior the database's DST rules produce.
Python parity
Python's standard library zoneinfo module (3.9+) reads the same IANA tz
database — either from the operating system's copy or, if absent, from the
tzdata PyPI package — so a correctly configured Python runtime resolves
zone ids identically to luxon, provided both are running the same tzdb
release. Checking that release:
import zoneinfo
print(zoneinfo.TZPATH) # where the OS/package tzdata is being read from
There is no cross-language guarantee of matching tzdb versions without
explicit alignment — pin tzdata to the same release the Node/luxon runtime
uses if exact cross-system agreement near a recent transition matters.
