TC39 Temporal, not a homegrown type system
Built directly on TC39 Temporal, the standard
JavaScript date and time API. No Date object anywhere, enforced by three dedicated lint
packages.
Current release: v1.18.0
@northguild/gmt is built on the published standards for dates and times, not on a
library’s own idea of them: TC39 Temporal for the
types and arithmetic, ISO 8601
and RFC 9557 for every string in and out, and
the IANA Time Zone Database for zones. On top of that sits a smaller, opinionated API for
what application code actually needs — arithmetic, comparison, parsing, formatting, unix
conversions, timezone conversion and validation — tested against those standards and exact
to the nanosecond at the edges of Temporal’s range. No hardcoded offset tables, no Date object: every zone on
the globe above is computed live in your browser and recomputed every second.
TC39 Temporal, not a homegrown type system
Built directly on TC39 Temporal, the standard
JavaScript date and time API. No Date object anywhere, enforced by three dedicated lint
packages.
Standard strings in, standard strings out
IANA zones, CLDR locales
Time zones are IANA Time Zone Database identifiers, never hardcoded offsets. Locales are BCP 47 tags, with week rules and wording from the Unicode CLDR data your runtime ships.
Explicit DST disambiguation
Wall-clock times in a daylight-saving gap or overlap resolve with Temporal’s own
disambiguation options — compatible, earlier, later or reject — on construction
and arithmetic, a control none of the other libraries expose.
World calendars by the proposal
Hebrew, Islamic, Japanese, Buddhist, Persian, Ethiopic and more, with era codes and proleptic dates as the Intl Era and Month Code proposal defines them, across Temporal’s whole date range.
The formats freight actually sends
Freight mostly does not send ISO 8601. GMT reads and writes UN/EDIFACT DTM values, ANSI X12
dates and time codes, and GS1 EPCIS 2.0 event times, each against the standard’s own code
list. A code either carries an offset or it does not, and GMT never invents one: an
offsetless value stays a local time until you name the zone.
Read the guide.
Every standard, in one place
TC39 and ECMA-402, Temporal, the Intl proposals, ISO 8601, RFC 3339, RFC 9557, the IANA Time Zone Database, BCP 47, CLDR, RFC 5322, RFC 9110, UN/EDIFACT, ANSI X12, GS1 EPCIS, SQL-92 and test262: what each one is, and what GMT uses it for. Read The Standards.
No borrowed test oracle
GMT’s expected answers come from the standards themselves — Temporal, the RFCs, the official test262 suite — and from the Temporal built into Chrome, never from the polyfill GMT runs on or from another date library. That’s how GMT catches the polyfill’s own bugs: see the fixes we’ve sent upstream.
~51× more CI test executions
1,700,370 executions — 56,679 tests across 10 timezones × 3 Node
versions — against Luxon, date-fns, Moment.js, Day.js,
Spacetime, and @internationalized/date’s combined 33,396.
Correct at the edges
A nanosecond before midnight stays on its day, the first and last instants Temporal can represent give exact answers, and daylight-saving rules hold to the end of the range. Each of these is a named test. See the evidence.
Safeguards against invalid inputs
Temporal throws RangeError / TypeError on invalid data. GMT validates,
normalizes, and prevents crashes, so your app stays robust.
Nanoseconds, not milliseconds
Temporal counts time in nanoseconds, and GMT keeps them exact as bigint, with a JSON-safe
string form and exact converters for .NET ticks, Windows FILETIME, NTP and Postgres
microseconds. Luxon, date-fns, Moment.js, Day.js, Spacetime and
@internationalized/date all stop at the millisecond.
See toNanoseconds.
Zero known bugs
None of the 56,679 tests is skipped, focused or marked as expected to fail. A check in CI fails the build if one ever is, so a known bug can’t ship switched off.
Date objectAlmost every date/time bug in a JavaScript codebase traces back to one type. Date conflates an instant with a wall-clock reading, parses two ways depending on the string, and mutates in place.
new Date("2025-01-15")Tue Jan 14 2025 19:00 GMT−5A bare date string is UTC midnight. West of Greenwich that is still the previous evening. Add a time and the rule silently flips to local.
new Date("2025-03-30T01:30")02:30 — no error01:30 never happened in London that night — the clocks jumped 01:00 → 02:00. Date invents a nearby valid instant instead of telling you.
new Date(2025, 3, 1)April 1, not March 1Months are 0-indexed. Days are 1-indexed. In the same constructor call.
Date.UTC(2025, 0, 15, 9, 30)a millisecond count, not a dateYear, month, day, hour and minute are bare positional numbers, so a swapped pair still runs. Months count from 0, and a year from 0 to 99 is read as 1900 to 1999.
d.setDate(d.getDate() + 1)mutates d in placeA Date handed to a function can be changed underneath you. There is no read-only Date.
"09:00 every Tuesday, Tokyo"not representableA Date is a single integer — milliseconds since the epoch. It cannot hold a wall-clock time or a recurring local time.
same code, two machinestwo different answersParsing and formatting use the host timezone. A laptop in Denver and a CI runner in UTC disagree.
wrapper.now() / wrapper(dateString)a native Date, renamedMost Date-wrapping libraries store a real Date as their internal state and default their clock to it. Every fault above is still reachable — only the syntax changed.
DST edge-case coveragethin, disabled, or failingDST transitions are the hardest case for a Date-shaped model to get right. It shows: wrapper libraries' own test suites are consistently the ones most likely to under-test, skip, or fail exactly this case.
how a wrapper checks itselfagainst a sibling wrapper, not a specWith no independent standard to test against, a Date-wrapping library can only grade itself against another one — so a bug shared by the whole family passes every one of their suites.
The popular libraries do not replace Date; they decorate it, so the faults come along — our gmt lint packages ban the Date wrappers below outright. The one library that fixes the model does it with a non-standard API you will migrate off. GMT is Temporal — the standard — with the ergonomics added on top.