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.
The full table
Section titled “The full table”| Library | Test cases | CI test executions | Node versions in CI | Time zones | Locales | Known failing tests | Built on | Finest time unit |
|---|---|---|---|---|---|---|---|---|
| @northguild/gmt | 56,006 | 1,680,180 | 3 (22, 24, 26) | 10 | 17 | 0 | TC39 Temporal | Nanosecond |
| @internationalized/date | 386 | 386 | 1 | 0 | 0 | 0 | not TC39 | Millisecond |
| Luxon | 1,222 | 4,888 | 4 | 0 | 0 | not measured | built on Date | Millisecond |
| date-fns | 3,213 | 3,213 | 1 | 0 | 0 | not measured | built on Date | Millisecond |
| Moment.js | 3,901 | 11,703 | 3 | 0 | 139 | 0 | built on Date | Millisecond |
| Day.js | 794 | 1,034 | 1 | 6 | 42 | not measured | built on Date | Millisecond |
| Spacetime | 6,086 | 12,172 | 2 | 8 | 2 | 41 | leans on Date | Millisecond |
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.
How these were measured
Section titled “How these were measured”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.
| Library | Version tested | Repository and commit | Measured on | Command | CI configuration |
|---|---|---|---|---|---|
| @northguild/gmt | this repository | this repository | every change | collected from the suite by scripts/stats.mjs | .github/workflows/ci.yml |
| @internationalized/date | 3.12.3; API counted at 3.12.4 | adobe/react-spectrum@5d191ab | 2026-08-22 | npx jest packages/@internationalized/date/tests/ | adobe/react-spectrum/blob/main/.circleci/config.yml |
| Luxon | 3.7.2 | moment/luxon@f427515 | 2026-08-22 | jest | moment/luxon/blob/master/.github/workflows/test.yml |
| date-fns | 4.4.0 | date-fns/date-fns@a0a3922 | 2026-08-22 | not recorded | date-fns/date-fns/tree/main/.github/workflows |
| Moment.js | 2.30.1 | moment/moment@cf524af | 2026-08-22 | node scripts/test.js | moment/moment/tree/develop/.github/workflows |
| Day.js | 1.11.23 | iamkun/dayjs@539c4b9 | 2026-09-09 | npm test | iamkun/dayjs/blob/dev/.github/workflows/lint-test.yml |
| Spacetime | 7.13.0 | spencermountain/spacetime@2acdc4e | 2026-09-09 | tape ./test/**/*.test.js | spencermountain/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 teston 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.formatDateis an alias offormat, 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 teston 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;localescounts the 139 of them that test a locale. - Day.js. A single default run is 93 suites and 794 tests. The repository's
testscript 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.localescounts test/locale.test.js plus the 41 files in test/locale/. CI runsnpm teston 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 thedevbranch, wherepackage.jsoncarries the placeholder version0.0.0-development: itssrc,testandtypestrees 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-offpost-transition assertions in europe/london, europe/ljubljana and atlantic/madeira. CI runs Node 20.x and 26.x, so executions are 6,086 × 2.localescounts test/i18n.test.js and test/intl.test.js.timezonescounts 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.jsextension and are in no count. 32 of its methods are declared as function-typed members, counted as methods.
What each library covers
Section titled “What each library covers”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.
| Capability | GMT | Also has it |
|---|---|---|
| Duration type (ISO 8601 parse, format, arithmetic) | Yes | Luxon Duration |
| Interval and range math (contains, overlap, union, intersection, split, set operations) | Yes | Luxon Interval, date-fns areIntervalsOverlapping |
| DST disambiguation control on construction and on arithmetic | Yes | None 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 rounding | Yes | temporal-kit (arithmetic only) |
| Interval rounding-out (boundary count, from a duration) | Yes | Luxon |
Locale calendar metadata (names, hasDST) | Yes | Luxon Info |
| Overlap-day count, relative rounding, DST transitions, hours in a day | Yes | date-fns, @internationalized/date |
| Field setters, token-pattern parsing, named machine formats, calendar-style formatting | Yes | Luxon .set(), toRFC2822, toHTTP and toSQL; Moment .calendar() |
| Non-Gregorian calendar systems (conversion and calendar-aware interval and duration math) | Yes | @internationalized/date's toCalendar |
Where GMT differs
Section titled “Where GMT differs”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.
| Topic | GMT | What each other library's measurement shows |
|---|---|---|
| Time zones | The 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. |
| Locales | 17 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 on | TC39 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 executions | 1,680,180 | All 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.
How GMT’s own suite is designed
Section titled “How GMT’s own suite is designed”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,falseor[]) 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 fornull,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,utcandunixfamilies 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.