Skip to content

CORE-8 pre v1.16.0 fixes #251 - #253

Merged
craig-o-curtis merged 55 commits into
mainfrom
chore/251-core-8-final-review-with-gmt-reviewer-of-entire-api-pre-v1160
Sep 23, 2026
Merged

craig-o-curtis merged 55 commits into
mainfrom
chore/251-core-8-final-review-with-gmt-reviewer-of-entire-api-pre-v1160

Conversation

@craig-o-curtis

@craig-o-curtis craig-o-curtis commented Sep 16, 2026 •

Copy link
Copy Markdown
Contributor

Title: chore(gmt): final whole-API standards review before 1.16.0 (CORE-8, #251)


Summary

CORE-8 reviews the whole public API of @northguild/gmt before 1.16.0 ships. That is about 550 functions in ten namespaces, 90% of which predate the 1.16.0 epic, checked against TC39 Temporal, ECMA-402, the RFCs and the domain standards each area cites. Sixteen gmt-reviewer units swept the library by dimension and by namespace, and an external review then re-ran every claim on a rebuilt branch. Every finding was reproduced, then fixed test-first, together with its whole defect class.

Phase 2 follows the owner's decision of 2026-09-17: there is no 2.0.0, and GMT's users are internal. So every inconsistency and every breaking fix also ships in 1.16.0. That covers half-open positional intervals, RFC 9557 calendar strings, a unified unix/ namespace, Temporal option handling, and removing options that did nothing. Each breaking change is a minor changeset with a Breaking changes migration section, and there are no deprecation shims. Three re-review units then checked everything phase 2 touched, and those findings are fixed here too.


What Changed

1. New gates

  • scripts/api-surface.mjs (new, run in validate) fails on any of these:
    • a public file no subpath reaches;
    • a wrong @example result (2,797 of 2,961 run);
    • a wrong documented result in a docs page, README or skill (554 of 618 run);
    • an unresolvable documented import;
    • a broken reference or site link.
  • src/test/noThrow.test.ts feeds garbage to every argument of every export. It asserts no throw and the declared sentinel.
  • src/test/optionsObject.test.ts passes null, "x" and 1 as options to all 174 options-taking functions. src/test/requiredLocale.test.ts covers the 17 functions that require a locale.
  • scripts/temporal-compat.mjs gains a canary for the new calendar month-arithmetic workaround.
  • scripts/stats.mjs writes gmt-stats.json in the formatter's layout, so a stats:sync that changes a figure no longer fails the CI format check.

2. Half-open positional intervals (breaking)

  • The interval functions in plain/, utc/, zoned/ and unix/ are half-open [start, end) (SQL:2011 PERIOD, RFC 5545 §3.6.1), matching interval/. The ±1-unit steps are gone.
  • A zero-length interval counts 0.
  • intervalOverlappingDays* returns 0 for an empty intersection.
  • A split with an invalid unit returns [].

3. RFC 9557 calendar strings (breaking)

  • Calendar strings are ISO digits plus [u-ca=<id>], and zoned strings are …[zone][u-ca=…]. Calendar ids are the BCP 47 / Temporal ids (gregory, roc, islamic-tbla, ethioaa).
  • ;era= is gone, and the critical flag is accepted.
  • There is no converter for old strings: the migration regenerates them from ISO dates.

4. unix/ unified (breaking)

  • timeZone defaults to UTC everywhere.
  • Epoch arguments follow one grammar: a safe integer, or a string of optionally signed ASCII digits.
  • Units are passed as { epochUnit } options everywhere, including the converters and getUnixNow.
  • Interval length, count and split take epochUnit and timeZone.
  • Formatters return "" for an unknown zone, and nanosecond fields are three digits.

5. Options follow Temporal (breaking)

  • A non-object options argument, including null, returns the sentinel (GetOptionsObject). The Intl.DateTimeFormat formatters keep CoerceOptionsToObject.
  • Explicit undefined means omitted.
  • Unit names can be singular or plural everywhere.
  • setZoned, setUnix and cycleZoned default offset to "prefer". disambiguation applies to a date step into a DST gap.
  • An invalid reference or weekStartsOn returns the sentinel. The 17 required-locale functions reject [].
  • Locales accept string | string[].
  • formatRelative* picks units up to year.
  • Options that did nothing are removed (each confirmed inert against native Temporal):
    • the boundary disambiguation/offset members;
    • offset on addZoned, subtractZoned, intervalFromDurationZoned and convertPlainDateTimeToZoned;
    • offset and disambiguation on setUtc;
    • overflow on addTime, subtractTime, intervalFromDurationTime and cycleTime;
    • four FormatCalendarUnixOptions members;
    • fractionalSecondDigits on areUtcEqualBy/areDateTimesEqualBy.

6. Differences, parsing and string grammars (breaking)

  • Diff records:
    • In a diff* unit array, amounts of unlisted units fold into the next smaller listed unit, including weeks listed below months or years.
    • units = [] returns null.
    • diffUnix keys are plural.
  • Week numbers: Sunday-mode week numbering follows UTS B4 Add intervalIntersection #35.
  • Parsing: parseUnitFrom* reads microseconds and nanoseconds.
  • Annotations: elective [key=value] annotations are accepted and ignored. Unknown critical annotations are rejected (RFC 9557 §3.3).
  • String shapes: validators accept only the extended ISO 8601 shape. Offset zone ids are valid.
  • SQL literals: seconds are required, and years must be 0001–9999.
  • Leap seconds: isLeapSecond detects every second-60 spelling, and every validator still rejects them.

7. Invalid input, bounded work, internet grammars, DST and formatting

  • No throws on invalid input: 62 functions threw on nullish or wrong-typed arguments and now return sentinels.
  • Bounded work: the new maxPieces option (default 1,000,000) bounds splits, divides and mapped ranges.
    • splitIntervalByUnitTime no longer loops forever.
    • Non-ISO calendar month arithmetic no longer aborts the process: internal/temporalCompat/largeMonthSpan.ts does the arithmetic instead.
  • Internet date grammars: parseRfc2822 and parseHttp accept the full RFC 5322 and RFC 9110 receiver grammars and check the weekday. The formatters stay inside their grammars.
  • Zoned arithmetic follows Temporal §6.5.5/§6.5.6 across DST, and zoned differences measure calendar units on the wall clock.
  • Formatting follows the Temporal Intl spec (widths, era, …ToParts). Answers at the range limits are exact, and intervalDivideEqually* is exact.

8. Packaging

  • Undeclared nested subpaths are closed, and the dead ./regex/* entry is removed.
  • Temporal, Intl and toTemporalInstant are re-exported from every namespace subpath. UnixUnit is exported from /types.
  • typesVersions and sideEffects: false are added.
  • engines is >=22.16.0. CI stays on Node 22, 24 and 26.

9. Lint plugins

The gmt-oxlint, gmt-eslint and gmt-biome messages named convertUtcDateTimeToUnix, which doesn't exist, and the old positional getUnixNow. They now name convertUtcToUnix(…, { epochUnit }) and getUnixNow({ epochUnit }).

10. Docs site

  • Widgets: two widgets used the old positional converter calls, and the zoned-input repairer now writes ±HH:MM offsets.
  • Reference corpus: it keeps | null in signatures (the generator now runs with strictNullChecks) and no longer truncates long signatures.
  • Test isolation: importing the generator in a test no longer regenerates the pages another test reads.
  • Docs pages: about 180 documented results, imports and links are corrected. The READMEs, guides and agent skills are updated for every change above.

11. GMT covers every Temporal bug it reported upstream

While testing against the spec, GMT found defects in @js-temporal/polyfill and in the proposal's own reference implementation, and filed them upstream: 11 issues and PRs across js-temporal/temporal-polyfill and tc39/proposal-temporal, most with a verified patch, plus a twelfth tracker entry that is not a filing of ours — our comment on the maintainer's PR #361, which already carried the D11 port.

Outcome as of 2026-09-23 (re-checked; nothing has moved since 2026-09-21). All six polyfill PRs were closed unmerged, and none was rejected: ports must land "in order" so the maintainer's rebase stays manageable (#367–#369), and new fixes should be applied "in proposal-temporal and pull[ed] in here after" (#370–#372). #373 (release) is open and answered — one release once the rebase has caught up. #361 merged on 2026-09-21, landing 50d66d2 (D11) plus D3, D5 and D7 on the polyfill's main. Nothing is released, so every GMT workaround stays and pnpm compat is unchanged at 13 of 14 groups needed.

  • New zoned.E workaround: the polyfill starts its time zone transition search in 1847, so the 1844-12-31 date-line crossing in Asia/Manila, Pacific/Guam, Pacific/Saipan, Pacific/Kosrae and Pacific/Palau was never found. GMT now finds it from offsets alone, for instants before 1847 only (zero cost after). Fixed: getDstTransitions (missed the crossing), getHoursInZonedDay (returned 479,340 hours), roundZoned/roundUnix by day (""), startOfZoned/floorToZone/areZonedEqualBy by week (a day off), intervalCountZoned (counted the missing day) and addZonedBusinessDays (jumped to 1899). Its canary retires it once a release contains Start the time zone transition search before the first transition in 1844 js-temporal/temporal-polyfill#372.
  • New D10 canary: Polyfill: calendarToIsoDate overshoot assertion from #3292 is reachable in coptic/ethiopic/ethioaa month 13 tc39/proposal-temporal#3329 (coptic/ethiopic month 13 in far years) can't reach GMT, which computes those calendars through ethioaa and finds dates from fields by bisection. The canary and 40 far-year test rows pin that; a polyfill failure that is not a RangeError now falls back to GMT's own algorithms instead of an empty result.
  • Business-day walks are fast: they walk integer day counts instead of building a Temporal object per day, so a capped 200,000-day walk takes milliseconds (it could exceed CI's 5 s test limit). Results match the previous implementation on 47,749 inputs.
  • The canary now reports 13 workaround groups: every one except D10 still needed on polyfill 0.5.1, as expected.

12. Docs site: "Helping fix Temporal", and a faster dev start

  • New page /upstream/ ("Helping fix Temporal", under Start here), built around the defect rather than the filing. It leads with what GMT works around and whether it still has to — one row per canary group, with the "still needed" column read from temporal-compat.json, which pnpm compat:snapshot writes from the same probes pnpm compat runs and validate gates. 14 groups, 81 probes, 13 still needed. The filings table follows as "what we sent, and what happened to it", where a closed row carries the maintainer's own reason; upstream check now fails on a closed-unmerged filing with no explanation, because a bare "Closed" is the misleading case. Status refreshes from GitHub during generate (cached for 6 hours, falls back to the committed snapshot offline). The page asserts no upstream pipeline stage: commit presence proved unreliable (tc39 squash-merges, so a fix's original sha reports as absent from main while its code is there), so it carries only what regenerates itself.
  • The page is written to ISO 24495-1. It used to open with three paragraphs on our filing tactics and answer the reader's own question — do I need to do anything? — in section four; it now answers it in the first line. rebase and routed are gone, and the twelve filing descriptions are split so the symptom comes before the cause. Average prose sentence is 12.6 words.
  • llms.txt, llms-full.txt and the site chat now include every top-level page (they skipped Why GMT, Core rules, Install and the home page).
  • The home page's "No borrowed test oracle" card now says exactly what is true: expected answers come from the standards, test262 and Chrome's built-in Temporal, never from the polyfill or another date library.
  • Faster pnpm dox:dev: the generator writes only files whose content changed (it used to rewrite about 1,300 on every start), an incremental gmt build replaces the full one before dev, a dev-only watcher regenerates the reference when gmt source changes, and VS Code no longer watches generated output.

13. Story, tracker and changesets

  • context/domination/issues/CORE-8.md holds the review units, triage, the findings ledger for every wave, and Phase 2 with its decisions of record.
  • context/coding-standards.md gains § "Intervals are half-open" and the RFC 9557 calendar-string rules. The changeset policy is now: a breaking change is minor with a Breaking changes section, and major is never used.
  • context/domination/tracker.md marks CORE-8 Done.

14. Review response (baldurpan, 2026-09-20)

The review rebuilt the branch and re-ran every headline claim rather than reading it, then filed 3 blocking
findings, one root cause, 16 docs/tooling items, 3 questions, a coverage-gap list and an addendum. All of it is
answered on this branch: 19 findings fixed, 1 refuted from the primary source, every coverage gap closed — and
one of those gaps was hiding a real bug.

  • Refuted — formatDate/formatTime returning "". The review read toLocaleString as constructing with
    ~any~. The Temporal amendment constructs [[TemporalPlainDateFormat]] with (date, date, relevant)
    (§15.4.1), GetDateTimeFormat returns null for a lone time field under inherit = relevant (§15.6.1),
    and HandleDateTimeTemporalDate then throws a TypeError (§15.6.15). Chromium 153 and the polyfill both
    throw for every listed row, so "" is the correct sentinel and the JSDoc is right. The ~any~ rules belong
    to new Intl.DateTimeFormat(…).format(plainDate), which is what formatDateToParts uses. No change.

  • A polyfill defect, found by closing a coverage gap (D11). The review noted that
    intervalLength* in calendar units had only ever been compared against the same until/total
    primitives the implementation calls. An oracle built from add and instant subtraction alone
    disagreed, and Chromium 153 sided with the oracle. The cause is
    tc39/proposal-temporal#3168, fixed
    upstream by #3172 (5dd0b0d97ee1, merged
    2025-11-19) and not ported by @js-temporal/polyfill on 0.5.1 or main: TC39 recomputes the
    nudge window one unit further along when the target falls outside it, and the polyfill computes it
    once. It reaches three operations, all only from a relativeTo past the 28th, where adding a month
    constrains the day:

    Operation Rows scanned vs Chromium 153 Mismatches before After
    Duration#total 2,016 16 0
    Duration#round 12,960 15 0
    until/since with a calendar smallestUnit 9,940 15 0
    Duration.compare 256 0 0 — reaches no nudge window

    intervalLengthDateTime("2024-01-31T00:00:00", "2024-02-29T12:00:00", "month") was
    1.0172413793103448 and is now 1.0161290322580645; worse,
    normalizeDuration("P30DT12H", { smallestUnit: "month", roundingMode: "floor", relativeTo: "2024-01-30" })
    was "PT0S" and is now "P1M" — a whole month. Fixed at five seams (monthTotalBySpec,
    monthRoundBySpec, the zonedUntil gate, plainUntilWithRounding, and a D11 term in
    plainDateUntil), all routed to the nudgeToCalendarUnit GMT already owns. Probe-gated, retires
    by canary, written up as section H of the polyfill bug doc.

    Nothing was filed, and nothing should be. The port into @js-temporal/polyfill was already
    written by the maintainer — 50d66d2 in open PR
    #361 — so instead of a duplicate we
    added the evidence that PR was missing: a reproducer, the Chromium-153 scan, and before/after
    measurements of the commit applied to main
    (comment).
    50d66d2 is not on main and in no release, so every D11 workaround stays, and the removal
    trigger is now two gates: the commit lands on main, then ships in a release.

  • A second, GMT-side defect found by the same scan. Rounding a span that lands exactly on a whole
    month expanded it: ceil on P60D from 2024-01-31 gave P3M where Chromium gives P2M. TC39
    handles it inside ApplyUnsignedRoundingMode ("if x is equal to r1, return r1"); GMT's form takes a
    comparison rather than the value and could not see it. It was live for the non-ISO calendars before
    this PR. Fixed, pinned in ISO and hebrew, and it stays when D11 retires.

  • Six exports threw on ToString-hostile input (the review found 3): isLeapSecond, getNowUnit,
    getUtcNowUnit, getZonedNowUnit, getTimeZoneOffset, spanWallClock. Each guards the type and returns its
    sentinel; the no-throw gate's garbage set gains a throwing toString, a null-prototype object and a symbol.

  • locale: [] followed the host LANG in the three pattern parsers. An empty list is now the same as an
    omitted one ("en-US"), pinned by a table that fails under LANG=fr_FR/ja_JP before the fix.

  • null option members: 90 pairs across 62 exports (the review estimated 23). Temporal treats a null
    member as present-and-invalid; GMT returned the default. Every site now reads the member with an explicit
    undefined check. optionsObject.test.ts gains a generic pin: { m: null } must equal { m: "zzbogus" }
    for every documented member, which covers the class.

  • startOfQuarterForDateTime kept its sub-second fields (the addendum's finding) — pre-existing, fixed here
    rather than deferred, and the new rows use fractional inputs so the gate can see it.

  • Every coverage gap closed with tests, not caveats. A brute-force oracle for mergeIntervals* and
    intervalXorAll* over lists of three to eight (720 cases, all six families); reconstruction of difference
    records over deliberately gappy unit lists (312 cases, including through DST); diff* rounding values
    across all nine Temporal modes in both directions (1,368 cases, expected values from bigint longhand); the
    RFC 9557 annotation grammar swept shape by shape across six parsers against a Chromium-sourced table (132);
    and interval lengths against the longhand oracle that found D11. The round* family now documents how many
    fractional digits each unit prints. maxPieces stays off mergeIntervals*/intervalXorAll* deliberately:
    a boundary sweep emits at most as many spans as it was given, so there is no enumeration to bound.

  • Docs, changesets and gates: a third stale clock example (formatRelativeTime), stripMdx being two
    defects rather than one, and diffUtc carrying the same wrong Instant.until() claim as the diffUnix pair.
    api-surface check and upstream check run in CI's consistency job, and the skip budget is pinned
    (164 examples / 64 documented results), so a silent increase fails.

  • The text surfaces render the whole page, not the JSX. Following the review's wider stripMdx note, the
    .md, llms.txt, llms-full.txt and retrieval surfaces now render <Mistake> and <Scenario> to Markdown
    (73 cards that shipped as raw JSX), unwrap the layout components, and evaluate the splash pages'
    {gmtStats…} expressions at build time from the same modules pnpm stats:sync writes. llms-full.txt went
    from 80 unevaluated expressions to 4, and those four are prose ({yyyy}, {MM}, {dd}, {RelativeUnit}).
    Stripping now leaves fenced code alone, so a sample keeps the import line that names its function. Four
    tests fail the build if any of it returns.

  • Decisions of record: parseHttp keeps rejecting a mismatched weekday in all three HTTP-date forms,
    asctime included, and says so; the gregorian / ethiopic-amete-alem alias asymmetry stays as it is and is
    recorded as a divergence.


15. No native Date anywhere, and the lint that forbids it now runs

The repo ships four lint plugins whose whole purpose is banning Date, and none of them had ever linted this repo. Root oxlint.config.js failed to load on five separate counts: oxlint auto-discovers only .oxlintrc.json, .oxlintrc.jsonc, oxlint.config.ts and oxlint.config.mts, so the filename was never read; defineConfig([...]) returns an array where an object is required; the plugin was registered as plugins: [plugin] rather than jsPlugins: [specifier]; files: { include, ignore } is not a field, ignorePatterns is; and the two rule names were Biome's camelCase. packages/gmt and apps/dox each run their own oxlint from their own directory, where a root config never applies, so both now carry one — and packages/gmt resolved oxlint@^1.0.0 to 1.59.0, which predates JS-config discovery, so it is pinned to ^1.78.0. All three scopes now flag all four Date APIs, verified by linting a deliberate violation rather than by trusting a green exit.

That left date-ban.test.ts as the only live enforcement, and it did not scan apps/dox/worker — which is where all eight production calls had collected. It now scans the Worker, packages/gmt/src, .tsx and test files, and covers 1,705 files.

The sweep exposed a real bug. allowanceSpent picked the soonest quota reset with a bare .sort(), which is chronological only because toISOString is fixed width. nextMidnightMs returns nowMs unchanged when its zone lookup fails, so a stray subsecond can reach that list, and …T00:00:00.500Z sorts ahead of …T00:00:00Z because . precedes Z — putting the later instant first. It now uses sortUtc, which compares instants.

convertUnixToUtc emits 2026-06-16T07:00:00Z where toISOString emitted the same instant with a .000 fraction, so visitor.resetsAt and each providers[].resetsAt change shape. Every client parses them through GMT, whose utcDateTime grammar makes the fraction optional, and no type, schema or doc commits to millisecond precision.

Fixtures now say what they mean: Date.UTC(2026, 5, 16) reads as May and means June. The 27 in apps/dox call convertUtcToUnix directly. GMT's own 11 go through a Temporal helper, because a fixture built by the library cannot catch the library being wrong — setUnix.test.ts feeds a converted instant in and compares against a converted instant out, so an hour of drift would cancel on both sides and still pass. One Date call remains in the repo: elapsed time against a file's mtimeMs.

16. Review follow-ups, the plain-English skill, and CI

The three Questions from the review were answered in 44447b7 but never said back to the reviewer; a follow-up comment now points at where each landed. Closing the one genuinely open half — mapDatesInRange and mapZonedDatesInRange never naming the interval* half-open convention in their JSDoc — turned up another instance of a class the review had flagged: the migration bullet recommended subDate, which has never been exported. All 37 pending changesets were swept for names that resolve to nothing in the built dist; that was the only real hit.

A plain-english skill now carries ISO 24495-1 for every surface a person reads rather than runs — commit messages, changesets, PR text, review comments and upstream filings — wired into AGENTS.md as Core Rule 13 and into the four skills that emit that prose.

A CI fix that matters for this PR. Story consistency was failing on seven broken site links to /guides/concepts/dst-disambiguation/. The page is generated, not committed: docs/dst-disambiguation.md is the canonical source and build-guides.mjs copies it in. The job built @northguild/gmt but never generated the dox content, so those pages did not exist on a fresh checkout and seven pages linking to them came back broken. Reproduced locally by deleting the generated files, then fixed by generating them before the check. All eight steps of that job now pass from a clean state.


17. Second review response (baldurpan, 2026-09-23)

A second pass reproduced the D11 work against Chrome 153's native Temporal — 58,968 rows, 0 mismatches, where the raw polyfill gives 68 — then filed four findings. All four were real. Two were larger than reported, and closing the first exposed a third hole. The reviewer named the shape himself: an enumerated list standing in for a general rule, where what slipped through is what was not on the list.

  • Hostile input now returns the sentinel. 221 of the 551 exports threw when an argument resisted inspection: a revoked Proxy throws on get, ownKeys and Array.isArray, and an object with a throwing getter throws when a rest-spread enumerates it. Those operations sat in the guards above each function's try, so the guard written to reject invalid input raised instead. Every affected export now evaluates its body inside the try whose catch already returned its sentinel. Three corrections to the report: it is 221 of 551, not 556; the revoked Proxy carries all of it (the throwing getter accounts for 4, all already in that set); and while "options destructured outside the try" explains those 4 and ~126 options-slot cases, roughly 95 fail at non-options positions in functions that take no options at all, dying in Array.isArray and string coercion. GARBAGE now also carries a proxy that throws on every trap — 153/551 on its own, and hostile to any access rather than to one named operation.

  • The /upstream/ defect table reached no text surface. A raw <UpstreamDefects /> shipped into dist/upstream.md, llms-full.txt and the retrieval chunks, so all seven upstream chunks carried zero defect rows while the HTML carried eleven — and the prose around it spoke of "each row" and "the middle column" over nothing. The gate that should have caught it matched nine component names: exactly the nine renderMdxComponents handles, so it mirrored the handler list rather than checking it. It now matches any capitalised tag, excluding TypeScript generics, across the same three surfaces its sibling scans. That general form found eight more unhandled components, including WhyDateBug — the home page's own case against Date, whose faults table had never reached the text surfaces either. A second tally was missing too: the "we have sent 11 reports and fixes" sentence moved into a component last round to escape the {expr} problem, which put it out of reach entirely. upstream.md is 8,357 bytes against 5,368.

  • The vendored carve-out's premise was false. Four unmarked Date calls lived under ai-elements/, one a calendar value formatted into a user-visible filename on the site that argues Date is the wrong tool. The exemption called that tree "vendored, unmodified". All 15 oxlint pragmas naming this repo's own plugins were present in the vendoring commit itself, prompt-input.tsx was edited again afterwards with a LOCAL MODIFICATION (GMT) marker, and the directory's own README says "we own and re-theme every component here". The tree is now linted for Date while staying exempt from the style rules, the stopwatch takes a monotonic clock, and a test pins the three carve-out lists against each other. ui/** stays exempt — 18 files, no pragmas, no Date.

  • A D11 seam that corrected nothing is gone. plainDateUntil routed ISO date differences through the nudge-window workaround whenever the start day was past the 28th. The defect cannot reach there: the window only misses its target when the target sits strictly between its bounds by a sub-day amount, and two PlainDates have no time component. Measured over ~1.96M rows against the raw polyfill, both directions, every rounding mode and increment 1–12: zero divergence. Removing it costs no answer and withdraws GMT's own exact-boundary behaviour from ISO date differences. The changeset and the bug doc no longer claim a diffDate fix that was never scanned.

  • And the gates behind all of this had never run in CI. All three text-surface checks early-return when apps/dox/dist is absent, and the Tests job builds only @northguild/gmt — the site is built in a different job, on a different runner. A skip reports as a pass, which is how the raw tag shipped green, and the generalised gate would have rotted the same way. Story consistency now builds the site and runs those checks, and outside a local run a missing dist fails loudly rather than passing silently.

18. Discord

A Discord link sits beside GitHub in the site header, generated from Starlight's social config like its neighbour, plus the discord glyph in the local Icon.astro (which carries only 22 of Starlight's icons, so the config entry alone would have rendered an empty <svg>). Also in both READMEs — packages/gmt/README.md ships in the npm tarball, so it is user-facing there.


