Skip to content

GMT and the other libraries

This page sets GMT beside 6 other date libraries: what each one’s own test suite and CI run, what each is built on, and what each covers. Every figure can be checked. The second section names the version, commit, date and command behind it.

The summary on Why GMT? draws charts from the same data.

LibraryTest casesCI test executionsNode versions in CITime zonesLocalesKnown failing testsBuilt onFinest time unit
@northguild/gmt56,0061,680,1803 (22, 24, 26)10170TC39 TemporalNanosecond
@internationalized/date3863861000not TC39Millisecond
Luxon1,2224,888400not measuredbuilt on DateMillisecond
date-fns3,2133,213100not measuredbuilt on DateMillisecond
Moment.js3,90111,703301390built on DateMillisecond
Day.js7941,0341642not measuredbuilt on DateMillisecond
Spacetime6,08612,17228241leans on DateMillisecond

How to read the columns:

  • Test cases is the number of tests in one run of the library’s own suite.
  • CI test executions is the test cases multiplied by every Node version and time-zone run the library’s own CI applies.
  • Time zones: for GMT, the zones the whole suite runs in. For the others, the zones their dedicated time-zone and DST test files cover. A 0 means none was found.
  • Locales: for GMT, the locales every locale-aware function is tested in. For the others, the locale test files their repository holds.
  • Known failing tests counts tests that fail on the library’s own default branch. “Not measured” means the figure was not taken. Failures caused by the measuring machine are not counted here; see the notes below.

Each figure for the other libraries is a snapshot taken on the date in the table, between 2026-08-22 and 2026-09-09. Libraries change their tests and CI, so check the date before you cite a figure. GMT’s own figures are not a snapshot: scripts/stats.mjs regenerates them from this repository on every change, and the build fails if a published figure drifts from the repository.

LibraryVersion testedRepository and commitMeasured onCommandCI configuration
@northguild/gmtthis repositorythis repositoryevery changecollected from the suite by scripts/stats.mjs.github/workflows/ci.yml
@internationalized/date3.12.3; API counted at 3.12.4adobe/react-spectrum@5d191ab2026-08-22npx jest packages/@internationalized/date/tests/adobe/react-spectrum/blob/main/.circleci/config.yml
Luxon3.7.2moment/luxon@f4275152026-08-22jestmoment/luxon/blob/master/.github/workflows/test.yml
date-fns4.4.0date-fns/date-fns@a0a39222026-08-22not recordeddate-fns/date-fns/tree/main/.github/workflows
Moment.js2.30.1moment/moment@cf524af2026-08-22node scripts/test.jsmoment/moment/tree/develop/.github/workflows
Day.js1.11.23iamkun/dayjs@539c4b92026-09-09npm testiamkun/dayjs/blob/dev/.github/workflows/lint-test.yml
Spacetime7.13.0spencermountain/spacetime@2acdc4e2026-09-09tape ./test/**/*.test.jsspencermountain/spacetime/tree/master/.github/workflows

Test cases for each library were obtained by cloning its repository at the commit shown, installing its dependencies, running the project’s own test command, and reading that runner’s final summary. They were not counted from the source. CI test executions come from each project’s public CI configuration, linked in the last column: the test count multiplied by the Node versions and time-zone runs that configuration applies.

