Skip to content

@northguild/gmt

Give Me Temporal - a Temporal-first date and time library with strict string-in, typed-out contract. No Date, anywhere.
DocsDox

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.

Built on the standards

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

Every function takes and returns ISO 8601 strings, and every zoned value carries its RFC 9557 time-zone annotation. Formatters emit RFC 3339, RFC 5322 and RFC 9110 HTTP dates.

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.

Tested and precise

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.

The bug is the Date object

Almost 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.

  • Fault
    new Date("2025-01-15")Tue Jan 14 2025 19:00 GMT−5

    A 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.

  • Fault
    new Date("2025-03-30T01:30")02:30 — no error

    01:30 never happened in London that night — the clocks jumped 01:00 → 02:00. Date invents a nearby valid instant instead of telling you.

  • Fault
    new Date(2025, 3, 1)April 1, not March 1

    Months are 0-indexed. Days are 1-indexed. In the same constructor call.

  • Fault
    Date.UTC(2025, 0, 15, 9, 30)a millisecond count, not a date

    Year, 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.

  • Fault
    d.setDate(d.getDate() + 1)mutates d in place

    A Date handed to a function can be changed underneath you. There is no read-only Date.

  • Fault
    "09:00 every Tuesday, Tokyo"not representable

    A Date is a single integer — milliseconds since the epoch. It cannot hold a wall-clock time or a recurring local time.

  • Fault
    same code, two machinestwo different answers

    Parsing and formatting use the host timezone. A laptop in Denver and a CI runner in UTC disagree.

  • Fault
    wrapper.now() / wrapper(dateString)a native Date, renamed

    Most 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.

  • Fault
    DST edge-case coveragethin, disabled, or failing

    DST 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.

  • Fault
    how a wrapper checks itselfagainst a sibling wrapper, not a spec

    With 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 alternatives inherit it, or deviate from the standard

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.

  1. TemporalThe TC39 standard. Separate types for instants, wall-clock times, zoned times, plain dates, and durations. No Date anywhere.TC39 standard
  2. @northguild/gmtTemporal underneath, with an ISO 8601 string-in / typed-value-out surface on top. The layer you actually write against.Temporal inside
  3. DateEpoch milliseconds interpreted through the host timezone. Every fault above lives here.root cause
  4. LuxonA DateTime is a Date plus a zone string. Invalid inputs become an Invalid DateTime you have to remember to check for.built on Date
  5. date-fnsTree-shakeable functions — but every argument and every return value is a Date, so all of its footguns are still yours. Our gmt lint packages also ban the companion date-fns-tz package.built on Date
  6. Moment.jsA wrapper around Date. In legacy maintenance mode since 2020, and mutable like the thing it wraps. Our gmt lint packages also ban the companion moment-timezone package.built on Date
  7. Day.jsWraps a real Date instance under the hood. Its timezone plugin is graded against moment's output — it can't verify itself without the library it replaces.built on Date
  8. Spacetimenow()/today() default through new Date(). Four DST edge-case suites are named .ignore.js and never run — and the DST tests that do run are currently failing.leans on Date
  9. @internationalized/dateIts own CalendarDate / ZonedDateTime types. Fixes the model, but it is Adobe's API — a second migration once Temporal ships.not TC39