Why

  • Per-story review can't see defects that sit between namespaces, and 90% of the surface predated the epic's reviews.
  • Documented examples were never executed; wrong ones had been fixed by hand four times, so the class needed a gate.
  • The API contract is that invalid input returns a sentinel and never throws. A harness now enforces that for every export.
  • Several functions contradicted a primary source (TC39 Temporal, ECMA-402, RFC 5322, RFC 9110, RFC 3339, RFC 9557, UTS B4 Add intervalIntersection #35, SQL:2011).
  • A handful of inputs could abort the host process.
  • The owner ruled that 1.16.0 ships every fix, with no 2.0.0 backlog.

Validation

  • No-throw harness: 556 of 556 with the widened garbage set (a revoked proxy, an all-trap proxy and a throwing getter), where the same set reported 221 failures before the fix.
  • CI green on the pushed branch — run 35838871823 on daebdde, all 38 jobs passing, including Story consistency, which had been failing on seven broken site links to a page that is generated rather than committed.
  • Full pnpm run validate (all steps): passed on the final tree:
    • deps check ✅, test-markers check ✅, build ✅, stats check ✅
    • api-surface check ✅ (2,797 of 2,961 examples and 554 of 618 documented results run, 0 failing), upstream check ✅ (12 filings), compat snapshot --check ✅ (14 groups, 13 still needed)
    • lint ✅, typecheck ✅ (including astro check)
  • Tests:
    • @northguild/gmt: 36,348 tests ✅ (27,334 at the start of the story)
    • apps/dox: 4,283 tests ✅
    • gmt-oxlint: 97 tests ✅
  • Temporal compatibility:
    • pnpm compat ✅: 14 workaround groups; zoned.E still needed (5 of 5 probes), the new D11 still needed (6 of 6), D10 removable on 0.5.1 (pinned against regression)
    • pnpm compat:oracle ✅: 409,552 comparisons against Chromium 153 native Temporal, 0 mismatches, including after phase 2
  • Differences: the unit-array folding was checked against native Temporal over 5,500 random rounding cases, with 0 mismatches.
  • Code quality: npx fallow audit clears every finding this branch introduced except eleven tool artefacts, recorded here rather than suppressed. Every function over 60 lines is split — including runTwin (271 lines) into one function per scan — the clone group is gone, and two unused devDependencies are removed. The eleven that remain: nine are CRAP-only, and with no coverage data CRAP is cyclomatic² + cyclomatic, so clearing them means cyclomatic ≤ 4 in every function of a CLI script — the trap .fallowrc.jsonc already documents for api-surface.mjs; two are fallow not resolving Astro's customCss, which it demonstrates by also flagging gmt-tokens.css, the file defining every token on the site. No thresholdOverrides were added for any of it.
  • runTwin was verified, not assumed. It is untested comparison code, so the refactor was checked by capturing an oracle baseline from the committed version and re-running after: 409,552 comparisons against Chromium 153 across every calendar, 0 mismatches, identical. compat check output diffs byte-for-byte against HEAD.
  • Test-first: every fix was confirmed red against the pre-fix code, then green. The 1844 rows and the far-year coptic rows use values from Chromium 153 native Temporal.
  • Upstream tracker, rebuilt around defects: /upstream/ leads with what GMT works around and whether it still has to, generated by pnpm compat:snapshot from the same probes pnpm compat runs and gated by validate. The filings table follows as "what we sent, and what happened to it", where a closed row carries the maintainer's own reason — upstream check now fails on a closed-unmerged filing with no explanation. The page asserts no upstream pipeline stage: commit presence proved unreliable (tc39 squash-merges, so a fix's original sha reports as absent from main while its code is there), so it carries only what regenerates itself.
  • The docs site and the scripts use GMT for dates. UpstreamTracker.astro, upstream.mjs and temporal-compat.mjs formatted and compared timestamps with new Date; they now use getUtcNow, parseDateFromUtc, isValidUtc and isValidDate. The remaining Date.now() calls are elapsed-time budgets, now performance.now() where a monotonic clock is the correct primitive.
  • Six bugs in our own tooling, found doing this: pnpm upstream:sync had never worked past the first repository (REPOS.flatMap(discover) passes the array index as discover's timeout); buildFiling silently dropped hand-written fields it did not name, and a dropped "whose PR is this" flag reads as ours; a merged filing broke the state enum, which no filing of ours had ever exercised until #361; the site used new Date; apps/dox had no testTimeout, so import-bound tests at ~3.9 s failed against Vitest's 5 s default whenever pnpm -r run test shared a CPU with packages/gmt's 36k tests (now 20 s, and this would have failed CI on a slower runner); and two devDependencies were declared that nothing imports — @fontsource/michroma (its one .woff2 has been committed since DOX-A3: JSDoc-driven MDX reference generator and site shell (132) #161) and @open-wc/testing. Removing them pruned 1,404 lines of transitive lockfile entries. The lockfile is regenerated with them, because pnpm install --frozen-lockfile runs in five CI jobs and fails on a manifest/lockfile mismatch.
  • The defect table was unreadable, and the cause is worth recording. Starlight sets display: block on content tables so wide ones scroll; a block box is not a table box, so table-layout: fixed and width: 100% never reached the columns, and with gmt-content.css setting white-space: nowrap on every first cell, column one grew to 862px inside a 612px page. Fixing it from a scoped <style> is impossible: Astro compiles scoped styles to :where(.astro-hash), which carries no specificity, so a component rule can never outrank a site rule. The styles now live in src/styles/gmt-upstream.css.
  • Known and left alone: apps/dox/tsconfig.json sets jsx: react-jsx for its React islands, which disables Astro's Props inference, so Astro.props is Record<string, any> in every .astro file in the app. The new components assert their Props; changing the compiler option site-wide is its own piece of work.
  • pnpm install --frozen-lockfile verified after the dependency removals, so the five CI jobs that use it will install cleanly.
  • scratchpad/ is now gitignored. It held four stale validate-*.log files (~400 KB of build output, one of them a truncated run) and was untracked but not ignored, so git add -A would have committed them.
  • Release notes: every "before" value in the Breaking changes tables was run against the published @northguild/gmt@1.15.0, and every "after" value against this branch.

Risk / Impact

High (intentional). 1.16.0 carries breaking changes for any caller that:

  • uses the positional interval functions' closed-end behaviour;
  • stores calendar strings;
  • passes positional units or null options to unix/ functions;
  • relies on the removed options or on diff* unit arrays.

Every breaking change is listed with old → new calls in its changeset's Breaking changes section. Stored calendar strings must be regenerated from their ISO dates, not parsed. All known users are internal. The native-Temporal oracle shows no drift.


Release

minor for @northguild/gmt, rolling into 1.16.0 with the other pending changesets. patch for @northguild/gmt-oxlint, @northguild/gmt-eslint and @northguild/gmt-biome. Merging publishes nothing.

Breaking (minor) changesets:

  • half-open-positional-intervals.md
  • rfc9557-calendar-strings.md
  • unix-unified.md
  • options-follow-temporal.md
  • diff-records-and-parsing.md
  • string-grammars-follow-temporal.md
  • package-exports-and-engines.md
  • steady-zones-floor.md
  • unix-validation-and-rounding-options.md

Additive (minor): piece-limits.md (maxPieces) and unix-parse-exports-week-rule.md.

Patch:

  • boundary-and-parts-defaults.md, documented-examples-execute.md, invalid-input-never-throws.md
  • internet-date-grammars.md, leap-seconds-zone-names-calendar-ids.md, zoned-arithmetic-across-dst.md
  • intl-formatting-follows-temporal.md, range-edges-and-exact-results.md, roll-convention-provenance.md
  • lint-messages-name-real-functions.md (the three lint plugins)
  • date-line-crossings-1844.md (the zoned.E fix)
  • earlier epic changesets updated so none describes behaviour that no longer ships

Checklist

  • Each "What Changed" section has a corresponding entry
  • Type-check passed
  • No secrets committed
  • Changeset present with the correct bump

…es and site links (CORE-8, #251)

api-surface.mjs derives the public surface from the reference corpus and fails validate on an unreachable public file, a wrong @example result, a wrong documented result on a doc page, an unresolvable documented import, or a broken reference or site link. Clock-dependent calls are skipped only when the function reads Temporal.Now and the call does not pin a reference. build-reference.ts now treats a rest parameter and the documented elements of a trailing rest tuple as optional, so their playground forms are generated.
…w harness over every export (CORE-8, #251)

Moves the startOrEndOf and resolveUnixIntervalPair helpers into internal, adds isObject, maxPieces, unixEpochInstant, resolveManualRoundingOptions, resolveMinimalDaysInFirstWeek, plainFormatOptions, rfc5322DateTime and httpDateFields, and tightens leap-second and calendar-id guards. test/noThrow feeds garbage to every exported function and asserts no throw and the declared sentinel.
…-8, #251)

No-throw guards, plural smallestUnit in round*, startOfDate day, rejected invalid roundingMode, equal-by bucket comparison, maxPieces caps on splits, divides and mapDatesInRange, ToParts and formatCalendar without leaked zone names, isLeapYear and getWeekNumber validate first, cycle amounts validated, documented examples corrected.
…-8, #251)

RFC 2822 parser accepts the receiver grammar and obsolete syntax, rejects a mismatched weekday; RFC 2822 and RFC 3339 formatters stop emitting out-of-grammar years and sub-minute offsets; leap seconds rejected in every spelling; IANA single-component zone names accepted; isValidZonedRange calendar gate; getLocaleZonedEndOfWeek nanosecond default; maxPieces caps; no-throw guards.
…#251)

parseHttp accepts rfc850-date and asctime-date and rejects a mismatched weekday; formatHttp rejects years outside IMF-fixdate; areUtcEqualBy compares bucket floors; plural smallestUnit in roundUtc; week parsing for expanded years; maxPieces caps; no-throw guards.
#251)

Exports parseMillisecondFromUnix and parseMinuteFromUnix; validates epochUnit everywhere; blank epoch strings no longer read as 1970; sort, min and max drop non-instants and no longer overflow the stack; FormatCalendarUnixOptions drops four options it never read; interval JSDoc states milliseconds; invalid-input examples; maxPieces caps; plural smallestUnit in roundUnix; no-throw guards.
…ections (CORE-8, #251)

formatDuration renders sub-second parts exactly; FILETIME values at or above 2^63 are out of range; businessDaysBetween never returns -0; toOffsetInstant returns IANA-cased zone ids; timeZoneLike accepts every IANA name; date regexes reject -000000 and RFC 3339 offsets are range-checked; modifiedPreceding cited to FpML, not ISDA 4.12(a); JSDoc corrections for NTP zero, .NET Kind, ignored zone annotations, relativeTo resolution and rounding increments.
…READMEs and skills (CORE-8, #251)

Every documented import resolves, every documented result is what the function returns, and every internal link resolves; api-surface now enforces all three. Also corrects the swapped fiscal-year figures in CORE-5 and the ISDA citation for modifiedPreceding in CORE-7.
@craig-o-curtis craig-o-curtis added this to the Domination milestone Sep 16, 2026
@craig-o-curtis craig-o-curtis added bug Something isn't working documentation Improvements or additions to documentation gmt labels Sep 16, 2026
@craig-o-curtis craig-o-curtis changed the title CORE-8 pre v1.16.0 fixes CORE-8 pre v1.16.0 fixes 251 Sep 16, 2026
…ention citation (CORE-8, #251)

Minor for the newly exported unix parse functions, the three signature types and the minimalDays option; patch for the endOf and ToParts defaults and the corrected @example results. The roll-convention changeset now cites FpML for modifiedPreceding. These will be extended as the remaining review rounds land.
)

CORE-8 takes build-order row 8; only the # column renumbers. The findings ledger covers rounds 0 and 1 and is extended as the later rounds close.
…FC 5322 comments, stricter example gate (CORE-8, #251)

The polyfill adds and differences non-ISO months one at a time, so a large month span in persian, hebrew, islamic, ethiopic or japanese aborted the process with a heap OOM. temporalCompat/largeMonthSpan.ts jumps whole years using only Temporal calendar reads, is dormant below 1,200 months, and has a canary in pnpm compat. Adds divisionBoundary (exact bigint division), measureNearRangeEnd, zonedLocalDates, instantFormatOptions and a spec-exact time format path, and folds RFC 5322 comments in one linear scan. api-surface now runs invalid-input examples of clock accessors and examples whose aside contains a dash.
…edges and time spans are exact (CORE-8, #251)

formatDate, formatDateTime, formatTime and the range formatters keep requested field widths, give era alone its defaults, ignore timeZoneName on plain values, and reject a style that does not apply. roundDate and roundDateTime return representable floors in the last year; the week functions work at both range limits; intervalDivideEqually is exact; mapDatesInRange keeps dates at the maximum; intervalFromDurationTime rejects spans of 24 hours or more and negative wraps; diff units arrays, sunday week numbering, areDatesEqualBy and calendar-id compatibility are documented.
…mporal across DST (CORE-8, #251)

addZoned, subtractZoned and intervalFromDurationZoned add the time portion in exact time and apply disambiguation only to the date step; diffZoned, diffZonedAsDuration and intervalLengthZoned measure calendar units on the zone wall clock and return the sentinel across two zones; intervalOverlappingDaysZoned counts the local dates actually touched; formatZonedDateTime keeps requested widths and formatZonedToParts and formatZonedRange reject a timeZone option; exact division, range-edge splits and counts; corrected compatibility notes.
… and five (CORE-8, #251)

formatUtc and formatUnix follow the Temporal Intl spec; intervalOverlappingDaysUnix counts local dates touched; exact division; range-edge splits and counts; unix functions document their time zone defaults; FormatCalendarUnixOptions keeps its four never-read members as deprecated so existing calls still type-check; bucketRange returns the representable bucket at the maximum and refuses over-cap ranges without walking them.
… in the READMEs, guides and agent skills (CORE-8, #251)
… (CORE-8, #251)

Adds eight changesets: piece-limits (minor, the maxPieces option) and seven patches covering invalid input, internet date grammars, leap seconds and zone names, zoned arithmetic across DST, Intl formatting, range edges and exactness, and unix validation and rounding options. Updates two earlier CORE-8 changesets. The CORE-8 findings ledger covers every review wave, and the tracker marks CORE-8 Done.
@craig-o-curtis craig-o-curtis changed the title CORE-8 pre v1.16.0 fixes 251 CORE-8 pre v1.16.0 fixes #251 Sep 17, 2026
@craig-o-curtis craig-o-curtis linked an issue Sep 17, 2026 that may be closed by this pull request
…s section; major is never used (CORE-8, #251)

Owner decision 2026-09-17: GMT users are internal, so there is no 2.0.0. AGENTS.md core rule 11, PUBLISHING.md, the finalizer and the changelog, tanstack-intent and update-readme skills drop major and describe the Breaking changes migration section. dox-builder states that package exports declare only the root, namespace and category barrels.
… RFC 9557 strings, with contract tests over every export (CORE-8, #251)

Adds internal helpers for GetOptionsObject checks, required locales, half-open interval relations, instant interval text and the diff weeks carry, and removes the closed-interval and calendar-suffix helpers. New contract tests pass null, strings and numbers as options to every options-taking export and cover the 17 required-locale functions. Package exports close undeclared nested subpaths, add typesVersions and sideEffects false, and set engines to node 22.16.0 or later. The api-surface and temporal-compat checkers and the fallow config follow.
…ls and RFC 9557 calendar strings (CORE-8, #251)

Positional intervals are half-open. Calendar strings are ISO digits plus [u-ca=id]. Non-object options return the sentinel, units are singular or plural, diff unit arrays fold unlisted amounts into the next smaller listed unit including weeks, and inert overflow options are removed from addTime, subtractTime, intervalFromDurationTime and cycleTime. isLeapSecond detects every second-60 spelling.
… half-open intervals (CORE-8, #251)

setZoned and cycleZoned default offset to prefer, disambiguation applies to a date step into a DST gap, and inert offset options are removed from addZoned, subtractZoned, intervalFromDurationZoned and convertPlainDateTimeToZoned. diffZoned accepts singular units, non-object options and an invalid reference return the sentinel, and zoned calendar strings are zone then [u-ca=id].
@baldurpan

Copy link
Copy Markdown
Contributor

Review — CORE-8

Reviewed by dimension (internal helpers, plain/utc, zoned/unix, test gates, packaging/changesets, dox). I rebuilt the branch and re-ran the claims rather than reading them; the engineering here is strong and the headline numbers are all real.

Independently reproduced: 33,432 / 2,594 / 97 tests green (exact claimed counts) · api-surface, stats, deps, upstream, lint, typecheck clean · compat 13 groups with zoned.E 5/5 still needed · compat:oracle at 409,552 comparisons / 0 mismatches vs native Temporal · all 19 half-open table rows and all 12 RFC 9557 rows (the latter checked against the published 1.15.0 tarball, which confirmed every "before" value) · throw class 45 → 0 exports on plain garbage, no regressions · business-day rewrite 24,009 differential comparisons / 0 mismatches · roundDateTime 1,323 combinations identical to 1.15.0 · maxPieces bounds hold in ms, always the sentinel and never a partial value.

Two honest caveats on that list, since it reads stronger than it is. The oracle compares calendar fields and arithmetic only — it has no toLocaleString or DateTimeFormat comparisons, so its 0 mismatches say nothing about finding 1. And my "no throws" sweep used values that all stringify, so it could not see finding 2 — the same blind spot as the harness itself.

I also checked the new gate isn't vacuous: corrupting one @example makes it exit 1 with a precise diff, and a stray directory trips its unreachable-file check.


Blocking

1. formatDate / formatTime return "" where the spec requires a string.
plain/format/formatDate.ts:56, plain/format/formatTime.ts:49; root cause internal/plainFormatOptions.ts:91-98,132.

formatDate("2024-02-03", "en-US", { hour: "numeric" })  →  ""    native V8: "2/3/2024"
formatTime("14:30:45",   "en-US", { year: "numeric" })  →  ""    native V8: "2:30:45 PM"

Also {minute}, {second}, {dayPeriod}, {hour,minute} on dates and {weekday}, {month}, {day} on times. toLocaleString is CreateDateTimeFormat(…, ~date~, ~date~), whose time-field loop is guarded by required is time or any — a lone hour can't clear needDefaults, so year/month/day default to numeric and a string comes back. The helper implements the Intl.DateTimeFormat-constructor ~any~ semantics at a toLocaleString entry point. formatDateToParts/formatDateRange use it correctly. The JSDoc also asserts a TypeError the spec doesn't raise (only its timeStyle half is right).

This predates the PR, but plainFormatOptions.ts is new here and the changeset is "formatting follows the Temporal Intl spec", so it seems in scope for the final sweep.

⚠️ Please don't fix by aligning to V8 wholesale — the mirror case dateStyle+timeStyle on a PlainDate must throw, and there GMT's "" is correct while V8 diverges. Worth a TAGGED_MISMATCHES row so nobody "fixes" that later.

2. Three exports throw instead of returning their sentinel.
plain/validate/isLeapSecond.ts:37, plain/get/getNowUnit.ts:43, utc/get/getUtcNowUnit.ts — any argument whose ToString throws or is absent:

input these three siblings (isValidDate, …)
{ toString() { throw } } throws Error false
Object.create(null) throws TypeError false
Symbol() isLeapSecond throws TypeError false

Missed because test/noThrow.ts:69's garbage set (null, undefined, 0, "", "x", [], {}, NaN) contains nothing that triggers a ToString. Adding those three values catches all three functions. Worth noting how narrow this is: 1.15.0 had ~175 Symbol-throwing call sites and this branch has one.

3. locale: [] makes the pattern parsers follow the machine's LANG.
internal/patternToken.ts:509 uses resolveLocale (host default) where the documented default is "en-US":

LANG=en_US  parseDateWithPattern("15-Mar-2024", "dd-MMM-yyyy", [])  →  "2024-03-15"
LANG=fr_FR  parseDateWithPattern("15-Mar-2024", "dd-MMM-yyyy", [])  →  ""

undefined correctly gives "en-US" in both. Also parseDateTimeWithPattern. This contradicts the []-as-omitted house rule recorded in resolveRequiredLocale's own JSDoc, and since CI runs one locale nothing pins it. A parser whose answer moves with the host feels like the one worth fixing even if the other two slip.


One root cause, 23 functions

?? treats a null option member as absent, where Temporal treats it as present-but-invalid (ToString(null) → RangeError):

  • overflow: null → "constrain" (12 fns) — addDate("2024-01-31", {months:1}, {overflow:null}) → "2024-02-29", want "". {overflow:"bogus"} correctly gives "", so null is the only invalid value that leaks.
  • fractionalSecondDigits: null → the default (7 fns) — endOfTime("12:34:56","hour",{fractionalSecondDigits:null}) → "12:59:59.999999999".
  • inclusiveStart/inclusiveEnd: null → true (4 fns) — while 0 correctly gives false.

=== undefined instead of ?? fixes all three; the existing catch then yields the sentinel.


Docs and tooling

  • plain/interval/intervalCountDate.ts:60 has a stray * ( * * @example) — the only one in the repo. It glues the previous example's comment into call and writes result: "*" into gmt-corpus.json, so the corpus carries a garbled example for exactly the behaviour this migration introduces. The gate files it under prose and stays green.
  • Two stale clock examples, both "6 months ago" today: formatRelativeZoned.ts:48 (documents "tomorrow") and formatRelativeDateTime.ts:32 (documents "in 3 hours"). Each ships in JSDoc, packages/gmt/README.md and the reference .mdx. Pinning reference: as the sibling examples do fixes both — they're the only two unconditional pinned-date clock examples; everything else in that bucket is correctly hedged.
  • mapZonedHoursInDay.ts:20 — the example's offset is -05:00 where New York is -04:00 at that wall time, so it returns [], not the documented 23 entries.
  • .changeset/tall-moons-tickle.md:55 documents isValidInstant("2024-03-10 12:00:00Z") // true; it returns false, and the function's own JSDoc says false. Changesets ship verbatim to the CHANGELOG and api-surface doesn't cover .changeset/*.md.
  • README multipliers: ~40× should be ~50× (1,002,960 / 20,190 = 49.7) and ~69× ~87× (33,432 / 386 = 86.6), in both READMEs. The raw counts resynced correctly; stats.mjs has no rule for the multipliers, so validate can't catch it.
  • /upstream/'s counts never render in the text surfaces. dist/upstream.md, llms-full.txt and retrieval-chunks.json each carry 5 unresolved expressions ({totalFilings}, {prs}, {coversAll ? …}) plus a leaked import line, so the headline "we have sent {totalFilings} reports" ships literally. stripMdx's line-by-line import regex is pre-existing and unchanged here — but broadening the corpus to the top-level pages is what first exposes it, and 9 built .md files leak import continuations (why-gmt.md worst at 16 lines). Since the page's whole point is live counts, worth fixing alongside it.
  • apps/dox/src/content/docs/upstream.mdx:50 hardcodes "For GMT 1.16.0" while install.md renders v1.15.0 from gmtVersion — two current versions on one built site.
  • app-integration.mdx:35 (new text) says convertUtcToUnix "uses milliseconds unless you pass "seconds""; it takes { epochUnit: "seconds" }. Three sibling pages this PR touched say it correctly.
  • api-surface.mjs check runs in no CI job. It's chained into validate, but no workflow runs validate and the Story-consistency job hand-picks deps/test-markers/build/stats/changeset-status. I measured it at ~5 s with dist present — cheaper than the stats check beside it. Same for upstream.mjs check. As it stands the PR's flagship gate only fires locally, which undersells it.
  • The prose skip bucket is a bare catch with no pinned budget — three of the doc findings above live in skip buckets. Pinning the 167/64 counts would turn a silent degradation into a failing gate.
  • noThrow.ts:318 skips the declared-type assertion for the 54 get/ clock readers; skipping the value is right, the type could still be checked.
  • internal/temporalCompat/largeMonthSpan.ts:186 — meanMonthDays ?? 0 makes wholeMonths return Infinity instead of throwing. Dead today (only hebrew is live in that set), but a trap for the next leap-month calendar.
  • diffUnix / diffUnixAsDuration JSDoc says "Uses Temporal.Instant.until()"; both convert to ZonedDateTime, and Instant.until rejects largestUnit: "days" outright. Neither states the wall-clock calendar-unit rule diffZoned spells out.
  • Seven Intl-backed formatters (formatDate:51, formatDateTime:53, formatTime:48, formatDateRange:50, formatDateTimeRange:53, formatDateToParts:57, formatDateTimeToParts:64, utc/formatUtc:69) accept a non-object options — formatDate("2024-02-03","en-US","dateStyle") returns "2/3/2024". That's ECMA-402-faithful (CoerceOptionsToObject vs GetOptionsObject) and the PR says as much for the Intl family, but none of these seven document their own contract while the relative/calendar family both guards and documents it. Worth one line each.
  • diffUtc / diffDateTime nanosecond answers past ~104 days silently stop being safe integers (diffUtc("2024-01-01T00:00:00Z","2024-07-19T00:00:00.000000001Z","nanoseconds") → 17280000000000000, exact value …001). No JSDoc note, no pointer to the bigint APIs in span//precision/, and no test asserts it.
  • A batch of doc-vs-behaviour mismatches where the code is right and the comment isn't — formatDateToParts/formatDateTimeToParts omit relatedYear/yearName; getLocaleWeekdayNames.ts:11 says ar-SA starts Saturday (it's Sunday — the doc conflates weekendStart with firstDay); intervalDifference{Date,DateTime,Time}'s "(or equals A)" is wrong (returns []); intervalContainsTime/intervalEngulfsTime say no interval can hold 23:59:59.999999999 as its end when the true claim is that none can contain it; intervalUnion*'s gap rule needs an "unless one is empty" caveat; closestDateTo.ts:8 cites PlainDate.compare but uses until().days. Also getLocaleMonthNames.ts:54 / getLocaleEraNames.ts:43,55,65 pass calendar: "gregory" as an option, which ResolveLocale ranks above -u-ca-, so a caller's -u-ca-hebrew is silently overridden rather than rejected — worth saying so.
  • .changeset/piece-limits.md is minor and changes output for previously-valid input, but has no Breaking changes section.
  • apps/dox engines is >=22.12.0 against the root's new >=22.16.0.
  • package-exports-and-engines.md says every namespace subpath re-exports the polyfill; types doesn't (the README has the carve-out).

Questions

  • mapDatesInRange stays inclusive of end while interval* is now half-open, so mapDatesInRange("2024-03-01","2024-03-05").length === 5 but intervalCountDate(…, "day") === 4. Both are documented and the naming separates them, but it's the one place a caller could mix the two conventions in one expression — worth a line in the migration notes?
  • parseHttp now rejects a weekday that disagrees with the date. That's squarely RFC 5322 §3.3 for parseRfc2822, but asctime-date has no RFC 5322 lineage and many HTTP clients are laxer here — is rejecting the intended receiver posture for that form specifically?
  • gregorian is rejected as "not a calendar id", but CLDR lists it as a gregory alias via the same attribute that gives ethioaa its ethiopic-amete-alem, which GMT does accept. Worth either correcting that JSDoc claim or recording the asymmetry as a tagged divergence. (Native V8 rejects all aliases, so GMT is closer to the spec than V8 here.)

Coverage gaps, so none of the above reads as exhaustive: intervalLengthUtc's calendar-unit values were only compared against the same zonedUntil/durationTotal primitives the implementation uses, so a bug inside those is invisible to that check · differenceRecord's carry rule for deeply-skipped units is thinly covered · diff* rounding options and the toString precision options were checked for validity and sentinels, not for the correctness of rounded values · roundUtc's per-unit fractionalSecondDigits default is undocumented and unverified · intervalXorAll*/mergeIntervals* weren't fuzzed past two elements (and unlike intervalDivideEqually*/splitIntervalByUnit* they carry no maxPieces bound — deliberate?) · annotation sweeps beyond [u-ca=…], [foo=bar], [!foo=bar], [!u-ca=…], [Europe/Paris] weren't done per-parser.

@baldurpan

Copy link
Copy Markdown
Contributor

Addendum — one more defect, and a correction to my coverage claim

startOfQuarterForDateTime leaves the sub-second fields intact. plain/calculate/startOfQuarterForDateTime.ts:30:

.with({ month: quarterStartMonth, day: 1, hour: 0, minute: 0, second: 0 })

No millisecond, microsecond or nanosecond, so the input's fraction survives:

startOfQuarterForDateTime("2024-05-15T12:34:56.789")       →  "2024-04-01T00:00:00.789"        want "2024-04-01T00:00:00"
startOfQuarterForDateTime("2024-02-29T23:59:59.999999999") →  "2024-01-01T00:00:00.999999999"  want "2024-01-01T00:00:00"
startOfQuarterForUtc("2024-02-29T23:59:59.999999999Z")     →  "2024-01-01T00:00:00Z"           (correct)

It contradicts its own JSDoc line 8 ("the first day of the quarter with time set to 00:00:00"), and it's the lone outlier among the applicable siblings — startOfQuarterForUtc:30-39, startOfQuarterForZoned, startOfQuarterForUnix and endOfQuarterForDateTime:44-46 all set the three fields and say so. Even startOfDateTime(…, "month") zeroes them correctly. endOfQuarterFor* agree across families, so this is specific to startOfQuarter.

Two scoping points, stated plainly:

  • This is pre-existing, not a regression from this PR. git diff against the base is empty for both startOfQuarterForDateTime.ts and startOfQuarterForUtc.ts. Whether it blocks here or lands as its own fix is your call — I'd lean toward its own fix rather than widening an already-large PR, as long as it doesn't ship silently. The JSDoc line needs to change with the code either way.
  • All three @examples use whole-second inputs, so no documented example exposes it and api-surface can't catch it. That's another instance of the gap I raised about the skip buckets and example coverage: the gate only ever checks the inputs the author chose.

Correction to my own review. I listed compat:oracle's 409,552 comparisons / 0 mismatches among the things I'd verified, which reads as broader assurance than it is. The oracle references no GMT public function at all — it compares calendar algorithms against native Temporal. It therefore can't catch a wrapper-level bug like this one, and (as I noted) has no toLocaleString comparisons either. It's strong evidence for the calendar engine specifically, and shouldn't be read as covering the API surface.

Same caveat applies to my differential runs against 1.15.0: this function is byte-identical to 1.15.0, so "identical to 1.15.0" was never going to surface a bug that has been present in both.

…ions as Temporal does, and fix a month total the polyfill gets wrong (CORE-8, #251)

Six exports threw instead of returning their sentinel when an argument did not stringify, and the garbage set in noThrow.ts had nothing that triggered a ToString, so nothing caught them; it now carries a throwing toString, a null-prototype object and a symbol, and all 556 exports pass. A null option member took the `??` default where Temporal treats it as present-but-invalid: 62 exports over 90 member pairs in 46 files now compare against undefined, five tests that had pinned the defect are corrected, and optionsObject.test.ts pins the rule for every documented member. patternToken treats an empty locale array as omitted, so a pattern parse no longer follows the machine LANG. startOfQuarterForDateTime resets its millisecond, microsecond and nanosecond fields like its siblings.

Closing the review gap on interval lengths found a polyfill defect: Duration#total divides a calendar remainder by the month that ends at the whole-month mark, where TC39 and Chromium 153 divide by the month that starts there, so any length in months from the 29th, 30th or 31st was wrong in the fraction. A 2,016-case scan in both engines found 16 mismatches, all months. Compat defect D11 sends month and year totals past the 28th through the nudgeToCalendarUnit path GMT already owns, and intervalLengthDate, intervalLengthDateTime, formatRelativeDate and formatRelativeDateTime now go through durationTotal instead of calling the polyfill. Every scanned case matches Chromium; the workaround is probe-gated and retires by canary.

New suites close the coverage the review called out: a brute-force oracle for merge and xor over lists of three to eight, reconstruction of difference records over deliberately gappy unit lists, diff rounding values across all nine Temporal modes in both directions, the RFC 9557 annotation grammar per parser against a Chromium table, and interval lengths against a longhand oracle. The round functions document how many fractional digits each unit prints. The docs text surfaces render mistake and scenario cards to Markdown and evaluate the splash pages figures at build time, so llms-full.txt no longer ships 73 raw JSX blocks and 80 unevaluated expressions, and fenced code keeps its import lines.
@craig-o-curtis

Copy link
Copy Markdown
Contributor Author

Thanks — this is the review that earned its keep. Everything is fixed on the branch; nothing is deferred and there is no follow-up story. One finding is refuted with primary sources, and one of your coverage gaps turned out to be hiding a real bug.

Blocking 1 — refuted: the spec does require a TypeError

formatDate("2024-02-03", "en-US", { hour: "numeric" }) returning "" is correct, and native V8 agrees.

The reasoning traces CreateDateTimeFormat, but toLocaleString on a PlainDate doesn't reach the constructor path. The Temporal amendment adds a third argument to GetDateTimeFormat, and the chain is:

  1. §15.4.1 CreateDateTimeFormat — for every Intl.DateTimeFormat:

    Set dateTimeFormat.[[TemporalPlainDateFormat]] to GetDateTimeFormat(formats, formatMatcher, formatOptions, **date, date, relevant**).

  2. §15.6.1 GetDateTimeFormat — returns "either a DateTime Format Record or null":

    If required is date, then let requiredOptions be « "weekday", "year", "month", "day" ».
    … Let anyPresent be false. For each property name prop of « weekday, year, month, day, dayPeriod, hour, minute, second, fractionalSecondDigits », if options.[[<prop>]] is not undefined, set anyPresent to true.
    … If needDefaults is true, then if anyPresent is true and inherit is relevant, return null.

    { hour: "numeric" } on a PlainDate: no date field is present, so needDefaults stays true; anyPresent is true because of hour; inherit is relevant → null.

  3. §15.6.15 HandleDateTimeTemporalDate:

    Let format be dateTimeFormat.[[TemporalPlainDateFormat]]. If format is null, throw a TypeError exception.

Base ECMA-402 §11.1.2 has no null path — it only fills in defaults — which is why the constructor-shaped reading comes out the other way. The null return is added by the Temporal amendment's GetDateTimeFormat with inherit = relevant, and that is exactly the entry point PlainDate#toLocaleString uses.

Checked rather than argued: Chromium 153.0.8010.12 throws TypeError, and @js-temporal/polyfill 0.5.1 throws TypeError. The "2/3/2024" in the report is what you get from the Intl.DateTimeFormat constructor, which takes the ~any~ path — a different operation. GMT's "" is the right sentinel for a call that throws, so no change, and no TAGGED_MISMATCHES row: there is no divergence to tag.

You were right about the mirror case for a different reason, and we kept your warning: dateStyle + timeStyle on a PlainDate throws under base §11.1.2 ("If required is date and timeStyle is not undefined, throw a TypeError exception"). Chromium throws there too, so again there is nothing to tag. The JSDoc on the helper now states both paths so the next reader doesn't have to re-derive them.

Blocking 2 and 3 — confirmed, and both were bigger than filed

Throwing exports: 6, not 3. Adding your three values to the garbage set found isLeapSecond, getNowUnit, getUtcNowUnit — and also getZonedNowUnit, getTimeZoneOffset and spanWallClock. All six now return their sentinel on a non-string argument. test/noThrow.ts carries { toString() { throw } }, Object.create(null) and Symbol() permanently, so the blind spot is closed rather than patched: 556/556 exports pass.

locale: [] following LANG: confirmed and fixed. patternToken.ts treats [] as omitted, so [], undefined and "en-US" now give the same answer. Verified under LANG=en_US, fr_FR and ja_JP — 21/21 each, where the red run was 3/3 wrong under ja_JP.

"One root cause, 23 functions" — it was 62 exports and 90 pairs

The ??-versus-=== undefined defect was larger than the three families listed. A probe over the built dist for every documented option member of every export found 62 exports and 90 (null, bogus) pairs that disagreed, across 46 source files. All fixed, plus five existing tests that had pinned the wrong behaviour (resolveOverflow, fromExcelSerial, toExcelSerial, resolveLocal, getQuarter).

test/optionsObject.test.ts now pins the rule generically: for every case and every documented member, { member: null } must equal { member: "zzbogus" } wherever the bogus value yields the sentinel. A new member that reaches for ?? fails the suite.

Docs and tooling — all confirmed and fixed

Every item on the list is fixed. Three were larger than filed:

  • Stale clock examples: three, not two. formatRelativeZoned.ts and formatRelativeDateTime.ts as you found, plus a third in the same bucket. All pinned with reference:.
  • stripMdx: two defects, and then the whole class. The multi-line import regex is fixed and the /upstream/ counts moved into UpstreamTracker.astro. Your wider note was right too, and since we don't defer: the text surfaces now render <Mistake> and <Scenario> to Markdown (73 cards that shipped as raw JSX), unwrap the layout components, and evaluate the splash pages' {gmtStats…} expressions at build time from the same modules. llms-full.txt went from 80 unevaluated expressions to 4 — and those four are prose ({yyyy}, {MM}, {dd}, {RelativeUnit}). Three new tests fail the build if any of it comes back, and a fourth fails if a page grows an expression the values table doesn't know.
  • diffUtc.ts too. The Instant.until() claim was in diffUnix, diffUnixAsDuration and diffUtc. All three corrected; Instant.until with largestUnit: "days" throws, so the claim was wrong everywhere it appeared.

The rest as filed: the stray * in intervalCountDate.ts, the mapZonedHoursInDay offset, the isValidInstant claim in tall-moons-tickle.md, the README multipliers (with three new stats.mjs rules so validate catches them next time), the hardcoded version on upstream.mdx, app-integration.mdx's { epochUnit: "seconds" }, api-surface.mjs check and upstream.mjs check in the consistency job, the pinned skip budget ({ examples: 164, docResults: 64 }, asserted by exact equality), noThrow.ts's type assertion for clock readers, largeMonthSpan.ts throwing instead of returning Infinity, the nanosecond note on the four diff* functions with tests, the CoerceOptionsToObject line on the seven Intl formatters, the doc-vs-behaviour batch, the piece-limits.md breaking-changes section, apps/dox engines, and the types carve-out.

The addendum — fixed here, not as its own change

startOfQuarterForDateTime is confirmed: it set hour, minute and second but not the three sub-second fields, so "2024-05-15T12:34:56.789" came back as "2024-04-01T00:00:00.789".

You offered to let it land separately. We don't ship a known defect, so it's fixed here, test-first: four rows covering milliseconds, microseconds, nanoseconds and a bare .5, red before the change, green after, and the JSDoc now says it resets every time field like its siblings do. We also swept the repo for the class — startOfQuarterForDateTime was the only member; endOfQuarterFor* overwrite all three fields on the way out, and the DST probes build their times from whole fields.

Your point that no @example exposed it is the sharper one, and it's why the new example uses "2024-05-15T12:34:56.789": api-surface executes it, so the gate now covers the input that was missing.

Coverage gaps — all closed, and one was hiding a real bug

We took the gap list as a work list rather than a caveat.

1. intervalLengthUtc measured only against its own primitives — and there was a bug under it.

Writing an oracle from add and instant subtraction alone (TC39 TotalRelativeDuration longhand) disagreed with GMT on one case. Chromium 153 was the tie-breaker, and native V8 sided with the oracle:

intervalLengthDateTime("2024-01-31T00:00:00", "2024-02-29T12:00:00", "month");
// GMT was 1.0172413793103448   (12h over February's 29 days)
// Chromium 153: 1.0161290322580645   (12h over the following month's 31 days)

Duration.prototype.total measures a calendar remainder against the unit the end falls in — relativeTo + r1 units to relativeTo + (r1 + 1) units. @js-temporal/polyfill 0.5.1 measures it against the month that ends there. The two coincide unless adding a month constrains the day, so only a relativeTo on the 29th, 30th or 31st reaches it.

Scanned every day-of-month across all twelve months of 2024, ending at several offsets past 0, 1, 2 and 13 units, for year and month — 2,016 rows in both engines. 16 mismatches, every one unit: "month" (start day 31 → 12, 30 → 2, 29 → 2). until agreed on all 2,016 rows: only the fraction was wrong.

Fixed as compat defect D11: durationTotal routes month and year totals with a relativeTo past the 28th through the nudgeToCalendarUnit spec path GMT already owns for the non-ISO calendars. Four call sites that were calling Duration#total directly — intervalLengthDate, intervalLengthDateTime, formatRelativeDate, formatRelativeDateTime — now go through the wrapper. All 2,016 rows match Chromium exactly. It is probe-gated and retires by canary, pnpm compat lists it, and it's written up as section H of the polyfill bug doc for filing upstream.

2. differenceRecord's carry for deeply-skipped units — now checked by reconstruction instead of a table: adding a record back to its start must land on or before the end, and one more of the smallest listed unit must pass it. That pins every unit's amount, including skipped ones. 312 cases over deliberately gappy lists (["years","seconds"], ["weeks","minutes"], ["days","nanoseconds"]) across diffDateTime, diffUtc and diffZoned through DST. Where a nanosecond count exceeds 2^53 the test pins the nearest double rather than skipping, so the documented limit can't hide an arithmetic bug.

3. diff* rounding values, not just option validity — 1,368 cases: all nine Temporal rounding modes, valid increments per unit, four units, spans landing below, on and above the half-way point, each measured forwards and backwards so every mode is exercised in both directions. Expected values come from bigint arithmetic on the epoch-nanosecond count, with RoundNumberToIncrement written out longhand.

4. roundUtc's per-unit fractionalSecondDigits default — was undocumented on all four round* functions. Now documented on each, with executed examples, and roundUtc's override documented as overriding for every unit.

5. intervalXorAll* / mergeIntervals* past two elements — 720 cases against a brute-force oracle: intervals become grid indices, the oracle counts cells covered at all (merge) and covered an odd number of times (xor), and the result is compared as a set, plus the sorted/disjoint/non-touching invariants. Lists of 3, 4, 5, 6 and 8, across all six families. No mismatches.

On your parenthesis — yes, deliberate. maxPieces bounds enumeration: a split or a mapped range builds one element per unit in the span, so the output grows with the range rather than the input. mergeIntervals* and intervalXorAll* are a boundary sweep: sort the endpoints, walk them once, emit at most as many spans as came in. There is no amount a caller can pass that makes them build more than their input, so there is nothing for maxPieces to bound.

6. Annotation sweeps per parser — a 22-shape grammar table applied to six validators, 132 assertions. Shapes include a repeated elective key, a repeated u-ca with either one critical, an uppercase key, a leading underscore, a hyphenated value, an empty annotation, a missing key and a missing value. Expected values are Temporal.PlainDate.from / Instant.from / ZonedDateTime.from in Chromium 153, not the polyfill and not GMT. GMT matched on every row, including the two places the types legitimately differ: an instant never reads the calendar, so it ignores a u-ca it can't resolve; and a zoned string already carries a zone annotation, so a bare [foo] after it is a second one and is rejected.

Your three questions

mapDatesInRange staying end-inclusive. Worth a line, and it's in: half-open-positional-intervals.md now contrasts the two directly (mapDatesInRange("2024-03-01","2024-03-05") gives 5 dates, intervalCountDate(…, "day") gives 4), and mapDatesInRange's README entry carries the same note. The behaviour stands — it maps over dates, it isn't an interval operation — but a caller mixing them in one expression now has to walk past the warning.

parseHttp rejecting a mismatched weekday in asctime. Intended, and it's the receiver posture RFC 9110 §5.6.7 asks for: a recipient that accepts the obsolete formats MUST do so only for compatibility, and the weekday is redundant data that can only disagree by being wrong. RFC 9110 gives all three formats the same day-name production, so accepting a contradiction in one form and not the others would be GMT's invention rather than the grammar's. The JSDoc and internet-date-grammars.md now state it for all three forms, with the rejection shown.

gregorian rejected while ethiopic-amete-alem is accepted. Recorded as section G of the polyfill bug doc, with the full table: Chromium 153 (ICU4X), the polyfill and GMT agree row for row, and Intl.DateTimeFormat rejects gregorian too. It isn't GMT's asymmetry — it's CLDR's alias table not being wired into Temporal's calendar-id resolution — so the JSDoc claim is corrected rather than the behaviour, and it's written up as "no ask, don't fix this later". Your note that V8 rejects all four aliases is in there: GMT is closer to the spec than V8 on ethiopic-amete-alem, and identical everywhere else.

Two decisions from the owner, for the record

  • No 2.0.0. Every breaking fix ships in 1.16.0 as a minor changeset with a Breaking changes migration section, and there are no deprecation shims or compatibility converters. GMT's users are internal, so the migration is a code change, not a support window.
  • gregorian stays rejected and ethiopic-amete-alem stays accepted, per section G — matching both engines rather than CLDR's alias list.

Where it stands

pnpm validate is green end to end: 36,268 packages/gmt tests, 2,606 dox, 97 lint-plugin, api-surface 0 failing across 2,797 executed examples and 554 documented results with the skip budget pinned, stats check matching, upstream check at 11 filings, lint and typecheck clean. pnpm compat reports D11 as newly needed alongside the existing groups.

Thanks again — the two blocking findings we accepted were both wider than they looked, and the gap list found a bug that eleven review units and a 409,552-comparison oracle had all walked past.

…s too, not just totals (CORE-8, #251)

The polyfill bounds a duration between relativeTo plus r1 units and relativeTo plus r2 units and never rechecks that the target falls inside that window, where TC39 recomputes it one unit further along. The previous commit routed only Duration#total around it. The same gap reaches Duration#round and until or since with a calendar smallestUnit, and there it loses a whole unit rather than a fraction: normalizeDuration of P30DT12H floored to months from 2024-01-30 gave PT0S instead of P1M, and diffDateTime from 2024-01-31 to 2024-02-29T12:00 gave 0 months instead of 1. Five seams now take the spec path GMT already owns — monthTotalBySpec, monthRoundBySpec, the zonedUntil gate that covers diffUtc, diffUnix and diffZoned, the new plainUntilWithRounding for diffDateTime, and a D11 term in plainDateUntil for diffDate. All 12,960 round rows, 9,940 until rows and 2,016 total rows now match Chromium 153, and Duration.compare needs no gate because it reaches no nudge window.

The round scan also found a defect in GMT own mirror of the algorithm: a target landing exactly on the window lower bound is already rounded, which TC39 states inside ApplyUnsignedRoundingMode, but GMT compares rather than reading the value, so ceil expanded an exact boundary — P60D from 2024-01-31 gave P3M instead of P2M. That was live for the non-ISO calendars before this branch. Fixed by the missing zero-progress branch, pinned in ISO and hebrew, and kept when D11 retires.

Corrects the upstream record: this is tc39 proposal-temporal issue 3168, fixed there by PR 3172 (commit 5dd0b0d97ee1, merged 2025-11-19) and not ported by js-temporal on 0.5.1 or main. The ask is a port and release request against js-temporal only, not a new patch and not a tc39 filing. The bug doc section H, the compat README, the canary group, the defect-4 header and the changeset now say so, and three more canary probes cover round and until.
@craig-o-curtis

craig-o-curtis commented Sep 20, 2026 •

Copy link
Copy Markdown
Contributor Author

Correction (2026-09-23). The D11 filing section below is superseded. The port already existed as 50d66d2 in ptomato's open PR js-temporal/temporal-polyfill#361, so nothing of ours was filed and no new port-and-release request is needed. The test count here is also stale: 36,348, not 36,349. Full correction: #253 (comment)

Correction on D11 — I claimed too much and reported too little

Two corrections to my previous comment, both found while writing the upstream filing it promised.

1. It is not a new defect. It is a known one, already fixed upstream

D11 is tc39/proposal-temporal #3168,
fixed by #3172 (Normative, 5dd0b0d97ee1,
merged 2025-11-19). ptomato's diagnosis there is word for word ours: "destEpochNs > endEpochNs …
r1 and r2 should be 1 and 2 instead of 0 and 1."

I called it a newly discovered polyfill bug and said it needed filings in both repositories. Neither
is right. A tc39 filing would be a duplicate of a closed issue, and what @js-temporal/polyfill needs
is a port-and-release request for 5dd0b0d97ee1, not a new patch. One filing, not two.

My root-cause wording was also the symptom rather than the mechanism. It is not that the fraction is
"divided by the wrong month": TC39 bounds the duration between relativeTo + r1 units and
relativeTo + r2 units, checks that the target falls inside that window, and recomputes it one unit
further along when it does not (ComputeNudgeWindow with additionalShift). The polyfill computes
the window once and never retries — its own assert(start ≤ dest ≤ end) is compiled out of
production builds, so progress silently exceeds 1 instead of failing.

The polyfill has not ported it on 0.5.1 or on main, and no js-temporal issue mentions it, so it
cannot ride their existing release request #373.

2. I fixed a third of it

This is the part that matters, and you had already pointed at the reason I missed it: the oracle
"references no GMT public function at all". I gated Duration#total and stopped there. The same
missing retry reaches two more operations, and both were still wrong on the branch I asked you to
re-read:

normalizeDuration("P30DT12H", { smallestUnit: "month", roundingMode: "floor", relativeTo: "2024-01-30" })
// was "PT0S"        Chromium 153 and the spec: "P1M"

diffDateTime("2024-01-31T00:00:00", "2024-02-29T12:00:00", ["months"], { smallestUnit: "months", roundingMode: "floor" })
// was { months: 0 }  Chromium 153 and the spec: { months: 1 }

A whole month, not a fraction. Scans against Chromium 153:

Operation Rows Mismatches before After
Duration#total 2,016 16 0
Duration#round 12,960 15 0
until / since with a calendar smallestUnit 9,940 15 0
Duration.compare 256 0 0 — reaches no nudge window, so no gate

Every mismatch is unit: "month", only in the directed rounding modes, only from a start past the
28th. until without rounding agrees everywhere, which is why the difference tables never saw it.

Now fixed at five seams: monthTotalBySpec and monthRoundBySpec in durationTotal/durationRound,
a defect-4 gate in zonedUntil (covering diffUtc, diffUnix, diffZoned), a new
plainUntilWithRounding for diffDateTime, and a D11 term in plainDateUntil for diffDate. Three
more canary probes, so pnpm compat now reports 6 of 6 failing on 0.5.1.

And a third defect, this one ours

The round scan turned up a bug in GMT's own mirror of the algorithm, not the polyfill's. When the
target lands exactly on the window's lower bound the duration is already rounded — TC39 says so inside
ApplyUnsignedRoundingMode ("if x is equal to r1, return r1"). GMT's form takes a comparison rather
than the value, so it could not see that case and ceil expanded an exact boundary:

normalizeDuration("P60D", { smallestUnit: "month", roundingMode: "ceil", relativeTo: "2024-01-31" })
// was "P3M"        Chromium 153: "P2M"

It was live for the non-ISO calendars before any of this, since they already took that path —
so it predates the D11 work and predates this PR. Fixed by the missing progress === 0n branch, with
rows pinned in ISO and in hebrew, and it stays when D11 eventually retires.

Where that leaves the numbers

pnpm validate green: 36,349 packages/gmt tests (up from 36,268), api-surface 0 failing, the rest
unchanged. The changeset is rewritten as nudge-window-retry.md and now names all three operations
and the boundary fix.

You were right that the coverage gaps were the interesting part of your review. Closing one found a
defect; writing up that defect honestly found two more.

…R that already carries the port (CORE-8, #251)

The port we were about to file already existed: commit 50d66d2 in ptomato open js-temporal PR 361, opened in April and unreviewed since. Our earlier reasoning checked only our own filings 367 to 372 for NudgeToCalendarUnit and never looked at the maintainer own open PRs. What 361 was missing was evidence that the normative commit inside it fixes a user-visible wrong answer today, so that is what we added as a comment, with the reproducer, the Chromium 153 scan and before and after measurements of the commit applied to main. Nothing of ours was filed and no tc39 filing is needed, since tc39 3168 is already fixed there by 3172.

The upstream tracker gains that as a twelfth entry, marked as someone else pull request: a role field distinguishes a filing we opened from one we only contributed to, and contributionUrl links our comment, because sync rewrites url from the repo, kind and number every run. The page headline now reads 11 reports and fixes sent, 6 of them as ready-to-merge code changes, and the evidence added to one more the maintainers had already written, so a maintainer PR is never counted among fixes we sent. Its live state is worth tracking because it is what retires D11.

Two bugs found while doing it. upstream:sync had never worked past the first repository: REPOS.flatMap(discover) passes the array index as the discover timeout, so the second repo got a one millisecond cap and the command always died there. And buildFiling enumerates its output fields, so a hand-written field it does not name is dropped on the next sync, which for the new role field would silently turn a maintainer PR into one of ours; both are fixed, with the row pinned in a test that reads the committed file rather than the live refresh.

D11 itself is unchanged: 50d66d2 is not on main and in no release, so every workaround stays and the removal trigger is now two gates, the commit landing on main and then shipping in a release. Section H of the polyfill bug doc records the measured main versus 50d66d2 table, the suite results, and three traps: Node bundled Temporal is not a valid oracle here, nudgedEpochNs is intentionally left alone upstream, and the 3168 reproducer does not expose this.
…t (CORE-8, #251)

The suite ran on Vitest's 5s default, which was never chosen for it. Several tests
here are import-bound rather than slow: they await an import of a GMT namespace,
which pulls in the polyfill and costs about 3.9s in a fresh worker on an idle
machine. Under Scope: 5 of 6 workspace projects
packages/gmt-oxlint test$ vitest run --config vitest.config.ts
packages/gmt-oxlint test: �[1m�[30m�[46m RUN �[49m�[39m�[22m �[36mv4.1.4 �[39m�[90m/Users/craigcurtis/workbench/northguild/gmt.worktrees/chore/251-core-8-final-review-with-gmt-reviewer-of-entire-api-pre-v1160/packages/gmt-oxlint�[39m
packages/gmt-oxlint test:  �[32m✓�[39m src/utils/isBannedDateLibrary.test.ts �[2m(�[22m�[2m13 tests�[22m�[2m)�[22m�[32m 2�[2mms�[22m�[39m
packages/gmt-oxlint test:  �[32m✓�[39m src/rules/no-date-getTimezoneOffset.test.ts �[2m(�[22m�[2m5 tests�[22m�[2m)�[22m�[32m 3�[2mms�[22m�[39m
packages/gmt-oxlint test:  �[32m✓�[39m src/rules/no-date-library-imports.test.ts �[2m(�[22m�[2m19 tests�[22m�[2m)�[22m�[32m 4�[2mms�[22m�[39m
packages/gmt-oxlint test:  �[32m✓�[39m src/utils/hasDateStaticMemberCall.test.ts �[2m(�[22m�[2m5 tests�[22m�[2m)�[22m�[32m 2�[2mms�[22m�[39m
packages/gmt-oxlint test:  �[32m✓�[39m src/rules/no-date-parse.test.ts �[2m(�[22m�[2m8 tests�[22m�[2m)�[22m�[32m 4�[2mms�[22m�[39m
packages/gmt-oxlint test:  �[32m✓�[39m src/rules/no-date-global.test.ts �[2m(�[22m�[2m6 tests�[22m�[2m)�[22m�[32m 6�[2mms�[22m�[39m
packages/gmt-oxlint test:  �[32m✓�[39m src/rules/no-date-now.test.ts �[2m(�[22m�[2m7 tests�[22m�[2m)�[22m�[32m 7�[2mms�[22m�[39m
packages/gmt-oxlint test:  �[32m✓�[39m src/rules/no-date-utc.test.ts �[2m(�[22m�[2m8 tests�[22m�[2m)�[22m�[32m 4�[2mms�[22m�[39m
packages/gmt-oxlint test:  �[32m✓�[39m src/rules/no-new-date.test.ts �[2m(�[22m�[2m6 tests�[22m�[2m)�[22m�[32m 4�[2mms�[22m�[39m
packages/gmt-oxlint test:  �[32m✓�[39m src/recommended-config.test.ts �[2m(�[22m�[2m1 test�[22m�[2m)�[22m�[32m 2�[2mms�[22m�[39m
packages/gmt-oxlint test:  �[32m✓�[39m src/index.test.ts �[2m(�[22m�[2m6 tests�[22m�[2m)�[22m�[32m 3�[2mms�[22m�[39m
packages/gmt-oxlint test:  �[32m✓�[39m src/utils/isIdentifier.test.ts �[2m(�[22m�[2m4 tests�[22m�[2m)�[22m�[32m 2�[2mms�[22m�[39m
packages/gmt-oxlint test:  �[32m✓�[39m src/utils/isMemberExpression.test.ts �[2m(�[22m�[2m3 tests�[22m�[2m)�[22m�[32m 2�[2mms�[22m�[39m
packages/gmt-oxlint test:  �[32m✓�[39m src/utils/isChainExpression.test.ts �[2m(�[22m�[2m3 tests�[22m�[2m)�[22m�[32m 2�[2mms�[22m�[39m
packages/gmt-oxlint test:  �[32m✓�[39m src/utils/getMemberExpressionFromCall.test.ts �[2m(�[22m�[2m3 tests�[22m�[2m)�[22m�[32m 2�[2mms�[22m�[39m
packages/gmt-oxlint test: �[2m Test Files �[22m �[1m�[32m15 passed�[39m�[22m�[90m (15)�[39m
packages/gmt-oxlint test: �[2m      Tests �[22m �[1m�[32m97 passed�[39m�[22m�[90m (97)�[39m
packages/gmt-oxlint test: �[2m   Start at �[22m 08:08:41
packages/gmt-oxlint test: �[2m   Duration �[22m 571ms�[2m (transform 447ms, setup 0ms, import 1.08s, tests 48ms, environment 1ms)�[22m
packages/gmt-oxlint test: Done
packages/gmt test$ vitest run
packages/gmt test: �[1m�[30m�[46m RUN �[49m�[39m�[22m �[36mv4.1.4 �[39m�[90m/Users/craigcurtis/workbench/northguild/gmt.worktrees/chore/251-core-8-final-review-with-gmt-reviewer-of-entire-api-pre-v1160/packages/gmt�[39m
packages/gmt test:  �[32m✓�[39m src/calendar/calculate/floorToZone.test.ts �[2m(�[22m�[2m194 tests�[22m�[2m)�[22m�[33m 805�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/plain/calculate/diffDateAsDuration.grid.lunar.test.ts �[2m(�[22m�[2m774 tests�[22m�[2m)�[22m�[33m 977�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/zoned/interval/intervalAlgebraZoned.test.ts �[2m(�[22m�[2m8 tests�[22m�[2m)�[22m�[33m 802�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/zoned/interval/intervalCountZoned.test.ts �[2m(�[22m�[2m111 tests�[22m�[2m)�[22m�[33m 1242�[2mms�[22m�[39m
packages/gmt test:      �[33m�[2m✓�[22m�[39m returns null for '2024-01-01T00:00:00+00:00[UTC]' to '2024-01-08T00:00:00+00:00[UTC]' by 'hour' with a transition every minute �[33m 694�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/plain/calculate/diffDateAsDuration.grid.solar.test.ts �[2m(�[22m�[2m1801 tests�[22m�[2m)�[22m�[33m 1268�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/zoned/interval/intervalOverlappingDaysZoned.test.ts �[2m(�[22m�[2m43 tests�[22m�[2m)�[22m�[33m 1483�[2mms�[22m�[39m
packages/gmt test:      �[33m�[2m✓�[22m�[39m returns null when the intersection crosses more than 10,000 zone transitions (America/New_York, 1970 to 7000) �[33m 1439�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/internal/zonedBucket.test.ts �[2m(�[22m�[2m68 tests�[22m�[2m)�[22m�[33m 1672�[2mms�[22m�[39m
packages/gmt test:      �[33m�[2m✓�[22m�[39m always advances, and lands on a real bucket start, in 'GMT' �[33m 309�[2mms�[22m�[39m
packages/gmt test:      �[33m�[2m✓�[22m�[39m always advances, and lands on a real bucket start, in 'Etc/GMT' �[33m 417�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/plain/calculate/diffDateAsDuration.edges.test.ts �[2m(�[22m�[2m328 tests�[22m�[2m)�[22m�[33m 433�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/plain/calculate/diffDateAsDuration.umalqura.test.ts �[2m(�[22m�[2m259 tests�[22m�[2m)�[22m�[33m 310�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/plain/calculate/diffDateAsDuration.test.ts �[2m(�[22m�[2m376 tests�[22m�[2m)�[22m�[33m 316�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/unix/interval/intervalCountUnix.test.ts �[2m(�[22m�[2m75 tests�[22m�[2m)�[22m�[32m 210�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/plain/parse/parseDateTimeWithPattern.test.ts �[2m(�[22m�[2m49 tests�[22m�[2m)�[22m�[32m 260�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/utc/interval/intervalAlgebraUtc.test.ts �[2m(�[22m�[2m8 tests�[22m�[2m)�[22m�[32m 251�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/zoned/calculate/startOfZoned.test.ts �[2m(�[22m�[2m111 tests�[22m�[2m)�[22m�[33m 2434�[2mms�[22m�[39m
packages/gmt test:      �[33m�[2m✓�[22m�[39m agree with floorToZone and contain the input for hour/day/week/month in 'GMT' �[33m 617�[2mms�[22m�[39m
packages/gmt test:      �[33m�[2m✓�[22m�[39m agree with floorToZone and contain the input for hour/day/week/month in 'Etc/GMT' �[33m 508�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/plain/parse/parseDateWithPattern.test.ts �[2m(�[22m�[2m99 tests�[22m�[2m)�[22m�[32m 152�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/unix/interval/intervalAlgebraUnix.test.ts �[2m(�[22m�[2m8 tests�[22m�[2m)�[22m�[32m 207�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/plain/locale/getLocaleMonthNames.test.ts �[2m(�[22m�[2m24 tests�[22m�[2m)�[22m�[32m 133�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/zoned/compare/areZonedEqualBy.test.ts �[2m(�[22m�[2m90 tests�[22m�[2m)�[22m�[32m 144�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/plain/calculate/subtractDate.edges.test.ts �[2m(�[22m�[2m336 tests�[22m�[2m)�[22m�[32m 118�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/internal/zonedWallClock.test.ts �[2m(�[22m�[2m67 tests�[22m�[2m)�[22m�[32m 110�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/plain/locale/getLocaleWeekdayNames.test.ts �[2m(�[22m�[2m24 tests�[22m�[2m)�[22m�[32m 88�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/zoned/calculate/getLocaleZonedStartOfWeek.test.ts �[2m(�[22m�[2m83 tests�[22m�[2m)�[22m�[32m 101�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/zoned/calculate/roundZoned.test.ts �[2m(�[22m�[2m812 tests�[22m�[2m)�[22m�[32m 84�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/plain/calculate/addDate.edges.test.ts �[2m(�[22m�[2m414 tests�[22m�[2m)�[22m�[32m 82�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/plain/calculate/addDate.test.ts �[2m(�[22m�[2m251 tests�[22m�[2m)�[22m�[32m 72�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/zoned/get/getDstTransitions.test.ts �[2m(�[22m�[2m53 tests�[22m�[2m)�[22m�[32m 63�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/internal/zonedWallClockOperations.test.ts �[2m(�[22m�[2m128 tests�[22m�[2m)�[22m�[32m 71�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/unix/unixContract.test.ts �[2m(�[22m�[2m1048 tests�[22m�[2m)�[22m�[32m 121�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/zoned/calculate/subtractZoned.test.ts �[2m(�[22m�[2m191 tests�[22m�[2m)�[22m�[32m 62�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/zoned/interval/intervalDifferenceZoned.test.ts �[2m(�[22m�[2m34 tests�[22m�[2m)�[22m�[32m 66�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/internal/businessCalendar.test.ts �[2m(�[22m�[2m90 tests�[22m�[2m)�[22m�[32m 65�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/internal/calendarZonedString.test.ts �[2m(�[22m�[2m329 tests�[22m�[2m)�[22m�[32m 81�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/internal/temporalCompat/nonIsoArithmetic.test.ts �[2m(�[22m�[2m36 tests�[22m�[2m)�[22m�[32m 54�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/zoned/calculate/diffZonedAsDuration.test.ts �[2m(�[22m�[2m91 tests�[22m�[2m)�[22m�[32m 52�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/zoned/interval/splitIntervalByUnitZoned.test.ts �[2m(�[22m�[2m69 tests�[22m�[2m)�[22m�[32m 59�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/calendar/calculate/bucketRange.test.ts �[2m(�[22m�[2m129 tests�[22m�[2m)�[22m�[33m 6501�[2mms�[22m�[39m
packages/gmt test:      �[33m�[2m✓�[22m�[39m returns only genuine bucket starts, strictly increasing, in 'GMT' �[33m 820�[2mms�[22m�[39m
packages/gmt test:      �[33m�[2m✓�[22m�[39m returns only genuine bucket starts, strictly increasing, in 'Etc/GMT' �[33m 680�[2mms�[22m�[39m
packages/gmt test:      �[33m�[2m✓�[22m�[39m returns only genuine bucket starts, strictly increasing, in 'Asia/Kolkata' �[33m 330�[2mms�[22m�[39m
packages/gmt test:      �[33m�[2m✓�[22m�[39m returns 10000 buckets when a range needs 'exactly the maximum number of buckets' �[33m 350�[2mms�[22m�[39m
packages/gmt test:      �[33m�[2m✓�[22m�[39m returns 9999 buckets when a range needs 'one under it' �[33m 328�[2mms�[22m�[39m
packages/gmt test:      �[33m�[2m✓�[22m�[39m returns an empty array when a range needs 'one bucket over the maximum' �[33m 331�[2mms�[22m�[39m
packages/gmt test:      �[33m�[2m✓�[22m�[39m returns [] for 'day' buckets from '1900-01-01T00:00:00Z' to '1960-01-01T00:00:00Z' in 'Etc/GMT+12' within a second ('60 years of days') �[33m 315�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/zoned/calculate/addZoned.test.ts �[2m(�[22m�[2m216 tests�[22m�[2m)�[22m�[32m 69�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/test/differenceCarry.test.ts �[2m(�[22m�[2m312 tests�[22m�[2m)�[22m�[32m 106�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/test/diffRoundingValues.test.ts �[2m(�[22m�[2m1368 tests�[22m�[2m)�[22m�[32m 144�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/test/intervalSetAlgebra.test.ts �[2m(�[22m�[2m720 tests�[22m�[2m)�[22m�[32m 226�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/zoned/calculate/endOfZoned.test.ts �[2m(�[22m�[2m95 tests�[22m�[2m)�[22m�[32m 56�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/plain/format/formatDateTime.test.ts �[2m(�[22m�[2m190 tests�[22m�[2m)�[22m�[32m 38�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/zoned/format/formatZonedRange.test.ts �[2m(�[22m�[2m71 tests�[22m�[2m)�[22m�[32m 71�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/zoned/interval/intervalXorAllZoned.test.ts �[2m(�[22m�[2m26 tests�[22m�[2m)�[22m�[32m 56�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/zoned/interval/intervalXorZoned.test.ts �[2m(�[22m�[2m32 tests�[22m�[2m)�[22m�[32m 56�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/zoned/map/mapZonedHoursInDay.test.ts �[2m(�[22m�[2m64 tests�[22m�[2m)�[22m�[32m 50�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/span/calculate/spanWallClock.test.ts �[2m(�[22m�[2m236 tests�[22m�[2m)�[22m�[32m 42�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/test/intervalLengthOracle.test.ts �[2m(�[22m�[2m108 tests�[22m�[2m)�[22m�[33m 987�[2mms�[22m�[39m
packages/gmt test:      �[33m�[2m✓�[22m�[39m day from 1970-01-01T00:00:00 to 2024-07-04T09:15:00 (Zoned, America/New_York) �[33m 383�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/duration/calculate/durationAs.test.ts �[2m(�[22m�[2m135 tests�[22m�[2m)�[22m�[32m 41�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/zoned/interval/intervalLengthZoned.test.ts �[2m(�[22m�[2m48 tests�[22m�[2m)�[22m�[32m 46�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/plain/format/formatDateTimeRange.test.ts �[2m(�[22m�[2m76 tests�[22m�[2m)�[22m�[32m 44�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/plain/format/formatRelativeDateTime.test.ts �[2m(�[22m�[2m265 tests�[22m�[2m)�[22m�[32m 36�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/zoned/interval/intervalFromDurationZoned.test.ts �[2m(�[22m�[2m125 tests�[22m�[2m)�[22m�[32m 48�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/unix/calculate/startOfUnix.test.ts �[2m(�[22m�[2m61 tests�[22m�[2m)�[22m�[32m 46�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/plain/format/formatTime.test.ts �[2m(�[22m�[2m192 tests�[22m�[2m)�[22m�[32m 40�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/unix/compare/areUnixEqualBy.test.ts �[2m(�[22m�[2m47 tests�[22m�[2m)�[22m�[32m 41�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/zoned/format/formatZonedDateTime.test.ts �[2m(�[22m�[2m223 tests�[22m�[2m)�[22m�[32m 42�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/plain/calculate/subtractDate.test.ts �[2m(�[22m�[2m57 tests�[22m�[2m)�[22m�[32m 38�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/plain/format/formatRelativeTime.test.ts �[2m(�[22m�[2m250 tests�[22m�[2m)�[22m�[32m 41�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/plain/format/formatRelativeDate.test.ts �[2m(�[22m�[2m274 tests�[22m�[2m)�[22m�[32m 37�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/instant/convert/resolveLocal.test.ts �[2m(�[22m�[2m138 tests�[22m�[2m)�[22m�[32m 41�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/test/nudgeWindowRetry.test.ts �[2m(�[22m�[2m81 tests�[22m�[2m)�[22m�[32m 46�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/zoned/calculate/diffZoned.test.ts �[2m(�[22m�[2m91 tests�[22m�[2m)�[22m�[32m 38�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/zoned/interval/intervalEngulfsZoned.test.ts �[2m(�[22m�[2m32 tests�[22m�[2m)�[22m�[32m 39�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/zoned/interval/intervalAbutsZoned.test.ts �[2m(�[22m�[2m36 tests�[22m�[2m)�[22m�[32m 38�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/zoned/get/getTimeZones.test.ts �[2m(�[22m�[2m4 tests�[22m�[2m)�[22m�[32m 35�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/unix/interval/intervalOverlappingDaysUnix.test.ts �[2m(�[22m�[2m60 tests�[22m�[2m)�[22m�[32m 37�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/plain/format/formatDate.test.ts �[2m(�[22m�[2m195 tests�[22m�[2m)�[22m�[32m 37�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/zoned/format/formatRelativeZoned.test.ts �[2m(�[22m�[2m129 tests�[22m�[2m)�[22m�[32m 33�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/plain/convert/convertDateToCalendar.test.ts �[2m(�[22m�[2m329 tests�[22m�[2m)�[22m�[32m 37�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/unix/format/formatRelativeUnix.test.ts �[2m(�[22m�[2m153 tests�[22m�[2m)�[22m�[32m 33�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/zoned/calculate/getLocaleZonedEndOfWeek.test.ts �[2m(�[22m�[2m82 tests�[22m�[2m)�[22m�[32m 35�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/plain/calculate/addBusinessDays.test.ts �[2m(�[22m�[2m92 tests�[22m�[2m)�[22m�[32m 36�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/utc/format/formatRelativeUtc.test.ts �[2m(�[22m�[2m132 tests�[22m�[2m)�[22m�[32m 32�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/zoned/interval/intervalContainsZoned.test.ts �[2m(�[22m�[2m57 tests�[22m�[2m)�[22m�[32m 35�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/test/noThrow.test.ts �[2m(�[22m�[2m556 tests�[22m�[2m)�[22m�[32m 246�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/plain/calculate/diffDate.test.ts �[2m(�[22m�[2m89 tests�[22m�[2m)�[22m�[32m 30�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/instant/convert/fromOffsetInstant.test.ts �[2m(�[22m�[2m134 tests�[22m�[2m)�[22m�[32m 38�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/internal/zonedWallClockDifference.test.ts �[2m(�[22m�[2m56 tests�[22m�[2m)�[22m�[32m 31�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/plain/interval/intervalCountDate.test.ts �[2m(�[22m�[2m79 tests�[22m�[2m)�[22m�[32m 31�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/plain/interval/splitIntervalByUnitDate.test.ts �[2m(�[22m�[2m62 tests�[22m�[2m)�[22m�[32m 34�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/plain/calculate/addDate.grid.test.ts �[2m(�[22m�[2m150 tests�[22m�[2m)�[22m�[32m 34�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/test/optionsObject.test.ts �[2m(�[22m�[2m544 tests�[22m�[2m)�[22m�[32m 107�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/plain/convert/convertDateToCalendar.umalqura.test.ts �[2m(�[22m�[2m528 tests�[22m�[2m)�[22m�[32m 34�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/test/requiredLocale.test.ts �[2m(�[22m�[2m21 tests�[22m�[2m)�[22m�[32m 34�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/instant/convert/toOffsetInstant.test.ts �[2m(�[22m�[2m158 tests�[22m�[2m)�[22m�[32m 25�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/interval/calculate/intersectIntervals.test.ts �[2m(�[22m�[2m58 tests�[22m�[2m)�[22m�[32m 32�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/zoned/compare/isZonedThisUnit.test.ts �[2m(�[22m�[2m89 tests�[22m�[2m)�[22m�[32m 31�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/duration/normalize/normalizeDuration.test.ts �[2m(�[22m�[2m74 tests�[22m�[2m)�[22m�[32m 32�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/zoned/interval/intervalIntersectionZoned.test.ts �[2m(�[22m�[2m36 tests�[22m�[2m)�[22m�[32m 30�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/zoned/interval/intervalUnionZoned.test.ts �[2m(�[22m�[2m100 tests�[22m�[2m)�[22m�[32m 30�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/zoned/interval/calendarZonedIntervalPolicy.test.ts �[2m(�[22m�[2m27 tests�[22m�[2m)�[22m�[32m 32�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/zoned/calculate/startOfQuarterForZoned.test.ts �[2m(�[22m�[2m35 tests�[22m�[2m)�[22m�[32m 26�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/zoned/calculate/addZonedBusinessDays.test.ts �[2m(�[22m�[2m59 tests�[22m�[2m)�[22m�[32m 28�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/zoned/calculate/getHoursInZonedDay.test.ts �[2m(�[22m�[2m70 tests�[22m�[2m)�[22m�[32m 28�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/zoned/format/formatCalendarZoned.test.ts �[2m(�[22m�[2m64 tests�[22m�[2m)�[22m�[32m 30�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/plain/format/formatCalendar.test.ts �[2m(�[22m�[2m71 tests�[22m�[2m)�[22m�[32m 23�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/internal/temporalCompat/fieldSearch.test.ts �[2m(�[22m�[2m19 tests�[22m�[2m)�[22m�[32m 23�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/plain/interval/intervalLengthDate.test.ts �[2m(�[22m�[2m30 tests�[22m�[2m)�[22m�[32m 25�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/utc/format/formatCalendarUtc.test.ts �[2m(�[22m�[2m62 tests�[22m�[2m)�[22m�[32m 26�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/zoned/interval/intervalSplitAtZoned.test.ts �[2m(�[22m�[2m24 tests�[22m�[2m)�[22m�[32m 24�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/internal/temporalCompat/largeMonthArithmetic.test.ts �[2m(�[22m�[2m37 tests�[22m�[2m)�[22m�[33m 12340�[2mms�[22m�[39m
packages/gmt test:      �[33m�[2m✓�[22m�[39m 'gmt.addDate("2024-01-15[u-ca=persian]…' → '+252023-12-27[u-ca=persian]' ('persian 3,000,000 months = 250,000 ye…') �[33m 467�[2mms�[22m�[39m
packages/gmt test:      �[33m�[2m✓�[22m�[39m 'gmt.subtractDate("2024-01-14[u-ca=isl…' → '-240532-04-17[u-ca=islamic-civil]' ('islamic-civil back 250,000 years') �[33m 639�[2mms�[22m�[39m
packages/gmt test:      �[33m�[2m✓�[22m�[39m 'gmt.addDate("2024-01-15[u-ca=japanese…' → '+252024-01-15[u-ca=japanese]' ('japanese 250,000 years, still reiwa') �[33m 396�[2mms�[22m�[39m
packages/gmt test:      �[33m�[2m✓�[22m�[39m 'gmt.addDate("2024-01-15[u-ca=gregory]…' → '+252024-01-15[u-ca=gregory]' ('gregory 250,000 years (Chromium 153)') �[33m 365�[2mms�[22m�[39m
packages/gmt test:      �[33m�[2m✓�[22m�[39m 'gmt.addDate("2024-03-15[u-ca=ethiopic…' → '+232798-03-10[u-ca=ethiopic]' ('ethiopic 13-month years: 230,769 year…') �[33m 430�[2mms�[22m�[39m
packages/gmt test:      �[33m�[2m✓�[22m�[39m 'gmt.addDate("2024-03-15[u-ca=coptic]"…' → '+232798-03-10[u-ca=coptic]' ('coptic 13-month years: 230,769 years …') �[33m 370�[2mms�[22m�[39m
packages/gmt test:      �[33m�[2m✓�[22m�[39m 'gmt.addDate("2024-01-15[u-ca=hebrew]"…' → '+244580-02-07[u-ca=hebrew]' ('hebrew leap-month years (D–R month co…') �[33m 339�[2mms�[22m�[39m
packages/gmt test:      �[33m�[2m✓�[22m�[39m 'gmt.addDate("2024-01-15[u-ca=persian]…' → '' ('persian year 334735 is past the maxim…') �[33m 337�[2mms�[22m�[39m
packages/gmt test:      �[33m�[2m✓�[22m�[39m 'gmt.addDate("2024-01-15[u-ca=persian]…' → '' ('the largest valid Duration months fie…') �[33m 302�[2mms�[22m�[39m
packages/gmt test:      �[33m�[2m✓�[22m�[39m 'gmt.diffDate("-247976-02-03[u-ca=pers…' → 6000000 ('persian 500,000 years of months, acro…') �[33m 325�[2mms�[22m�[39m
packages/gmt test:      �[33m�[2m✓�[22m�[39m 'gmt.intervalLengthDate("2024-01-15[u-…' → 3000000 ('interval length in months') �[33m 458�[2mms�[22m�[39m
packages/gmt test:      �[33m�[2m✓�[22m�[39m 'gmt.durationAs("P3000000M", "days", {…' → 91310606 ('ISO 2024-01-15 → +252023-12-27') �[33m 403�[2mms�[22m�[39m
packages/gmt test:      �[33m�[2m✓�[22m�[39m 'gmt.durationAs("P3000000M", "days", {…' → 88591783 ('hebrew 5784-05-05 → 248337-07-05 in I…') �[33m 361�[2mms�[22m�[39m
packages/gmt test:      �[33m�[2m✓�[22m�[39m 'gmt.durationAs("P3000000M", "days", {…' → 84288467 ('coptic 1740-07-06 → 232509-10-06 in I…') �[33m 376�[2mms�[22m�[39m
packages/gmt test:      �[33m�[2m✓�[22m�[39m 'gmt.normalizeDuration("P90000000D", {…' → 'P2956940M5D' ('target persian 247814-06-30: 246,412 …') �[33m 343�[2mms�[22m�[39m
packages/gmt test:      �[33m�[2m✓�[22m�[39m 'gmt.normalizeDuration("P90000000D", {…' → 'P3203285M24D' ('target ethioaa 253923-01-30, 13-month…') �[33m 342�[2mms�[22m�[39m
packages/gmt test:      �[33m�[2m✓�[22m�[39m 'gmt.compareDurations("P3000000M", "P1…' → 1 ('3,000,000 months is longer than a day') �[33m 348�[2mms�[22m�[39m
packages/gmt test:      �[33m�[2m✓�[22m�[39m 'gmt.compareDurations("P4000000M", "P1…' → null ('relativeTo + 4,000,000 months is past…') �[33m 338�[2mms�[22m�[39m
packages/gmt test:      �[33m�[2m✓�[22m�[39m 'gmt.addZoned("2024-01-15T12:00:00+03:…' → '+252023-12-27T12:00:00+03:30[Asia/Teh…' ('Tehran has kept +03:30 without DST si…') �[33m 406�[2mms�[22m�[39m
packages/gmt test:      �[33m�[2m✓�[22m�[39m 'gmt.subtractZoned("+252023-12-27T12:0…' → '2024-01-15T12:00:00+03:30[Asia/Tehran…' ('inverse of the addZoned row') �[33m 396�[2mms�[22m�[39m
packages/gmt test:      �[33m�[2m✓�[22m�[39m 'gmt.addZoned("2025-09-05T00:30:00-04:…' → '' ('far past the maximum') �[33m 391�[2mms�[22m�[39m
packages/gmt test:      �[33m�[2m✓�[22m�[39m 'gmt.diffDate("2024-01-15[u-ca=hebrew]…' → 242553 ('years stay cheap: M07 follows M05') �[33m 385�[2mms�[22m�[39m
packages/gmt test:      �[33m�[2m✓�[22m�[39m 'gmt.diffZoned("2024-01-15T12:00:00+03…' → 3000000 ('zoned difference in months') �[33m 415�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/zoned/convert/convertZonedToCalendar.test.ts �[2m(�[22m�[2m119 tests�[22m�[2m)�[22m�[32m 30�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/duration/compare/compareDurations.test.ts �[2m(�[22m�[2m77 tests�[22m�[2m)�[22m�[32m 26�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/zoned/calculate/cycleZoned.test.ts �[2m(�[22m�[2m97 tests�[22m�[2m)�[22m�[32m 22�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/unix/format/formatCalendarUnix.test.ts �[2m(�[22m�[2m70 tests�[22m�[2m)�[22m�[32m 27�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/plain/format/formatDateRange.test.ts �[2m(�[22m�[2m55 tests�[22m�[2m)�[22m�[32m 22�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/zoned/calculate/endOfQuarterForZoned.test.ts �[2m(�[22m�[2m35 tests�[22m�[2m)�[22m�[32m 22�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/plain/interval/intervalFromDurationDate.test.ts �[2m(�[22m�[2m31 tests�[22m�[2m)�[22m�[32m 19�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/zoned/interval/mergeIntervalsZoned.test.ts �[2m(�[22m�[2m43 tests�[22m�[2m)�[22m�[32m 21�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/plain/interval/intervalAnnotationPolicy.test.ts �[2m(�[22m�[2m98 tests�[22m�[2m)�[22m�[32m 23�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/zoned/format/formatTimeZoneName.test.ts �[2m(�[22m�[2m31 tests�[22m�[2m)�[22m�[32m 38�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/instant/convert/classifyLocal.test.ts �[2m(�[22m�[2m102 tests�[22m�[2m)�[22m�[32m 22�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/plain/convert/convertDateToCalendar.edges.test.ts �[2m(�[22m�[2m358 tests�[22m�[2m)�[22m�[32m 25�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/internal/resolveLocale.test.ts �[2m(�[22m�[2m25 tests�[22m�[2m)�[22m�[32m 19�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/zoned/calculate/subtractZonedBusinessDays.test.ts �[2m(�[22m�[2m47 tests�[22m�[2m)�[22m�[32m 22�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/zoned/compare/isZonedBusinessDay.test.ts �[2m(�[22m�[2m42 tests�[22m�[2m)�[22m�[32m 21�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/zoned/calculate/setZoned.test.ts �[2m(�[22m�[2m81 tests�[22m�[2m)�[22m�[32m 20�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/unix/interval/intervalLengthUnix.test.ts �[2m(�[22m�[2m64 tests�[22m�[2m)�[22m�[32m 21�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/plain/locale/getLocaleEraNames.test.ts �[2m(�[22m�[2m25 tests�[22m�[2m)�[22m�[32m 19�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/zoned/calculate/clampZoned.test.ts �[2m(�[22m�[2m79 tests�[22m�[2m)�[22m�[32m 20�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/unix/format/formatUnix.test.ts �[2m(�[22m�[2m53 tests�[22m�[2m)�[22m�[32m 19�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/zoned/get/getZonedNow.test.ts �[2m(�[22m�[2m48 tests�[22m�[2m)�[22m�[32m 17�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/zoned/calculate/closestZonedTo.test.ts �[2m(�[22m�[2m84 tests�[22m�[2m)�[22m�[32m 21�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/unix/calculate/endOfUnix.test.ts �[2m(�[22m�[2m56 tests�[22m�[2m)�[22m�[32m 18�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/calendar/calculate/getFiscalPeriod.test.ts �[2m(�[22m�[2m102 tests�[22m�[2m)�[22m�[32m 24�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/zoned/convert/convertPlainDateTimeToZoned.test.ts �[2m(�[22m�[2m89 tests�[22m�[2m)�[22m�[32m 17�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/unix/calculate/diffUnixAsDuration.test.ts �[2m(�[22m�[2m70 tests�[22m�[2m)�[22m�[32m 22�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/interval/calculate/intervalAlgebra.test.ts �[2m(�[22m�[2m3 tests�[22m�[2m)�[22m�[32m 21�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/zoned/compare/isZonedRelativeDay.test.ts �[2m(�[22m�[2m49 tests�[22m�[2m)�[22m�[32m 18�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/plain/calculate/roundDate.test.ts �[2m(�[22m�[2m91 tests�[22m�[2m)�[22m�[32m 18�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/zoned/format/formatZonedToParts.test.ts �[2m(�[22m�[2m50 tests�[22m�[2m)�[22m�[32m 17�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/unix/calculate/diffUnix.test.ts �[2m(�[22m�[2m55 tests�[22m�[2m)�[22m�[32m 18�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/zoned/map/mapZonedDatesInRange.test.ts �[2m(�[22m�[2m73 tests�[22m�[2m)�[22m�[32m 18�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/zoned/get/getZonedNowUnit.test.ts �[2m(�[22m�[2m63 tests�[22m�[2m)�[22m�[32m 18�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/zoned/compare/isZonedWeekend.test.ts �[2m(�[22m�[2m111 tests�[22m�[2m)�[22m�[32m 18�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/zoned/compare/isZonedPast.test.ts �[2m(�[22m�[2m46 tests�[22m�[2m)�[22m�[32m 15�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/zoned/validate/isValidCalendarZonedDateTime.test.ts �[2m(�[22m�[2m98 tests�[22m�[2m)�[22m�[32m 20�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/plain/calculate/roundDateTime.test.ts �[2m(�[22m�[2m93 tests�[22m�[2m)�[22m�[32m 18�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/unix/calculate/roundUnix.test.ts �[2m(�[22m�[2m63 tests�[22m�[2m)�[22m�[32m 19�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/zoned/interval/intervalDivideEquallyZoned.test.ts �[2m(�[22m�[2m36 tests�[22m�[2m)�[22m�[32m 16�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/plain/compare/areDateTimesEqualBy.test.ts �[2m(�[22m�[2m69 tests�[22m�[2m)�[22m�[32m 12�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/plain/format/formatDateTimeToParts.test.ts �[2m(�[22m�[2m49 tests�[22m�[2m)�[22m�[32m 21�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/zoned/validate/isValidTimeZone.test.ts �[2m(�[22m�[2m91 tests�[22m�[2m)�[22m�[32m 16�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/zoned/validate/isValidZonedDateTime.test.ts �[2m(�[22m�[2m176 tests�[22m�[2m)�[22m�[32m 19�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/calendar/business/rollDate.test.ts �[2m(�[22m�[2m79 tests�[22m�[2m)�[22m�[32m 17�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/plain/calculate/diffTime.test.ts �[2m(�[22m�[2m61 tests�[22m�[2m)�[22m�[32m 17�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/utc/compare/areUtcEqualBy.test.ts �[2m(�[22m�[2m59 tests�[22m�[2m)�[22m�[32m 20�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/unix/calculate/minUnix.test.ts �[2m(�[22m�[2m14 tests�[22m�[2m)�[22m�[32m 16�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/plain/format/formatDateToParts.test.ts �[2m(�[22m�[2m49 tests�[22m�[2m)�[22m�[32m 17�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/plain/parse/parseTimeWithPattern.test.ts �[2m(�[22m�[2m60 tests�[22m�[2m)�[22m�[32m 16�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/utc/interval/intervalCountUtc.test.ts �[2m(�[22m�[2m50 tests�[22m�[2m)�[22m�[32m 16�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/utc/calculate/diffUtc.test.ts �[2m(�[22m�[2m47 tests�[22m�[2m)�[22m�[32m 15�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/plain/compare/isThisUnit.test.ts �[2m(�[22m�[2m60 tests�[22m�[2m)�[22m�[32m 16�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/zoned/convert/convertZonedToUnix.test.ts �[2m(�[22m�[2m94 tests�[22m�[2m)�[22m�[32m 15�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/unix/calculate/maxUnix.test.ts �[2m(�[22m�[2m16 tests�[22m�[2m)�[22m�[32m 16�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/utc/calculate/endOfUtc.test.ts �[2m(�[22m�[2m60 tests�[22m�[2m)�[22m�[32m 14�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/zoned/chop/chopZonedDateTime.test.ts �[2m(�[22m�[2m99 tests�[22m�[2m)�[22m�[32m 15�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/calendar/business/businessDaysBetween.test.ts �[2m(�[22m�[2m70 tests�[22m�[2m)�[22m�[32m 15�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/utc/interval/splitIntervalByUnitUtc.test.ts �[2m(�[22m�[2m53 tests�[22m�[2m)�[22m�[32m 15�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/calendar/business/mergeCalendars.test.ts �[2m(�[22m�[2m49 tests�[22m�[2m)�[22m�[32m 15�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/zoned/compare/isZonedFuture.test.ts �[2m(�[22m�[2m46 tests�[22m�[2m)�[22m�[32m 16�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/utc/interval/intervalAnnotationPolicy.test.ts �[2m(�[22m�[2m38 tests�[22m�[2m)�[22m�[32m 15�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/unix/calculate/setUnix.test.ts �[2m(�[22m�[2m46 tests�[22m�[2m)�[22m�[32m 16�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/zoned/compare/areZonedEqual.test.ts �[2m(�[22m�[2m44 tests�[22m�[2m)�[22m�[32m 14�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/zoned/get/getZonedToday.test.ts �[2m(�[22m�[2m48 tests�[22m�[2m)�[22m�[32m 13�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/unix/interval/intervalFromDurationUnix.test.ts �[2m(�[22m�[2m43 tests�[22m�[2m)�[22m�[32m 17�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/plain/calculate/diffDateTime.test.ts �[2m(�[22m�[2m68 tests�[22m�[2m)�[22m�[32m 12�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/duration/format/formatDuration.test.ts �[2m(�[22m�[2m55 tests�[22m�[2m)�[22m�[32m 21�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/unix/convert/convertUnixToZoned.test.ts �[2m(�[22m�[2m122 tests�[22m�[2m)�[22m�[32m 17�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/plain/calculate/subtractBusinessDays.test.ts �[2m(�[22m�[2m85 tests�[22m�[2m)�[22m�[32m 12�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/plain/interval/splitIntervalByUnitDateTime.test.ts �[2m(�[22m�[2m51 tests�[22m�[2m)�[22m�[32m 14�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/zoned/compare/isInDaylightSaving.test.ts �[2m(�[22m�[2m40 tests�[22m�[2m)�[22m�[32m 15�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/zoned/interval/validate/isValidZonedInterval.test.ts �[2m(�[22m�[2m57 tests�[22m�[2m)�[22m�[32m 14�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/plain/compare/isBusinessDay.test.ts �[2m(�[22m�[2m75 tests�[22m�[2m)�[22m�[32m 10�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/zoned/parse/parseRfc2822.test.ts �[2m(�[22m�[2m73 tests�[22m�[2m)�[22m�[32m 14�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/plain/compare/isRelativeDay.test.ts �[2m(�[22m�[2m19 tests�[22m�[2m)�[22m�[32m 13�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/unix/interval/splitIntervalByUnitUnix.test.ts �[2m(�[22m�[2m84 tests�[22m�[2m)�[22m�[32m 14�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/unix/calculate/startOfQuarterForUnix.test.ts �[2m(�[22m�[2m18 tests�[22m�[2m)�[22m�[32m 12�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/internal/fiscalCalendar.test.ts �[2m(�[22m�[2m47 tests�[22m�[2m)�[22m�[32m 13�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/plain/calculate/endOfDateTime.test.ts �[2m(�[22m�[2m74 tests�[22m�[2m)�[22m�[32m 12�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/zoned/interval/intervalsOverlapZoned.test.ts �[2m(�[22m�[2m32 tests�[22m�[2m)�[22m�[32m 13�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/internal/durationUntilString.test.ts �[2m(�[22m�[2m27 tests�[22m�[2m)�[22m�[32m 10�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/utc/interval/intervalLengthUtc.test.ts �[2m(�[22m�[2m17 tests�[22m�[2m)�[22m�[32m 12�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/utc/calculate/diffUtcAsDuration.test.ts �[2m(�[22m�[2m39 tests�[22m�[2m)�[22m�[32m 14�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/plain/calculate/getWeeksInLocaleWeekYear.test.ts �[2m(�[22m�[2m50 tests�[22m�[2m)�[22m�[32m 14�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/zoned/convert/convertZonedToZoned.test.ts �[2m(�[22m�[2m70 tests�[22m�[2m)�[22m�[32m 11�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/precision/convert/fromExcelSerial.test.ts �[2m(�[22m�[2m67 tests�[22m�[2m)�[22m�[32m 12�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/zoned/calculate/getLocaleZonedDayOfWeek.test.ts �[2m(�[22m�[2m75 tests�[22m�[2m)�[22m�[32m 14�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/zoned/chop/chopZonedMilliseconds.test.ts �[2m(�[22m�[2m56 tests�[22m�[2m)�[22m�[32m 13�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/utc/parse/parseHttp.test.ts �[2m(�[22m�[2m44 tests�[22m�[2m)�[22m�[32m 14�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/zoned/chop/chopZonedSeconds.test.ts �[2m(�[22m�[2m56 tests�[22m�[2m)�[22m�[32m 13�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/precision/convert/fromNtpTimestamp.test.ts �[2m(�[22m�[2m58 tests�[22m�[2m)�[22m�[32m 12�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/zoned/parse/parseUnitFromZoned.test.ts �[2m(�[22m�[2m71 tests�[22m�[2m)�[22m�[32m 12�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/zoned/parse/parseDateFromZoned.test.ts �[2m(�[22m�[2m46 tests�[22m�[2m)�[22m�[32m 12�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/zoned/format/formatRfc2822.test.ts �[2m(�[22m�[2m36 tests�[22m�[2m)�[22m�[32m 13�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/plain/interval/splitIntervalByUnitTime.test.ts �[2m(�[22m�[2m45 tests�[22m�[2m)�[22m�[32m 12�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/zoned/compare/isBeforeZoned.test.ts �[2m(�[22m�[2m42 tests�[22m�[2m)�[22m�[32m 11�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/zoned/parse/getZonedOffsetAs.test.ts �[2m(�[22m�[2m41 tests�[22m�[2m)�[22m�[32m 11�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/plain/locale/getLocaleMeridiems.test.ts �[2m(�[22m�[2m25 tests�[22m�[2m)�[22m�[32m 11�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/plain/interval/intervalCountDateTime.test.ts �[2m(�[22m�[2m63 tests�[22m�[2m)�[22m�[32m 11�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/zoned/compare/isAfterZoned.test.ts �[2m(�[22m�[2m42 tests�[22m�[2m)�[22m�[32m 10�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/plain/interval/mergeIntervalsDate.test.ts �[2m(�[22m�[2m18 tests�[22m�[2m)�[22m�[32m 8�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/zoned/parse/parseTimeFromZoned.test.ts �[2m(�[22m�[2m47 tests�[22m�[2m)�[22m�[32m 11�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/span/calculate/spanMs.test.ts �[2m(�[22m�[2m73 tests�[22m�[2m)�[22m�[32m 12�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/plain/interval/intervalDivideEquallyTime.test.ts �[2m(�[22m�[2m34 tests�[22m�[2m)�[22m�[32m 11�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/unix/parse/parseDateFromUnix.test.ts �[2m(�[22m�[2m37 tests�[22m�[2m)�[22m�[32m 12�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/zoned/convert/convertZonedToUtc.test.ts �[2m(�[22m�[2m66 tests�[22m�[2m)�[22m�[32m 13�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/internal/resolveDurationRelativeTo.test.ts �[2m(�[22m�[2m45 tests�[22m�[2m)�[22m�[32m 13�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/plain/calculate/diffDateTimeAsDuration.test.ts �[2m(�[22m�[2m52 tests�[22m�[2m)�[22m�[32m 10�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/unix/calculate/addUnix.test.ts �[2m(�[22m�[2m28 tests�[22m�[2m)�[22m�[32m 14�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/utc/convert/convertUtcToZoned.test.ts �[2m(�[22m�[2m49 tests�[22m�[2m)�[22m�[32m 10�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/utc/parse/parseUnitFromUtc.test.ts �[2m(�[22m�[2m55 tests�[22m�[2m)�[22m�[32m 12�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/unix/parse/parseTimeFromUnix.test.ts �[2m(�[22m�[2m31 tests�[22m�[2m)�[22m�[32m 10�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/interval/calculate/sumIntervals.test.ts �[2m(�[22m�[2m38 tests�[22m�[2m)�[22m�[32m 10�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/plain/validate/isValidCalendarDate.test.ts �[2m(�[22m�[2m87 tests�[22m�[2m)�[22m�[32m 11�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/precision/convert/fromNanoseconds.test.ts �[2m(�[22m�[2m54 tests�[22m�[2m)�[22m�[32m 8�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/plain/interval/intervalUnionDate.test.ts �[2m(�[22m�[2m36 tests�[22m�[2m)�[22m�[32m 10�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/utc/calculate/startOfUtc.test.ts �[2m(�[22m�[2m42 tests�[22m�[2m)�[22m�[32m 11�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/zoned/chop/chopZonedDate.test.ts �[2m(�[22m�[2m56 tests�[22m�[2m)�[22m�[32m 9�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/plain/map/mapDatesInRange.test.ts �[2m(�[22m�[2m42 tests�[22m�[2m)�[22m�[32m 8�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/unix/parse/parseWeekFromUnix.test.ts �[2m(�[22m�[2m30 tests�[22m�[2m)�[22m�[32m 11�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/unix/parse/parseUnitFromUnix.test.ts �[2m(�[22m�[2m76 tests�[22m�[2m)�[22m�[32m 10�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/plain/calculate/startOfDateTime.test.ts �[2m(�[22m�[2m57 tests�[22m�[2m)�[22m�[32m 10�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/internal/utcOffsetString.test.ts �[2m(�[22m�[2m159 tests�[22m�[2m)�[22m�[32m 11�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/interval/calculate/mergeIntervals.test.ts �[2m(�[22m�[2m61 tests�[22m�[2m)�[22m�[32m 12�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/precision/convert/toExcelSerial.test.ts �[2m(�[22m�[2m80 tests�[22m�[2m)�[22m�[32m 8�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/zoned/get/getTimeZoneOffset.test.ts �[2m(�[22m�[2m56 tests�[22m�[2m)�[22m�[32m 13�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/plain/interval/intervalDivideEquallyDate.test.ts �[2m(�[22m�[2m35 tests�[22m�[2m)�[22m�[32m 10�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/utc/interval/intervalUnionUtc.test.ts �[2m(�[22m�[2m39 tests�[22m�[2m)�[22m�[32m 11�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/plain/get/getNow.test.ts �[2m(�[22m�[2m25 tests�[22m�[2m)�[22m�[32m 9�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/interval/calculate/clampInterval.test.ts �[2m(�[22m�[2m28 tests�[22m�[2m)�[22m�[32m 10�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/test/annotationGrammar.test.ts �[2m(�[22m�[2m132 tests�[22m�[2m)�[22m�[32m 13�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/utc/format/formatUtc.test.ts �[2m(�[22m�[2m33 tests�[22m�[2m)�[22m�[32m 10�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/zoned/parse/parseWeekFromZoned.test.ts �[2m(�[22m�[2m33 tests�[22m�[2m)�[22m�[32m 9�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/precision/convert/fromFileTime.test.ts �[2m(�[22m�[2m37 tests�[22m�[2m)�[22m�[32m 8�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/internal/unixEpochValue.test.ts �[2m(�[22m�[2m101 tests�[22m�[2m)�[22m�[32m 10�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/plain/calculate/roundTime.test.ts �[2m(�[22m�[2m71 tests�[22m�[2m)�[22m�[32m 9�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/utc/interval/intervalSplitAtUtc.test.ts �[2m(�[22m�[2m16 tests�[22m�[2m)�[22m�[32m 7�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/plain/interval/intervalDifferenceDate.test.ts �[2m(�[22m�[2m33 tests�[22m�[2m)�[22m�[32m 8�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/utc/interval/mergeIntervalsUtc.test.ts �[2m(�[22m�[2m19 tests�[22m�[2m)�[22m�[32m 9�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/interval/compare/intervalsOverlap.test.ts �[2m(�[22m�[2m51 tests�[22m�[2m)�[22m�[32m 10�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/plain/calculate/closestDateTo.test.ts �[2m(�[22m�[2m38 tests�[22m�[2m)�[22m�[32m 9�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/interval/calculate/subtractIntervals.test.ts �[2m(�[22m�[2m48 tests�[22m�[2m)�[22m�[32m 10�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/zoned/format/formatRfc3339.test.ts �[2m(�[22m�[2m36 tests�[22m�[2m)�[22m�[32m 9�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/utc/calculate/roundUtc.test.ts �[2m(�[22m�[2m57 tests�[22m�[2m)�[22m�[32m 10�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/plain/get/getNowUnit.test.ts �[2m(�[22m�[2m26 tests�[22m�[2m)�[22m�[32m 11�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/calendar/validate/isValidBusinessCalendar.test.ts �[2m(�[22m�[2m66 tests�[22m�[2m)�[22m�[32m 9�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/utc/parse/parseWeekFromUtc.test.ts �[2m(�[22m�[2m39 tests�[22m�[2m)�[22m�[32m 12�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/plain/interval/intervalDivideEquallyDateTime.test.ts �[2m(�[22m�[2m34 tests�[22m�[2m)�[22m�[32m 9�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/utc/interval/intervalDivideEquallyUtc.test.ts �[2m(�[22m�[2m36 tests�[22m�[2m)�[22m�[32m 10�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/utc/interval/intervalDifferenceUtc.test.ts �[2m(�[22m�[2m30 tests�[22m�[2m)�[22m�[32m 12�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/utc/interval/intervalContainsUtc.test.ts �[2m(�[22m�[2m42 tests�[22m�[2m)�[22m�[32m 8�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/zoned/interval/intervalAnnotationPolicy.test.ts �[2m(�[22m�[2m14 tests�[22m�[2m)�[22m�[32m 10�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/plain/calculate/getLocaleDayOfWeek.test.ts �[2m(�[22m�[2m55 tests�[22m�[2m)�[22m�[32m 7�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/utc/calculate/subtractUtc.test.ts �[2m(�[22m�[2m33 tests�[22m�[2m)�[22m�[32m 11�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/utc/parse/parseDateFromUtc.test.ts �[2m(�[22m�[2m19 tests�[22m�[2m)�[22m�[32m 8�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/plain/interval/intervalFromDurationTime.test.ts �[2m(�[22m�[2m45 tests�[22m�[2m)�[22m�[32m 10�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/unix/calculate/endOfQuarterForUnix.test.ts �[2m(�[22m�[2m16 tests�[22m�[2m)�[22m�[32m 9�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/utc/interval/intervalIntersectionUtc.test.ts �[2m(�[22m�[2m31 tests�[22m�[2m)�[22m�[32m 10�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/zoned/chop/chopZonedTimezone.test.ts �[2m(�[22m�[2m56 tests�[22m�[2m)�[22m�[32m 11�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/precision/validate/isValidInstant.test.ts �[2m(�[22m�[2m64 tests�[22m�[2m)�[22m�[32m 9�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/internal/calendarDateString.test.ts �[2m(�[22m�[2m74 tests�[22m�[2m)�[22m�[32m 9�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/plain/interval/intervalCountTime.test.ts �[2m(�[22m�[2m52 tests�[22m�[2m)�[22m�[32m 11�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/utc/calculate/endOfQuarterForUtc.test.ts �[2m(�[22m�[2m13 tests�[22m�[2m)�[22m�[32m 10�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/calendar/business/nextBusinessDay.test.ts �[2m(�[22m�[2m53 tests�[22m�[2m)�[22m�[32m 9�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/plain/interval/intervalsOverlapDate.test.ts �[2m(�[22m�[2m30 tests�[22m�[2m)�[22m�[32m 8�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/utc/calculate/setUtc.test.ts �[2m(�[22m�[2m20 tests�[22m�[2m)�[22m�[32m 8�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/duration/calculate/getDurationUnit.test.ts �[2m(�[22m�[2m76 tests�[22m�[2m)�[22m�[32m 8�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/internal/calendarZonedPairPolicy.test.ts �[2m(�[22m�[2m6 tests�[22m�[2m)�[22m�[32m 8�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/zoned/chop/chopZonedTime.test.ts �[2m(�[22m�[2m56 tests�[22m�[2m)�[22m�[32m 9�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/plain/interval/intervalXorAllDate.test.ts �[2m(�[22m�[2m16 tests�[22m�[2m)�[22m�[32m 9�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/utc/interval/intervalFromDurationUtc.test.ts �[2m(�[22m�[2m25 tests�[22m�[2m)�[22m�[32m 9�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/span/calculate/spanNs.test.ts �[2m(�[22m�[2m62 tests�[22m�[2m)�[22m�[32m 8�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/plain/interval/intervalXorDate.test.ts �[2m(�[22m�[2m28 tests�[22m�[2m)�[22m�[32m 8�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/plain/calculate/getLocaleWeekYear.test.ts �[2m(�[22m�[2m54 tests�[22m�[2m)�[22m�[32m 11�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/plain/interval/intervalLengthDateTime.test.ts �[2m(�[22m�[2m14 tests�[22m�[2m)�[22m�[32m 9�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/utc/calculate/addUtc.test.ts �[2m(�[22m�[2m33 tests�[22m�[2m)�[22m�[32m 9�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/utc/parse/parseDayFromUtc.test.ts �[2m(�[22m�[2m13 tests�[22m�[2m)�[22m�[32m 7�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/calendar/business/previousBusinessDay.test.ts �[2m(�[22m�[2m53 tests�[22m�[2m)�[22m�[32m 9�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/utc/calculate/startOfQuarterForUtc.test.ts �[2m(�[22m�[2m15 tests�[22m�[2m)�[22m�[32m 8�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/plain/interval/intervalXorAllTime.test.ts �[2m(�[22m�[2m19 tests�[22m�[2m)�[22m�[32m 9�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/plain/interval/intervalLengthTime.test.ts �[2m(�[22m�[2m12 tests�[22m�[2m)�[22m�[32m 7�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/utc/get/getUtcNowUnit.test.ts �[2m(�[22m�[2m26 tests�[22m�[2m)�[22m�[32m 9�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/plain/interval/intervalIntersectionDate.test.ts �[2m(�[22m�[2m31 tests�[22m�[2m)�[22m�[32m 8�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/plain/interval/intervalAbutsDate.test.ts �[2m(�[22m�[2m35 tests�[22m�[2m)�[22m�[32m 12�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/span/validate/isValidSpan.test.ts �[2m(�[22m�[2m56 tests�[22m�[2m)�[22m�[32m 8�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/interval/calculate/splitIntervalAt.test.ts �[2m(�[22m�[2m42 tests�[22m�[2m)�[22m�[32m 10�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/internal/splitStep.test.ts �[2m(�[22m�[2m39 tests�[22m�[2m)�[22m�[32m 11�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/internal/rfc5322DateTime.test.ts �[2m(�[22m�[2m9 tests�[22m�[2m)�[22m�[32m 8�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/utc/interval/intervalXorAllUtc.test.ts �[2m(�[22m�[2m15 tests�[22m�[2m)�[22m�[32m 9�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/plain/interval/intervalOverlappingDaysDate.test.ts �[2m(�[22m�[2m32 tests�[22m�[2m)�[22m�[32m 10�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/zoned/parse/getZonedOffset.test.ts �[2m(�[22m�[2m32 tests�[22m�[2m)�[22m�[32m 8�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/plain/calculate/addDate.umalqura.test.ts �[2m(�[22m�[2m17 tests�[22m�[2m)�[22m�[32m 9�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/zoned/convert/convertZonedToPlainDateTime.test.ts �[2m(�[22m�[2m15 tests�[22m�[2m)�[22m�[32m 8�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/utc/format/formatHttp.test.ts �[2m(�[22m�[2m32 tests�[22m�[2m)�[22m�[32m 10�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/plain/calculate/addDateTime.test.ts �[2m(�[22m�[2m44 tests�[22m�[2m)�[22m�[32m 8�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/plain/calculate/cycleDateTime.test.ts �[2m(�[22m�[2m39 tests�[22m�[2m)�[22m�[32m 7�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/plain/interval/mergeIntervalsTime.test.ts �[2m(�[22m�[2m16 tests�[22m�[2m)�[22m�[32m 8�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/utc/interval/intervalXorUtc.test.ts �[2m(�[22m�[2m27 tests�[22m�[2m)�[22m�[32m 8�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/plain/calculate/endOfTime.test.ts �[2m(�[22m�[2m41 tests�[22m�[2m)�[22m�[32m 8�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/plain/interval/intervalContainsTime.test.ts �[2m(�[22m�[2m41 tests�[22m�[2m)�[22m�[32m 8�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/plain/interval/intervalUnionTime.test.ts �[2m(�[22m�[2m29 tests�[22m�[2m)�[22m�[32m 8�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/plain/interval/intervalUnionDateTime.test.ts �[2m(�[22m�[2m29 tests�[22m�[2m)�[22m�[32m 8�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/duration/parse/parseDuration.test.ts �[2m(�[22m�[2m59 tests�[22m�[2m)�[22m�[32m 8�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/unix/calculate/subtractUnix.test.ts �[2m(�[22m�[2m28 tests�[22m�[2m)�[22m�[32m 8�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/zoned/validate/isValidZonedRange.test.ts �[2m(�[22m�[2m64 tests�[22m�[2m)�[22m�[32m 8�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/plain/interval/intervalContainsDate.test.ts �[2m(�[22m�[2m41 tests�[22m�[2m)�[22m�[32m 10�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/plain/calculate/cycleTime.test.ts �[2m(�[22m�[2m40 tests�[22m�[2m)�[22m�[32m 7�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/internal/dateTimeUnitHelpers.test.ts �[2m(�[22m�[2m25 tests�[22m�[2m)�[22m�[32m 8�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/interval/compare/intervalContains.test.ts �[2m(�[22m�[2m54 tests�[22m�[2m)�[22m�[32m 9�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/plain/calculate/subtractDateTime.test.ts �[2m(�[22m�[2m41 tests�[22m�[2m)�[22m�[32m 10�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/utc/interval/intervalOverlappingDaysUtc.test.ts �[2m(�[22m�[2m23 tests�[22m�[2m)�[22m�[32m 8�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/unix/get/getUnixNowUnit.test.ts �[2m(�[22m�[2m22 tests�[22m�[2m)�[22m�[32m 8�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/plain/interval/intervalDifferenceTime.test.ts �[2m(�[22m�[2m25 tests�[22m�[2m)�[22m�[32m 7�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/plain/interval/intervalIntersectionTime.test.ts �[2m(�[22m�[2m24 tests�[22m�[2m)�[22m�[32m 8�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/plain/compare/isPast.test.ts �[2m(�[22m�[2m12 tests�[22m�[2m)�[22m�[32m 8�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/internal/normalizeTimeZone.test.ts �[2m(�[22m�[2m42 tests�[22m�[2m)�[22m�[32m 7�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/plain/calculate/startOfTime.test.ts �[2m(�[22m�[2m38 tests�[22m�[2m)�[22m�[32m 7�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/plain/compare/isFuture.test.ts �[2m(�[22m�[2m12 tests�[22m�[2m)�[22m�[32m 8�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/utc/parse/parseSecondFromUtc.test.ts �[2m(�[22m�[2m12 tests�[22m�[2m)�[22m�[32m 7�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/plain/parse/parseUnitFromDateTime.test.ts �[2m(�[22m�[2m50 tests�[22m�[2m)�[22m�[32m 7�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/zoned/parse/parseTimeZoneFromZoned.test.ts �[2m(�[22m�[2m29 tests�[22m�[2m)�[22m�[32m 7�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/unix/parse/parseMonthFromUnix.test.ts �[2m(�[22m�[2m18 tests�[22m�[2m)�[22m�[32m 7�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/plain/interval/intervalFromDurationDateTime.test.ts �[2m(�[22m�[2m24 tests�[22m�[2m)�[22m�[32m 8�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/plain/calculate/subtractTime.test.ts �[2m(�[22m�[2m31 tests�[22m�[2m)�[22m�[32m 7�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/precision/convert/fromPgMicroseconds.test.ts �[2m(�[22m�[2m31 tests�[22m�[2m)�[22m�[32m 8�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/plain/calculate/endOfQuarterForDateTime.test.ts �[2m(�[22m�[2m21 tests�[22m�[2m)�[22m�[32m 9�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/plain/calculate/subtractDate.umalqura.test.ts �[2m(�[22m�[2m6 tests�[22m�[2m)�[22m�[32m 7�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/plain/compare/areDatesEqualBy.test.ts �[2m(�[22m�[2m40 tests�[22m�[2m)�[22m�[32m 8�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/utc/parse/parseMicrosecondFromUtc.test.ts �[2m(�[22m�[2m10 tests�[22m�[2m)�[22m�[32m 7�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/zoned/validate/hasDaylightSaving.test.ts �[2m(�[22m�[2m33 tests�[22m�[2m)�[22m�[32m 8�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/unix/parse/parseYearFromUnix.test.ts �[2m(�[22m�[2m18 tests�[22m�[2m)�[22m�[32m 7�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/precision/convert/fromDotNetTicks.test.ts �[2m(�[22m�[2m31 tests�[22m�[2m)�[22m�[32m 12�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/calendar/calculate/getQuarter.test.ts �[2m(�[22m�[2m62 tests�[22m�[2m)�[22m�[32m 8�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/zoned/parse/parseMonthFromZoned.test.ts �[2m(�[22m�[2m6 tests�[22m�[2m)�[22m�[32m 5�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/utc/interval/intervalsOverlapUtc.test.ts �[2m(�[22m�[2m29 tests�[22m�[2m)�[22m�[32m 7�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/plain/interval/intervalXorAllDateTime.test.ts �[2m(�[22m�[2m14 tests�[22m�[2m)�[22m�[32m 7�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/utc/parse/parseHourFromUtc.test.ts �[2m(�[22m�[2m13 tests�[22m�[2m)�[22m�[32m 7�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/plain/interval/intervalContainsDateTime.test.ts �[2m(�[22m�[2m41 tests�[22m�[2m)�[22m�[32m 8�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/plain/interval/intervalAbutsTime.test.ts �[2m(�[22m�[2m30 tests�[22m�[2m)�[22m�[32m 7�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/internal/calendarDatePairPolicy.test.ts �[2m(�[22m�[2m9 tests�[22m�[2m)�[22m�[32m 7�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/precision/convert/toNanoseconds.test.ts �[2m(�[22m�[2m68 tests�[22m�[2m)�[22m�[32m 8�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/plain/interval/intervalSplitAtDate.test.ts �[2m(�[22m�[2m16 tests�[22m�[2m)�[22m�[32m 8�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/plain/interval/intervalEngulfsDate.test.ts �[2m(�[22m�[2m29 tests�[22m�[2m)�[22m�[32m 8�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/plain/interval/intervalsOverlapTime.test.ts �[2m(�[22m�[2m31 tests�[22m�[2m)�[22m�[32m 8�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/internal/instantNanoseconds.test.ts �[2m(�[22m�[2m50 tests�[22m�[2m)�[22m�[32m 7�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/plain/interval/intervalOverlappingDaysDateTime.test.ts �[2m(�[22m�[2m24 tests�[22m�[2m)�[22m�[32m 7�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/zoned/calculate/sortZoned.test.ts �[2m(�[22m�[2m4 tests�[22m�[2m)�[22m�[32m 8�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/plain/calculate/cycleDate.test.ts �[2m(�[22m�[2m48 tests�[22m�[2m)�[22m�[32m 7�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/internal/temporalCompat/capabilities.test.ts �[2m(�[22m�[2m115 tests�[22m�[2m)�[22m�[32m 9�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/plain/interval/mergeIntervalsDateTime.test.ts �[2m(�[22m�[2m15 tests�[22m�[2m)�[22m�[32m 7�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/utc/calculate/minUtc.test.ts �[2m(�[22m�[2m6 tests�[22m�[2m)�[22m�[32m 5�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/utc/convert/convertUtcToPlainDateTime.test.ts �[2m(�[22m�[2m9 tests�[22m�[2m)�[22m�[32m 7�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/plain/calculate/getWeekOfMonth.test.ts �[2m(�[22m�[2m60 tests�[22m�[2m)�[22m�[32m 8�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/utc/parse/parseNanosecondFromUtc.test.ts �[2m(�[22m�[2m13 tests�[22m�[2m)�[22m�[32m 7�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/plain/interval/intervalXorTime.test.ts �[2m(�[22m�[2m22 tests�[22m�[2m)�[22m�[32m 7�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/internal/intervalNanoseconds.test.ts �[2m(�[22m�[2m33 tests�[22m�[2m)�[22m�[32m 6�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/plain/chop/chopSeconds.test.ts �[2m(�[22m�[2m22 tests�[22m�[2m)�[22m�[32m 6�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/plain/calculate/getLocaleEndOfWeek.test.ts �[2m(�[22m�[2m50 tests�[22m�[2m)�[22m�[32m 7�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/plain/calculate/endOfDate.test.ts �[2m(�[22m�[2m48 tests�[22m�[2m)�[22m�[32m 8�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/zoned/compare/isBetweenZoned.test.ts �[2m(�[22m�[2m17 tests�[22m�[2m)�[22m�[32m 6�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/plain/calculate/getLocaleStartOfWeek.test.ts �[2m(�[22m�[2m50 tests�[22m�[2m)�[22m�[32m 9�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/interval/validate/isValidInterval.test.ts �[2m(�[22m�[2m48 tests�[22m�[2m)�[22m�[32m 7�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/utc/parse/parseMonthFromUtc.test.ts �[2m(�[22m�[2m12 tests�[22m�[2m)�[22m�[32m 7�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/plain/calculate/addTime.test.ts �[2m(�[22m�[2m27 tests�[22m�[2m)�[22m�[32m 7�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/unix/parse/parseSecondFromUnix.test.ts �[2m(�[22m�[2m18 tests�[22m�[2m)�[22m�[32m 6�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/internal/dateUnitHelpers.test.ts �[2m(�[22m�[2m34 tests�[22m�[2m)�[22m�[32m 7�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/plain/interval/intervalDifferenceDateTime.test.ts �[2m(�[22m�[2m23 tests�[22m�[2m)�[22m�[32m 6�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/zoned/parse/parseRfc3339.test.ts �[2m(�[22m�[2m14 tests�[22m�[2m)�[22m�[32m 6�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/plain/parse/parseUnitFromTime.test.ts �[2m(�[22m�[2m29 tests�[22m�[2m)�[22m�[32m 6�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/utc/parse/parseMillisecondFromUtc.test.ts �[2m(�[22m�[2m12 tests�[22m�[2m)�[22m�[32m 7�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/utc/parse/parseDayOfWeekFromUtc.test.ts �[2m(�[22m�[2m12 tests�[22m�[2m)�[22m�[32m 7�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/precision/convert/toDotNetTicks.test.ts �[2m(�[22m�[2m54 tests�[22m�[2m)�[22m�[32m 6�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/plain/interval/intervalIntersectionDateTime.test.ts �[2m(�[22m�[2m24 tests�[22m�[2m)�[22m�[32m 7�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/utc/parse/parseYearFromUtc.test.ts �[2m(�[22m�[2m13 tests�[22m�[2m)�[22m�[32m 7�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/utc/parse/parseMinuteFromUtc.test.ts �[2m(�[22m�[2m12 tests�[22m�[2m)�[22m�[32m 8�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/calendar/calculate/getIsoWeekDate.test.ts �[2m(�[22m�[2m38 tests�[22m�[2m)�[22m�[32m 7�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/precision/convert/toPgMicroseconds.test.ts �[2m(�[22m�[2m53 tests�[22m�[2m)�[22m�[32m 6�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/precision/convert/toNtpTimestamp.test.ts �[2m(�[22m�[2m50 tests�[22m�[2m)�[22m�[32m 6�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/zoned/get/getSystemTimeZone.test.ts �[2m(�[22m�[2m21 tests�[22m�[2m)�[22m�[32m 7�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/unix/parse/parseNanosecondFromUnix.test.ts �[2m(�[22m�[2m18 tests�[22m�[2m)�[22m�[32m 6�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/precision/convert/toFileTime.test.ts �[2m(�[22m�[2m58 tests�[22m�[2m)�[22m�[32m 7�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/plain/calculate/previousWeekday.test.ts �[2m(�[22m�[2m26 tests�[22m�[2m)�[22m�[32m 7�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/zoned/parse/parseMicrosecondFromZoned.test.ts �[2m(�[22m�[2m4 tests�[22m�[2m)�[22m�[32m 5�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/utc/interval/intervalAbutsUtc.test.ts �[2m(�[22m�[2m34 tests�[22m�[2m)�[22m�[32m 6�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/plain/calculate/endOfQuarterForDate.test.ts �[2m(�[22m�[2m22 tests�[22m�[2m)�[22m�[32m 7�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/internal/measureNearRangeEnd.test.ts �[2m(�[22m�[2m6 tests�[22m�[2m)�[22m�[32m 6�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/zoned/parse/parseDateTimeFromZoned.test.ts �[2m(�[22m�[2m28 tests�[22m�[2m)�[22m�[32m 7�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/plain/interval/intervalXorDateTime.test.ts �[2m(�[22m�[2m21 tests�[22m�[2m)�[22m�[32m 7�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/duration/validate/isValidDuration.test.ts �[2m(�[22m�[2m56 tests�[22m�[2m)�[22m�[32m 5�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/plain/parse/parseNanosecondFromTime.test.ts �[2m(�[22m�[2m7 tests�[22m�[2m)�[22m�[32m 5�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/plain/calculate/startOfDate.test.ts �[2m(�[22m�[2m42 tests�[22m�[2m)�[22m�[32m 7�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/plain/interval/intervalEngulfsTime.test.ts �[2m(�[22m�[2m25 tests�[22m�[2m)�[22m�[32m 7�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/internal/joinDateTimeConnector.test.ts �[2m(�[22m�[2m11 tests�[22m�[2m)�[22m�[32m 7�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/plain/calculate/nextWeekday.test.ts �[2m(�[22m�[2m26 tests�[22m�[2m)�[22m�[32m 6�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/regex/calendar-zoned-date-time.test.ts �[2m(�[22m�[2m38 tests�[22m�[2m)�[22m�[32m 6�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/plain/parse/parseMicrosecondFromTime.test.ts �[2m(�[22m�[2m6 tests�[22m�[2m)�[22m�[32m 5�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/plain/calculate/setTime.test.ts �[2m(�[22m�[2m22 tests�[22m�[2m)�[22m�[32m 6�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/plain/interval/intervalAbutsDateTime.test.ts �[2m(�[22m�[2m29 tests�[22m�[2m)�[22m�[32m 7�[2mms�[22m�[39m
packages/gmt test:  �[32m✓�[39m src/plain/c…
…251)

It held four stale validate logs, about 400 KB of build output, one of them a run
that never finished. The directory was untracked but not ignored, so `git add -A`
would have swept all of it into a commit.
`@fontsource/michroma` supplied one woff2 that has been committed under
apps/dox/public/fonts/ since #161, and no script installs from the package;
its provenance moves into the @font-face comment in gmt-tokens.css, which is
where anyone changing the font will look. `@open-wc/testing` is imported
nowhere at all.

The lockfile is regenerated with them, which prunes 1,404 lines of transitive
entries. That regeneration is not optional: `pnpm install --frozen-lockfile`
runs in five CI jobs and fails on any manifest and lockfile mismatch.
…es (CORE-8, #251)

Re-queried all twelve filings on 2026-09-23: nothing has moved since the 21st.

The B and E adaptations were marked drafted but unverified. They are now checked
against tc39's current polyfill/lib/ecmascript.mjs rather than assumed to match:
the loop B patches is verbatim at line 2444, MathMin is imported at 52, MS_MAX at
129, and the BEFORE_FIRST_DST line E replaces at 143. Both drafts state plainly
that neither has been run against tc39's own test suite.
…ish (CORE-8, #251)

The six polyfill PRs closed on 2026-09-21 made the old `/upstream/` page read as six
rejections. None was a rejection: every one was routed to the standard first, to keep the
polyfill's rebase manageable. A tracker whose spine is our filings cannot say that, so the
page is rebuilt on the defect instead.

What the page now says, and how it knows:

- One row per canary group, with `Still needed?` read from `temporal-compat.json`. That file
  is written by `pnpm compat:snapshot` from the same probes `pnpm compat` runs, and
  `validate` fails when the committed copy drifts, so no figure on the page is typed by a
  human. 14 groups, 81 probes, 13 still needed.
- The filings table stays, demoted to what we sent and what happened to it, where a closed
  row carries the maintainer's own reason. `upstream.mjs check` now fails on a closed and
  unmerged filing with no `outcome`, because a bare `Closed` is the misleading case.
- No pipeline stage is asserted anywhere. Commit presence is unreliable (tc39 squash-merges,
  so `5dd0b0d97ee1` reports diverged against `main` although its code is there) and a
  hand-written stage rots silently, so the page carries only what regenerates itself.

Written to ISO 24495-1: the opening answers the reader's own question instead of burying it
four sections down, `rebase` and `routed` are gone, and the twelve filing descriptions are
split so the symptom comes before the cause. Average prose sentence is 12.6 words.

Bugs fixed on the way:

- The defect table was unreadable. Starlight sets `display: block` on content tables so wide
  ones scroll, and a block box is not a table box, so `table-layout: fixed` and `width: 100%`
  never reached the columns; with `gmt-content.css` also setting `white-space: nowrap` on
  every first cell, column one grew to 862px inside a 612px page. The styles move to
  `gmt-upstream.css` because an Astro scoped style compiles to `:where(.astro-hash)`, which
  carries no specificity and therefore cannot override a site rule at all.
- `apps/dox` had no `testTimeout`, so it ran on Vitest's 5s default while several of its
  tests are import-bound at ~3.9s. Under `pnpm -r run test` those crossed 5s and failed
  `validate`. Now 20s, five times the measured cost.
- `@fontsource/michroma` was a dependency nothing imported; its one `.woff2` has been
  committed since #161. Removed, with the provenance kept in `gmt-tokens.css`.

Also reduces the complexity fallow reports on the files this branch touches: every function
over 60 lines is split, including `runTwin` (271 lines) into one function per scan. Verified
by oracle against Chromium 153 before and after, 409,552 comparisons, 0 mismatches.
@craig-o-curtis

Copy link
Copy Markdown
Contributor Author

Two corrections, and the three questions I never answered

1. The D11 filing story in my last comment is wrong

I wrote that @js-temporal/polyfill had not ported the fix on 0.5.1 or on main, that no js-temporal issue mentioned it, and that it therefore needed a fresh port-and-release request.

The port already existed. ptomato's own open PR js-temporal/temporal-polyfill#361 carries it as commit 50d66d2, opened in April and unreviewed since. I had checked only our own filings 367–372 for NudgeToCalendarUnit, and never looked at the maintainers' own open PRs.

So nothing of ours was filed, and no tc39 filing is needed either — tc39 #3168 is already fixed there by #3172. What #361 lacked was evidence that the normative commit inside it fixes a wrong answer users hit today. That is what we added, as a comment on #361: the reproducer, the Chromium 153 scan, and before/after measurements of the commit applied to main.

The upstream tracker records this as a twelfth entry with a role field marking it as someone else's PR, so a maintainer's work is never counted among fixes we sent. The page headline now reads 11 reports and fixes sent, 6 of them as ready-to-merge code changes, plus evidence added to one more.

D11 itself is unchanged. 50d66d2 is not on main and is in no release, so every workaround stays and pnpm compat still reports 6 of 6 D11 probes failing. The removal trigger is now two gates: the commit landing on main, then shipping in a release.

2. Your three questions were answered in the code, but never back to you

All three landed in 44447b7. My reply covered the blocking findings and the docs list and skipped this section entirely, which from your side will have looked like they were dropped.

Question Where it was answered
mapDatesInRange vs half-open interval* .changeset/half-open-positional-intervals.md, last Migration bullet — your exact example, plus the bridge in both directions
parseHttp and asctime-date posture parseHttp.ts JSDoc: RFC 9110 §5.6.7 gives all three forms RFC 5322's day-of-week semantics, so rejection applies to asctime-date too. Accepting a mismatch would mean picking which field to believe, and RFC 9110 gives no rule for that. Test rows cover all three forms side by side
gregorian alias asymmetry JSDoc claim corrected in convertDateToCalendar.ts — two mechanisms, not one: gregorian is nine letters and fails the 8-character type subtag limit, while islamic-tabular and taiwan are absent from AvailableCanonicalCalendars. Recorded as §G in the polyfill-bugs doc and Q5 in calendar-standards-decisions.md, with a dated owner decision

You were also right about the half of the first question I had missed. The migration note explained the clash, but mapDatesInRange and mapZonedDatesInRange JSDoc still said only "inclusive" and never named the interval* convention, so the generated reference inherited that gap. Both now carry it, and the reference is regenerated.

Closing that gap found another instance of the class you flagged. The migration bullet told readers to call subDate(end, { days: 1 }). There is no subDate — the export is subtractDate. Same failure mode as the isValidInstant claim you caught in tall-moons-tickle.md: changesets ship verbatim to the CHANGELOG and api-surface does not cover .changeset/*.md. I swept all 37 pending changesets for names that look like our API but are not exported; subDate was the only real hit, and it is fixed.

Two test-row labels still used the "not a calendar id" phrasing you objected to. taiwan and islamic-tabular now say "CLDR alias absent from AvailableCanonicalCalendars", which is the actual reason and distinguishes them from gregorian's length failure.

3. Current numbers

The branch has moved since my last comment, so the count I gave is stale. packages/gmt is now 36,348 tests, not 36,349. A formatRfc3339 test that re-derived an instant with native Date duplicated the table two lines above it, which already pins the exact output string, so it was removed rather than kept for a banned API. stats.mjs caught the drift on the next check and both READMEs are resynced — the rules you asked for doing their job.

That came out of a sweep removing every native Date call from the repo. The eight production calls were all in apps/dox/worker, and removing them exposed a real bug: allowanceSpent picked the soonest quota reset with a bare .sort(), which is only chronological because toISOString is fixed width. nextMidnightMs returns nowMs unchanged when its zone lookup fails, so a stray subsecond can reach that list, and …T00:00:00.500Z sorts ahead of …T00:00:00Z. It now uses sortUtc, which compares instants.

The sweep also found that none of the four lint plugins had ever linted this repo. oxlint.config.js failed to load on five separate counts — wrong filename for auto-discovery, an array where an object is required, the wrong plugin field, a files key that does not exist, and Biome-style rule names — and packages/gmt resolved oxlint@^1.0.0 to 1.59.0, which predates JS-config discovery. All three scopes now enforce the Date ban, verified by linting a deliberate violation rather than by trusting a green exit. One Date call remains in the repo: elapsed time against a file's mtimeMs.

…p a changeset line that recommends a function we never exported (CORE-8, #251)

The CORE-8 review asked whether `mapDatesInRange` staying end-inclusive while `interval*` went half-open deserved a line in the migration notes. It got one, and that answered half the question: the changeset explains the clash, but the JSDoc on `mapDatesInRange` and `mapZonedDatesInRange` said only inclusive and never named the `interval*` convention, so a caller reading the generated reference alone could not see it. Both now carry the rule, the direction it differs in, and the one-day bridge between the two conventions. The reference regenerates two pages from it.

Writing that bridge exposed a defect in the migration bullet it came from. The bullet told readers to call `subDate(end, { days: 1 })`; the export is `subtractDate` and `subDate` has never existed. It is the same failure mode the review caught in `tall-moons-tickle.md`, and it fails the same way: changesets ship verbatim into `CHANGELOG.md` and `api-surface` does not read `.changeset/*.md`, so nothing in `validate` was ever going to see it. All 37 pending changesets were swept for names that look like GMT API but resolve to nothing in the built `dist`. `subDate` was the only real hit. The other candidates were Temporal and JavaScript built-ins quoted legitimately, or, in `lint-messages-name-real-functions.md`, the wrong name quoted as the thing being corrected.

Two test-row labels still called a CLDR alias not a calendar id, which the review pointed out is not what it is. `taiwan` and `islamic-tabular` now say they are CLDR aliases absent from ECMA-402's `AvailableCanonicalCalendars`, which is the actual reason and keeps them distinct from `gregorian`, whose nine letters fail the eight-character limit on a Unicode locale-extension type subtag. `convertZonedToCalendar.test.ts` takes the wording its siblings already use.

`stats check` then failed: published 36,349 tests against 36,348 derived. Removing a redundant `formatRfc3339` test in the previous commit moved the count, and the rule that catches that drift is one this review asked for. `stats sync` brings both READMEs and `gmt-stats.json` back in line, and the competitor multipliers recompute with it.

`packages/gmt` 36,348 tests and `apps/dox` 4,268 pass, lint and typecheck are clean in all three scopes, and `stats`, `api-surface`, `test-markers`, `upstream` and `deps` all pass. The `api-surface` skip budget is unchanged at 164 examples and 64 documented results, so the new prose added no skipped rows.
Route changes painted the new page in pieces. Three causes, all fixed:

- The theme script set innerHTML on an SVG while the page was still
  parsing. In Chrome that cancels the rel=expect render block, so the
  page painted before it had finished loading. The icon is now built
  with DOM calls.
- Expressive Code linked its stylesheet inside the body. Everything
  after the first code block stayed blank until it loaded. Its styles
  are now inlined.
- Pages now use cross-document view transitions. The header and
  sidebar swap in place, and only the content fades. Firefox and
  reduced-motion users get a plain cut.

React goes to 19.3. On /dox, the widget rail slides in and out, and the
first message fades the empty state into the transcript, using the new
ViewTransition component.

From 72rem up, the layout now follows react.dev. The content column is
50rem wide and centred, the On this page sidebar has a fixed width
instead of taking half the spare space, and the search box lines up
with the content column at the same width.
@baldurpan

Copy link
Copy Markdown
Contributor

Review — CORE-8, second pass (0a497bc7..daebdde, 16 commits)

The D11 work is correct, and I could prove it independently: Chrome 153 ships native Temporal, so I ran GMT's built dist against it rather than against the polyfill GMT patches.

Independently reproduced. pnpm validate green end to end: 36,348 packages/gmt tests, 97 lint-plugin, 4,269 dox, api-surface 0 failing with the skip budget at exactly { examples: 164, docResults: 64 }, stats check matching, upstream check at 12 filings, compat reporting D11 6 of 6.

D11 against a third engine: 58,968 rows, 0 mismatches. Duration#round via normalizeDuration, Duration#total via intervalLengthDateTime, and until/since via diffDateTime and diffDate — every start day 27–31 across all twelve months of 2024, fifteen offsets, both calendar units, all nine rounding modes, measured forwards and backwards. The same matrix run against the raw polyfill gives 68 mismatches against Chrome, so the scan is not vacuous; GMT closes all of them.

The two narrow gates are correct, not holes. I went after plainUntilWithRounding's compare(one, two) <= 0 and plainDateUntil's start.day >= 29 on the theory they excluded reachable cases. 17,280 more rows anchored on the 29th, 30th and 31st, going backwards, with sub-month remainders: GMT matches Chrome everywhere, and so does the raw polyfill. The defect genuinely does not reach those regions.

The new tests gate. Stubbing isNudgeWindowCompatNeeded() to false turns 49 tests red across 2 files. Reverting just the progress === 0n branch turns 5 red.

Also reproduced: locale: [] now agrees with undefined and "en-US" under en_US, fr_FR, ja_JP and de_DE, and the whole suite passes under LANG=ja_JP + TZ=Asia/Tokyo · startOfQuarterForDateTime resets all three sub-second fields · the null-option rule holds on every new path, and the 11 remaining ?? sites are boolean members where ToBoolean(null) is false, so ?? is right there · the Date ban fires in all three lint scopes against a planted violation · README multipliers are arithmetically correct (1,090,440 / 20,190 = 54.0; 36,348 / 386 = 94.2) · no pending changeset names a function we do not export — the three call-shaped names left are Intl.Locale#getWeekInfo, Temporal's startOfDay(), and convertUtcDateTimeToUnix quoted as the name that never existed · llms-full.txt carries 4 unresolved expressions, all prose tokens, as claimed · the CI generator step is deterministic and both pages are gitignored · the dropped @fontsource/michroma is vendored into public/fonts/ with its provenance in the CSS · the new view transitions honour prefers-reduced-motion.


Findings

1. The /upstream/ page's numbers and defect table reach no text surface.

<UpstreamDefects /> ships as a raw tag in apps/dox/dist/upstream.md, in llms-full.txt and in retrieval-chunks.json. None of the 7 /upstream/ retrieval chunks contains a single defect row (D1–D11) or the filing counts; the HTML page has both. upstream.md is 5.4 KB of prose against 214 KB of HTML.

The prose reads as if the table is there. Line 21 says "the numbers are what those tests returned when this page was built", line 24 is the bare tag, line 26 says "Still needed? shows what GMT's own tests for that bug returned". A reader — or the Dox chat, which retrieves from these chunks — gets the framing of a table and none of its content. Ask the chat which polyfill bugs GMT works around and the corpus has no answer.

This is my earlier finding half-fixed. The {expr} class is genuinely gone and now tested. The component class has one live instance, and the gate that should catch it cannot:

// apps/dox/scripts/llms.test.ts:553
/<\/?(Mistake|Scenario|GridSection|ChartContainer|TimezoneMap|UpstreamTracker|Card|CardGrid|PlaygroundForm)\b/g

An allow-list of nine names. UpstreamDefects is not on it. <[A-Z][A-Za-z0-9]* finds it in one pass. The test also scans only llms-full.txt, while the expression test directly above it scans the .md files and the retrieval chunks too.

2. The no-throw blind spot is not closed — it is three values wider.

I added two values to your own GARBAGE set in test/noThrow.ts: an options object with a throwing getter, and a revoked Proxy. Your own harness then reports 221 of 556 exports failing.

formatUnix(0, "en-US", { get length() { throw new Error("boom"); } })  // throws Error
getZonedNow({ get length() { throw new Error("boom"); } })             // throws Error
addDate("2024-01-01", revokedProxy)      // throws TypeError: Cannot perform 'ownKeys'
clampInterval(revokedProxy, revokedProxy) // throws TypeError: Cannot perform 'get'
addBusinessDays("2024-01-01", 1, revokedProxy) // throws TypeError: Cannot perform 'IsArray'

In the ones I traced the cause is the same: the options or duration object is destructured or spread outside the try. formatUnix is the clearest — const { epochUnit, timeZone, includeTimeZoneName = false, ...intlOptions } = options ?? {} runs before the try, so the rest-spread's ownKeys and [[Get]] escape the guard. isValidDate and formatDate survive the same arguments, so it is not a property of the whole surface.

The methodological point is the one behind finding 1. { toString() { throw } }, Object.create(null) and Symbol() closed the three holes I happened to name. The set is still a hand-picked list, and a list cannot see what is not on it. A throwing getter is not exotic — a lazily-computed config object is one.

3. The Date ban: four more executable calls, and the carve-out's premise is false.

The claim is one remaining call. There are five. The declared one in scripts/upstream.mjs:424 is properly marked. The other four are not marked at all:

  • apps/dox/src/components/ai-elements/reasoning.tsx:92,95 — Date.now() twice, as a stopwatch
  • apps/dox/src/components/ai-elements/prompt-input.tsx:156,163 — new Date().toISOString() for a filename stamp, and Date.now() for lastModified

They survive because apps/dox/src/components/ai-elements/** is exempt in both oxlint.config.mts and date-ban.test.ts, justified identically: "vendored, unmodified shadcn/AI Elements registry source — reviewed as 'copied from upstream', not linted as our own."

That tree carries 15 locally added oxlint-disable pragmas naming this repo's own plugin stack (eslint-plugin-promise(avoid-new), eslint-plugin-unicorn(prefer-add-event-listener), and others across code-block.tsx and prompt-input.tsx). It is not unmodified. The exemption is doing more work than its stated reason supports.

prompt-input.tsx:156 is the one I would not leave: new Date().toISOString() builds a date value as text, which is exactly what the ban exists for, and it ships in the site that argues Date is the wrong tool. The two in reasoning.tsx are elapsed time, so by the repo's own standard (upstream.mjs, visual-snapshot.mjs) they want performance.now() and a date-ban: marker, not Date.now().

4. The plainDateUntil D11 seam is reachable, untested, and so far changes no answer.

I made the branch throw and ran the suite: zero hits across all 36,348 tests, all still green. It is reachable — diffDate("2024-01-31", "2024-03-30", ["months"], { smallestUnit: "months", roundingMode: "floor" }) enters it, and with the throw in place returns the sentinel instead of { months: 1 }.

Your D11 table has rows for Duration#total, Duration#round, until/since and Duration.compare. There is no diffDate row. Across the 11,124 diffDate rows I ran, the raw polyfill already matched Chrome on every one, so this seam currently routes around a defect that does not show up there. Either it needs a row proving it necessary, or it should go — right now it is the one D11 seam with neither a test nor a demonstrated mismatch behind it.


Minor

  • 38 pending changesets now, not the 37 swept.
  • The dox suite is 4,269 tests; the last figure given for it was 2,606.
  • compat reports D10 as REMOVABLE. That is the documented expected state — the group guards against a port of tc39 #3292 alone — so it is correct, just worth not mistaking for drift later.

What I did not check

The oracle covers calendar arithmetic, rounding and differences. It has no toLocaleString or DateTimeFormat comparisons, so the formatting surface rests on your own tests · I did not re-run the RFC 9557 or half-open tables, taking the earlier pass as still good since the code did not move · the view transitions and the widened content column I read rather than looked at, so the visual result is unverified · mergeIntervals* / intervalXorAll* past two elements I took from your 720-case oracle rather than rebuilding it · I did not exercise the Dox chat end to end, so finding 1's effect on answers is inferred from the corpus, not observed.

…render the defect table, lint the vendored tree, drop a dead D11 seam (CORE-8, #251)

The review filed four findings and named the shape behind two of them: an enumerated list standing in for a general rule, where what slipped through is what was not on the list. All four are real. Two are larger than reported, and a third hole turned up while closing the first.

**Hostile input returns the sentinel, not an exception.** 221 of the 551 exports threw when an argument resisted inspection. A revoked Proxy throws on get, ownKeys and Array.isArray; an object with a throwing getter throws when a rest-spread enumerates it. Those operations sit in the guards above each function's try, so the guard written to reject invalid input raised instead. Every affected export now evaluates its body inside the try whose catch already returned its sentinel. The reported cause covers part of it: the four spread-before-try sites and about 126 options-slot cases, but roughly 95 fail at non-options positions in functions that take no options at all. The count is 221 of 551 rather than 556, and it is carried by the revoked Proxy alone — the throwing getter accounts for four, all already inside that set. The harness now also feeds a proxy that throws on every trap, so the garbage set stops being a list of remembered shapes.

**The upstream defect table reached no text surface.** A raw UpstreamDefects tag shipped into dist/upstream.md, llms-full.txt and the retrieval chunks, so all seven upstream chunks carried zero defect rows while the HTML carried eleven, and the prose around it spoke of each row and the middle column over nothing. The gate that should have caught it matched nine component names — exactly the nine the renderer handles — so it mirrored the handler list instead of checking it. It now matches any capitalised tag, excluding TypeScript generics, across the same three surfaces its sibling scans. That general form found eight more unhandled components, including the home page's own case against Date, whose faults table had never reached the text surfaces either. A second tally was missing too: the sentence counting what we sent upstream moved into a component last round to escape the expression problem, which put it out of reach entirely.

**The vendored carve-out's premise was false.** Four unmarked Date calls lived under ai-elements, one of them a calendar value formatted into a user-visible filename on the site that argues Date is the wrong tool. The exemption called that tree vendored and unmodified. All fifteen oxlint pragmas naming this repo's own plugins were present in the vendoring commit itself, prompt-input.tsx was edited again afterwards with a LOCAL MODIFICATION marker, and the directory's own README says we re-theme every component there. The tree is now linted for Date while staying exempt from the style rules, the stopwatch takes a monotonic clock, and a test pins the three carve-out lists against each other. Only components/ui stays exempt; it is genuinely untouched.

**A D11 seam that corrected nothing is gone.** plainDateUntil routed ISO date differences through the nudge-window workaround whenever the start day was past the 28th. The defect cannot reach there: the window only misses its target when the target sits strictly between its bounds by a sub-day amount, and two PlainDates have no time component. Measured over about 1.96 million rows against the raw polyfill, in both directions and across every rounding mode and increment, the two paths never disagreed. Removing it costs no answer and withdraws GMT's own exact-boundary behaviour from ISO date differences. The changeset and the bug doc no longer claim a diffDate fix that was never scanned.

**And the gates that check any of this had never run in CI.** All three text-surface checks early-return when apps/dox/dist is absent, and the Tests job builds only @northguild/gmt — the site is built in a different job on a different runner. A skip reports as a pass, which is how the raw tag shipped green. Story consistency now builds the site and runs those checks, and outside a local run a missing dist fails loudly rather than passing silently.

packages/gmt 36,348 tests and apps/dox 4,283 pass, the no-throw harness is 556 of 556 with the widened set, api-surface reports 0 failing with the skip budget unchanged, and every script gate and all three lint scopes are clean.
@craig-o-curtis

Copy link
Copy Markdown
Contributor Author

All four confirmed and fixed — two were larger, and the gates behind them were not running

Thank you for the third-engine D11 scan. Running GMT's dist against Chrome's native Temporal rather than against the polyfill it patches is the check I could not do, and the 68-mismatch control is what makes it evidence rather than agreement.

You named the shape yourself: an enumerated list standing in for a general rule, and what slipped through is what wasn't on the list. That turned out to describe a third case too.

1. The defect table — confirmed, and there is a second dropped table

Fixed, and the gate is now general: any capitalised tag, across the same three surfaces the expression test scans, with a lookbehind so RoundingOptions<DateUnit> in a documented signature is not mistaken for one.

Two things you did not have. The nine names in that regex are exactly the nine renderMdxComponents handles, so it mirrored the handler list rather than checking it. And the general form finds eight more unhandled components — including WhyDateBug, the home page's own case against Date, whose faults table had never reached any text surface. Its data moved into data/date-faults.ts so both the component and the text pipeline render it.

The second dropped table is the tally: "we have sent 11 reports and fixes…" appeared in the HTML and nowhere else. That is a regression from last round — upstream-summary.ts moved the arithmetic into the component to escape the {expr} problem, which put it out of reach of the text surfaces entirely. upstream.md is now 8,357 bytes against 5,368.

2. Never-throw — 221 confirmed, attribution corrected

221 reproduces exactly. Three corrections:

  • It is 221 of 551 exports, not 556. 556 is the harness's test count.
  • The revoked Proxy carries all of it. The throwing getter accounts for 4 — formatUnix, formatUtc, formatZonedRange, getZonedNow — and all 4 are already inside the Proxy's set, so the union is still 221.
  • "Options destructured outside the try" explains those 4 and ~126 options-slot cases. The remaining ~95 fail at non-options positions, in functions with no options parameter at all — mergeCalendars@arg0, businessDaysBetween@arg2, rollDate@arg2 — dying in Array.isArray and string coercion. Breakdown: 148 get, 70 IsArray, 13 ownKeys, 4 boom.

All 221 fixed by moving each function's whole body inside the try whose catch already returned its sentinel. GARBAGE now also carries a proxy that throws on every trap — 153/551 on its own, a better general probe than either value, and the point is that it is hostile to any access rather than to one named operation.

3. The carve-out — confirmed, and false three ways

All four calls fixed. prompt-input.tsx:156 was the one that mattered and it now builds the stamp with GMT; the reasoning.tsx pair takes performance.now(), which is also the monotonic fix — as written, a clock adjustment mid-stream could have shown a negative duration.

Your evidence understates it. Beyond the 15 pragmas, all 15 were present in the vendoring commit itself (1c22cb1), prompt-input.tsx was edited again in 898e5f3 with an explicit LOCAL MODIFICATION (GMT) marker, and the directory's own README says "we own and re-theme every component here" with two Locally modified rows. There is also a third carve-out site you did not mention, in apps/dox/oxlint.config.mts.

ai-elements/** is now linted for Date while staying exempt from the style rules, and a test pins the three lists against each other — the comments already promised they matched, and nothing checked it. ui/** stays exempt: 18 files, no pragmas, no Date.

4. The plainDateUntil seam — confirmed, removed

Reachable, untested, changes no answer. I ran ~1.96M rows across two matrices — 40,800 through the public API and ~1.92M directly against plainDateUntilWithRounding vs start.until, both directions, negative offsets, increments 1–12, all 8 modes. Zero divergence.

The reason is structural, which is why no row will ever prove it necessary: the window can only miss its target when the target sits strictly between its bounds by a sub-day amount, and two PlainDates have no time component. Removed. Section H and the changeset no longer claim a diffDate fix that was never scanned.

It was not merely inert, as your instrumentation implied — nudgeWindowRetry.test.ts:128 records that GMT's own progress === 0n defect became reachable "through every calendar once the D11 gate routed ISO here too". The seam widened a GMT-owned bug's reach for no corrected answer.

The finding behind findings 1 and 2

Closing yours exposed a third instance. All three text-surface gates had never run in CI. Each early-returns when apps/dox/dist is missing, and the Tests job builds only @northguild/gmt — the site is built in a different job, on a different runner. A skip reports as a pass, so the suite was green either way. That is how the raw tag shipped, and my generalised gate would have rotted the same way.

Story consistency now builds the site and runs those checks, and outside a local run a missing dist fails loudly instead of passing silently.

Numbers, and one correction to yours

packages/gmt 36,348 · apps/dox 4,283 · no-throw 556/556 with the widened set · api-surface 0 failing, skip budget unchanged at { examples: 164, docResults: 64 } · every script gate and all three lint scopes clean.

It is 37 pending changesets, not 38 — now 39 with the two this adds. And you are right about D10: REMOVABLE is the documented expected state for that group, not drift.

…(CORE-8, #251)

The guard added in the previous commit failed whenever apps/dox/dist was absent under CI, which is every job. The Tests job runs the dox suite on three Node versions and has no reason to build the site, so all three failed while Story consistency — the one job that does build it — passed. The flag is now set only on that step. Also folds in pnpm format: the try/catch wrap indented 221 files by two spaces, pushing some lines past the print width.

@baldurpan baldurpan left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🐈

@craig-o-curtis
craig-o-curtis merged commit 729e0f1 into main Sep 23, 2026
38 checks passed
@craig-o-curtis
craig-o-curtis deleted the chore/251-core-8-final-review-with-gmt-reviewer-of-entire-api-pre-v1160 branch September 23, 2026 13:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working documentation Improvements or additions to documentation gmt

Projects

None yet

Development

Successfully merging this pull request may close these issues.

CORE-8 Final review with gmt-reviewer of entire API, pre-v1.16.0

2 participants