The notes below record how each library’s figures were reached, and what a reader needs to read them correctly.

  • @internationalized/date. All 386 tests passed. The suite runs once: CI tests React 16 to canary, not a range of Node versions. The 12 methods on the Calendar interface are counted once, not again on each of its 13 implementations.
  • Luxon. CI (.github/workflows/test.yml) runs npm run test on Node 20, 22, 24 and 25 under one fixed time zone, America/New_York, so executions are 1,222 × 4. The 39 local failures come from the machine: the suite assumes its CI container's local time zone is America/New_York. They do not change the test count. Luxon ships no types, so the API was counted from @types/luxon 3.7.5, which tracks 3.7.x.
  • date-fns. The Node version in CI is not set explicitly (node = "latest"), so the suite counts once. A dedicated time-zone workflow exists, but its scope is unclear and is not counted. The 46 local failures come from the machine: the experimental native-Temporal code path needs a global Temporal that Node does not provide. They do not change the test count. formatDate is an alias of format, counted once. The 1 method is the function-typed member of the exported Localize type.
  • Moment.js. 0 tests failed on Node 24. CI (.github/workflows/ci.yml) runs pnpm test on three Node versions (lts/*, lts/-1, latest), so executions are 3,901 × 3. timezones.yml re-runs 5 test modules, not the full suite, under 6 zones; those partial runs are not counted. The repository holds 191 test files; locales counts the 139 of them that test a locale.
  • Day.js. A single default run is 93 suites and 794 tests. The repository's test script first re-runs test/timezone.test.js (6 tests) and test/plugin/timezone.test.js (54 tests) under 6 distinct TZ values (Pacific/Auckland, Europe/London, America/Whitehorse, the system default, Europe/Paris, America/New_York), then runs the full jest suite: 4 × 6 + 4 × 54 + 794 = 1,034 executions. locales counts test/locale.test.js plus the 41 files in test/locale/. CI runs npm test on a single Node LTS. The API count covers the core and the 37 plugins shipped in the same npm package; core alone is 4 functions and 30 methods. The commit is on the dev branch, where package.json carries the placeholder version 0.0.0-development: its src, test and types trees are the same as those of the tag v1.11.23.
  • Spacetime. 6,086 tests ran: 6,045 passed and 41 failed. All 41 are dst-off post-transition assertions in europe/london, europe/ljubljana and atlantic/madeira. CI runs Node 20.x and 26.x, so executions are 6,086 × 2. locales counts test/i18n.test.js and test/intl.test.js. timezones counts the 8 dedicated time-zone and DST test files that run (dst-north, dst-south, dst-sneak, findTz, informal-tzs, kazakhstan-timezones, swapTz, timezone-name). Four more DST files exist but are disabled by their .ignore.js extension and are in no count. 32 of its methods are declared as function-typed members, counted as methods.

GMT is compared with @internationalized/date, Luxon, date-fns and Moment.js capability by capability. Day.js and Spacetime have not been compared this way, so the last column names only those four (and temporal-kit, a package for business-day arithmetic). Where it says “None of the others”, it means none of those four.

CapabilityGMTAlso has it
Duration type (ISO 8601 parse, format, arithmetic)YesLuxon Duration
Interval and range math (contains, overlap, union, intersection, split, set operations)YesLuxon Interval, date-fns areIntervalsOverlapping
DST disambiguation control on construction and on arithmeticYesNone of the others exposes this on arithmetic
Locale-aware calendar helpers (weekend, week start and end, day of week)Yes@internationalized/date
Business-day arithmetic with holiday calendars and roll conventions, clamp and closest, time roundingYestemporal-kit (arithmetic only)
Interval rounding-out (boundary count, from a duration)YesLuxon
Locale calendar metadata (names, hasDST)YesLuxon Info
Overlap-day count, relative rounding, DST transitions, hours in a dayYesdate-fns, @internationalized/date
Field setters, token-pattern parsing, named machine formats, calendar-style formattingYesLuxon .set(), toRFC2822, toHTTP and toSQL; Moment .calendar()
Non-Gregorian calendar systems (conversion and calendar-aware interval and duration math)Yes@internationalized/date's toCalendar

Each row says what GMT does and what each other library’s measurement shows. The rows name every library instead of saying “only GMT”, because a claim of that kind must hold against all 6 libraries in the data.

TopicGMTWhat each other library's measurement shows
Time zonesThe whole suite runs in each of 10 zones on each of 3 Node versions: 30 full-suite runs@internationalized/date has no time-zone matrix in its CI. Luxon has no time-zone matrix in its CI. date-fns has a dedicated time-zone workflow whose scope is unclear. Moment.js re-runs 5 time-zone test modules under 6 zones, not the full suite. Day.js re-runs 2 time-zone test files under 6 zones, not the full suite. Spacetime has 8 time-zone test files, run once per Node version.
Locales17 locales, run inside the 56,006 tests so that every locale-aware function meets all of them@internationalized/date has no locale tests. Luxon has no locale tests. date-fns has no locale tests. Moment.js has 139 locale test files. Day.js has 42 locale test files. Spacetime has 2 locale test files.
What it is built onTC39 Temporal. Lint packages for ESLint, Oxlint and Biome ban Date in your own code@internationalized/date: not TC39. Luxon: built on Date. date-fns: built on Date. Moment.js: built on Date. Day.js: built on Date. Spacetime: leans on Date.
CI test executions1,680,180All 6 other libraries combined: 386 + 4,888 + 3,213 + 11,703 + 1,034 + 12,172 = 33,396. GMT's figure is about 50 times that

Executions are test cases multiplied by the Node versions and time-zone runs CI applies. A library with a larger suite but a single run can still execute fewer tests than GMT does. The multiple is GMT’s 1,680,180 divided by 33,396, the executions of all 6 other libraries added together: about 50 times.

GMT’s suite balances thoroughness against maintenance by testing behavior, not permutations.

What it tests exhaustively

  • The locale matrix. Every locale-aware function runs in all 17 locales of the matrix: en-US, en-GB, de-DE, fr-FR, es-ES, it-IT, pt-PT, sv-SE, is-IS, zh-CN, zh-TW, ja-JP, ko-KR, ar-SA, he-IL, ru-RU, tr-TR. The set covers script direction, first-day-of-week differences and calendar metadata.
  • The time-zone matrix. Every zoned function runs in 10 IANA time zones that span every UTC offset band, from Pacific/Niue (UTC−11) to Pacific/Chatham (UTC+13:45). The set includes zones with DST transitions and zones at half-hour offsets.
  • Zero-length and identity cases. Every interval and arithmetic function is tested with zero-length inputs, identity operations and values next to a boundary.
  • Invalid-input sentinels. Every public function is tested for its documented fallback ("", null, false or []) on malformed strings, wrong types, leap seconds and inverted intervals.

What it collapses

  • Non-string input tables. A function that guards with typeof x !== "string" returns the same sentinel for null, undefined, 123, true, [] and {}. The suite tests one representative non-string per argument position, not every type at every position. That is safe because every non-string type reaches the same early return.
  • Repeated permutations. Adjacent, disjoint and reversed interval cases that give identical results are not repeated for every function variant. The plain, zoned, utc and unix families share the same mathematical behavior, so each family gets the fewest cases that prove it.

The result. 56,006 tests in 778 files. CI runs them as 1,680,180 executions: every test, in 3 Node versions and 10 time zones, which is 30 runs of each test.