Skip to content

Why GMT?

@northguild/gmt is not the latest entry in a crowded field — it is a fundamentally different bet about what a date/time library should guarantee. The pages below make that concrete with data drawn from the library itself.

The most direct measure of testing rigor is how many times the library is actually executed in CI. GMT’s suite is 56,006 tests, and its 17-locale matrix is built into those tests rather than layered on top. On every PR, CI then re-runs the whole suite under 10 timezones and 3 Node versions, so every test runs 30 times.

Test executions per library

One run of each suite, before any CI matrix. GMT: 56,006 tests. Competitors: 386–6,086.

The split matters. On suite size alone, GMT’s 56,006 tests already outnumber the largest alternative’s (Spacetime, 6,086). The matrix is what turns that into 1,680,180 executions: about 138× the most-executed alternative and about 4,353× the least, against a surface area that is itself larger.

GMT exposes 634 public functions across 12 namespaces, plus 27 regex patterns in regex. 10 namespaces are the core — `plain`, `zoned`, `utc`, `unix`, `calendar`, `precision`, `duration`, `interval`, `instant`, and `span` — and 2 are industry layers built on it: `intermodal` and `transport`. All of it is generated from the same TypeScript source and verified by the same test suite.

Public functions per namespace

564 core functions across 10 namespaces, plus 27 regex patterns. Every namespace shares one contract: ISO 8601 strings in, typed values out, no Date object.

The core is weighted toward plain and zoned, which hold 345 of its 564 functions. plain has one set of operations for each wall-clock shape — dates, times and date-times — and zoned carries them into a real time zone, where a day is not always 24 hours long. utc and unix apply the same rules to UTC strings and epoch numbers.

GMT tests against 10 timezones chosen not for geographic spread but for offset coverage — from UTC−11 (Pacific/Niue) to UTC+13:45 (Pacific/Chatham). That 24.75-hour window means that at any given moment, at least one zone is already living in tomorrow while another is still stuck in yesterday. Date-boundary bugs — off-by-one day, wrong month rollover, DST gaps — only surface when the suite runs across this full offset range. A test that passes at UTC can fail at UTC+14 simply because the calendar date is different.

CI timezones under test

10 timezones spanning UTC−11 to UTC+13:45. Hover a dot for the current local time in ISO 8601. Times refresh every second via Temporal.

GMT does not test every locale — there are hundreds, and most differ only in formatting minutiae. Instead it tests a curated matrix of 17 locales chosen to stress the dimensions that actually break date libraries: script direction (LTR/RTL Arabic and Hebrew), character sets (CJK, Cyrillic, Latin), pluralization rules, and ICU data variants. The goal is not exhaustive locale coverage — it is representative coverage of every class of bug that locale-aware code can produce.

Locale matrix by script family

17 locales across 5 script families. Latin (9), CJK (4), Arabic/Hebrew (2), Cyrillic (1), Turkic (1). Each cell shows the locale code and English name.

Most date bugs hide at the extremes, where ordinary tests never look. GMT tests them directly:

  • Midnight. A time at the last nanosecond of the day stays on that day.
  • The ends of time. The first and last moments a date can represent still give exact answers.
  • Daylight saving. Days, weeks and months start at the right moment across clock changes, right to the end of the calendar.
  • Zones ahead of UTC. Sydney and Kiritimati work right up to the last moment that can be represented.
  • Long totals. Adding up time stays exact to the nanosecond, however large the total.
  • Touching intervals. A 9–5 meeting and a 5–6 meeting don’t overlap: an interval includes its start, not its end.
  • Zero known bugs. None of the 56,006 tests is skipped or switched off.

See the evidence behind each of these → · Where the Temporal polyfill gets one of these wrong, GMT returns the spec’s answer and reports the defect upstream: Helping fix Temporal →

DimensionGMTLegacy libraries
CI executions1,680,180386 – 12,172
Locales tested17, built into every test0 – 139 files, tested in isolation from the rest of the API surface
Timezones tested10 × every public function0 – 8 files, tested in isolation
Node versions22, 24, 261 – 4
Public functions63480 – 246*
Time precisionNanoseconds, exact as bigintMilliseconds
Sentinel contract"" / null / false / []Throws RangeError

* Public functions and methods, counted from each library’s published TypeScript declarations: every exported function, plus every public method of its exported classes and types. Overloads, aliases and inherited methods count once; constructors, getters and properties do not. date-fns 4.4.0: 246 (245 functions, 1 method); Moment.js 2.30.1: 169 (27 functions, 142 methods); Luxon 3.7.2: 153 (0 functions, 153 methods); Day.js 1.11.23: 128 (16 functions, 112 methods); @internationalized/date 3.12.4: 104 (46 functions, 58 methods); Spacetime 7.13.0: 80 (0 functions, 80 methods). GMT exports functions only.

The table is the point. GMT does not add a few convenience methods to an existing Date wrapper — it replaces the type system entirely with TC39 Temporal, then rebuilds the ergonomic surface on top of that guarantee. Every function takes and returns ISO 8601 strings, every zoned value carries its RFC 9557 time-zone annotation, and the 17-locale, 10-timezone matrix runs against the library’s entire public API on every PR, not a sample of it. That is what 1,680,180 CI executions verify.

The full table, how each figure was measured, and what each library covers are on GMT and the other libraries.

Earth imagery: NASA Earth Observatory (Blue Marble Next Generation).