From 6f063bab7efda23a97a82a1a7cee79724e79f9fd Mon Sep 17 00:00:00 2001 From: Klink <85062+dogmar@users.noreply.github.com> Date: Sun, 14 Jun 2026 20:39:31 -0700 Subject: [PATCH] fix: detect PlainDate by calendarId in resolveFirstWeekSpec MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The PlainDate branch tested `"calendar" in spec`, but modern Temporal (native + @js-temporal/polyfill 0.5+) exposes `calendarId`, not `calendar`. So a PlainDate spec always fell through to the `{month, year, day}` branch — correct by accident for ISO dates, but a non-ISO-calendar PlainDate had its calendar fields (e.g. Buddhist year 2569) reinterpreted as ISO, landing ~543 years off. Detect via `calendarId` and convert non-ISO calendars to ISO with `withCalendar("iso8601")` before snapping (the snap is calendar-independent), keeping the ISO-only pipeline consistent. Co-Authored-By: Claude Opus 4.8 --- package/src/resolve-first-week.test.ts | 10 ++++++++++ package/src/resolve-first-week.ts | 12 +++++++++--- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/package/src/resolve-first-week.test.ts b/package/src/resolve-first-week.test.ts index 63928a3..4000a79 100644 --- a/package/src/resolve-first-week.test.ts +++ b/package/src/resolve-first-week.test.ts @@ -61,6 +61,16 @@ describe("resolveFirstWeekSpec", () => { const result = resolveFirstWeekSpec(spec, weekStartDay, T); expect(result.toString()).toBe(expected); }); + + it("converts a non-ISO-calendar PlainDate to ISO before snapping", () => { + // Buddhist 2026-03-15 (a Sunday) — its calendar fields read year 2569. + // Pre-fix the PlainDate fell through to the {month,year,day} branch and + // those fields were reinterpreted as ISO → ~543 years off. + const buddhist = T.PlainDate.from("2026-03-15").withCalendar("buddhist"); + const result = resolveFirstWeekSpec(buddhist, 0, T); + expect(result.toString()).toBe("2026-03-15"); + expect(result.calendarId).toBe("iso8601"); + }); }); describe("resolveFirstWeek", () => { diff --git a/package/src/resolve-first-week.ts b/package/src/resolve-first-week.ts index 4f00b20..7a6875f 100644 --- a/package/src/resolve-first-week.ts +++ b/package/src/resolve-first-week.ts @@ -40,9 +40,15 @@ export function resolveFirstWeekSpec( T: TemporalNamespace, timeZone?: string, ): Temporal.PlainDate { - // 1. PlainDate — Temporal objects have a "calendar" property - if ("calendar" in spec) { - return snapToWeekStart(spec as Temporal.PlainDate, weekStartDay); + // 1. Temporal.PlainDate — identified by its `calendarId` (modern Temporal + // and @js-temporal/polyfill 0.5+ expose `calendarId`, not `calendar`). + // Convert non-ISO calendars to ISO so the rest of the pipeline — which + // is ISO-only — stays consistent; the week snap itself is + // calendar-independent. + if ("calendarId" in spec) { + const pd = spec as Temporal.PlainDate; + const iso = pd.calendarId === "iso8601" ? pd : pd.withCalendar("iso8601"); + return snapToWeekStart(iso, weekStartDay); } // 2. Native Date