temporal-fmt 0.8.95 → 0.8.97

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/README.md +3 -1
  2. package/dist/calendarUtils.cjs +5 -0
  3. package/dist/calendarUtils.cjs.map +1 -1
  4. package/dist/calendarUtils.js +1 -1
  5. package/dist/{chunk-H5ZLOWP5.js → chunk-2R46FDWW.js} +3 -3
  6. package/dist/{chunk-J3YC66TB.js → chunk-3RLYULUQ.js} +29 -1
  7. package/dist/chunk-3RLYULUQ.js.map +1 -0
  8. package/dist/{chunk-ASLNQ4DG.js → chunk-7LAIYTPE.js} +6 -1
  9. package/dist/{chunk-ASLNQ4DG.js.map → chunk-7LAIYTPE.js.map} +1 -1
  10. package/dist/{chunk-Z34HC4X5.js → chunk-7YIFUVEV.js} +40 -34
  11. package/dist/chunk-7YIFUVEV.js.map +1 -0
  12. package/dist/{chunk-26H4NR3J.js → chunk-EA34SFOW.js} +10 -3
  13. package/dist/chunk-EA34SFOW.js.map +1 -0
  14. package/dist/{chunk-6GBHT4HH.js → chunk-EIJRFICK.js} +19 -4
  15. package/dist/chunk-EIJRFICK.js.map +1 -0
  16. package/dist/{chunk-6EB3FIZL.js → chunk-G7ADZLSD.js} +60 -21
  17. package/dist/chunk-G7ADZLSD.js.map +1 -0
  18. package/dist/{chunk-RHGGZMLN.js → chunk-OGVBMURH.js} +2 -2
  19. package/dist/{chunk-N3OPHFXP.js → chunk-PVJXJPHQ.js} +78 -7
  20. package/dist/chunk-PVJXJPHQ.js.map +1 -0
  21. package/dist/{chunk-C3755VAP.js → chunk-USONQ7MV.js} +37 -4
  22. package/dist/chunk-USONQ7MV.js.map +1 -0
  23. package/dist/{chunk-46IMIRAG.js → chunk-WVMHS4OX.js} +5 -6
  24. package/dist/{chunk-46IMIRAG.js.map → chunk-WVMHS4OX.js.map} +1 -1
  25. package/dist/{chunk-AC66SDAS.js → chunk-WZC43XGU.js} +38 -2
  26. package/dist/chunk-WZC43XGU.js.map +1 -0
  27. package/dist/{chunk-3ZXF3ELP.js → chunk-X64UUZ7M.js} +18 -3
  28. package/dist/chunk-X64UUZ7M.js.map +1 -0
  29. package/dist/{chunk-7SFQUOID.js → chunk-XIKLQMF7.js} +2 -2
  30. package/dist/chunk-XIKLQMF7.js.map +1 -0
  31. package/dist/{chunk-ETS7Q56I.js → chunk-XUQBJHG2.js} +11 -3
  32. package/dist/chunk-XUQBJHG2.js.map +1 -0
  33. package/dist/duration.cjs +41 -0
  34. package/dist/duration.cjs.map +1 -1
  35. package/dist/duration.js +4 -4
  36. package/dist/format.cjs +48 -0
  37. package/dist/format.cjs.map +1 -1
  38. package/dist/format.js +5 -5
  39. package/dist/index.cjs +488 -81
  40. package/dist/index.cjs.map +1 -1
  41. package/dist/index.js +185 -39
  42. package/dist/index.js.map +1 -1
  43. package/dist/interval.cjs +49 -2
  44. package/dist/interval.cjs.map +1 -1
  45. package/dist/interval.js +8 -8
  46. package/dist/localeRegistry.cjs +62 -0
  47. package/dist/localeRegistry.cjs.map +1 -1
  48. package/dist/localeRegistry.js +3 -3
  49. package/dist/parse.cjs +129 -5
  50. package/dist/parse.cjs.map +1 -1
  51. package/dist/parse.js +5 -5
  52. package/dist/recurrence.cjs +93 -18
  53. package/dist/recurrence.cjs.map +1 -1
  54. package/dist/recurrence.js +4 -4
  55. package/dist/relativeTime.cjs.map +1 -1
  56. package/dist/relativeTime.js +5 -5
  57. package/dist/timezone.cjs +74 -32
  58. package/dist/timezone.cjs.map +1 -1
  59. package/dist/timezone.js +3 -3
  60. package/package.json +4 -4
  61. package/dist/chunk-26H4NR3J.js.map +0 -1
  62. package/dist/chunk-3ZXF3ELP.js.map +0 -1
  63. package/dist/chunk-6EB3FIZL.js.map +0 -1
  64. package/dist/chunk-6GBHT4HH.js.map +0 -1
  65. package/dist/chunk-7SFQUOID.js.map +0 -1
  66. package/dist/chunk-AC66SDAS.js.map +0 -1
  67. package/dist/chunk-C3755VAP.js.map +0 -1
  68. package/dist/chunk-ETS7Q56I.js.map +0 -1
  69. package/dist/chunk-J3YC66TB.js.map +0 -1
  70. package/dist/chunk-N3OPHFXP.js.map +0 -1
  71. package/dist/chunk-Z34HC4X5.js.map +0 -1
  72. /package/dist/{chunk-H5ZLOWP5.js.map → chunk-2R46FDWW.js.map} +0 -0
  73. /package/dist/{chunk-RHGGZMLN.js.map → chunk-OGVBMURH.js.map} +0 -0
package/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # temporal-fmt 🥶🔥
2
2
 
3
+ ![coverage](https://img.shields.io/badge/coverage-100%25%20(c8)-brightgreen?style=flat-square)
4
+
3
5
  Format `Temporal.PlainDate` / `PlainTime` / `PlainDateTime` / `ZonedDateTime` objects
4
6
  using date-fns-style token strings.
5
7
 
@@ -979,7 +981,7 @@ Neither of these ships as part of this repository — separate packages, install
979
981
 
980
982
  ## Testing
981
983
 
982
- This library is heavily tested. The `node:test` suite (`test/*.test.js`) runs 800+ cases covering hand-picked scenarios, fuzzing, and adversarial input, alongside a separate `vitest/` suite unit-testing internals directly. On top of that there's a dedicated conformance suite, smoke tests that check the package actually resolves correctly under CJS/ESM/bundler/nodenext, and type tests. If it's mentioned in this README, it's backed by a test — not just a docstring.
984
+ This library is heavily tested. The `node:test` suite (`test/*.test.js`) runs 1200+ cases covering hand-picked scenarios, fuzzing, and adversarial input, alongside a separate `vitest/` suite unit-testing internals directly. On top of that there's a dedicated conformance suite, smoke tests that check the package actually resolves correctly under CJS/ESM/bundler/nodenext, and type tests. If it's mentioned in this README, it's backed by a test — not just a docstring.
983
985
 
984
986
  ## Contributing
985
987
 
@@ -155,7 +155,12 @@ function touchesTime(unit) {
155
155
  }
156
156
  function recomputeDayOfWeek(view) {
157
157
  if (typeof view.dayOfWeek !== "number") return;
158
+ /* c8 ignore start @preserve -- unreachable: startOf()/endOf() both call
159
+ * asDateFieldView() before this, which already throws if year/month/day
160
+ * aren't all numbers — so by the time a value with a numeric dayOfWeek
161
+ * reaches here, year/month/day are guaranteed present too. */
158
162
  if (typeof view.year !== "number" || typeof view.month !== "number" || typeof view.day !== "number") return;
163
+ /* c8 ignore stop @preserve */
159
164
  const jsDow = new Date(Date.UTC(view.year, view.month - 1, view.day)).getUTCDay();
160
165
  view.dayOfWeek = jsDow === 0 ? 7 : jsDow;
161
166
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/calendarUtils.ts","../src/isoWeek.ts"],"sourcesContent":["// Calendar utility helpers (plan section L). These are pure functions\n// over the TemporalLike shape — no Temporal namespace needed, same\n// approach as isoWeek.ts and the field-reading helpers in format.ts.\n// Letting callers compute dayOfYear/weekOfYear/etc. without going\n// through format() means they can build their own derived values\n// without committing to a string format.\n//\n// Calendar-sensitivity: the helpers in this module assume the\n// iso8601 (Gregorian) calendar — that's what TemporalLike fields\n// carry for the overwhelming majority of callers. Non-Gregorian\n// calendars (hebrew, islamic, etc.) need their own helpers; this\n// module doesn't try to be calendar-polymorphic the way Temporal\n// itself is. Documented limitation, not a design choice — see\n// VERIFICATION.md for the rationale.\n\nimport { isGregorianLeapYear, dayOfYear, isoWeekYearAndWeek } from './isoWeek.js';\n\n// A subset of TemporalLike that has the date fields these helpers need,\n// plus optional time fields. PlainTime isn't a DateFieldView (no\n// year/month/day), but PlainDateTime / ZonedDateTime / PlainDate all\n// match. Time fields are optional so callers can pass a PlainDate\n// to startOf(value, 'month') without having to populate hour/minute/etc.\n// Exported so the comparison/arithmetic modules can use the same\n// narrowing.\nexport interface DateFieldView {\n year?: number;\n month?: number;\n day?: number;\n hour?: number;\n minute?: number;\n second?: number;\n millisecond?: number;\n dayOfWeek?: number;\n calendarId?: string;\n}\n\nfunction requireFields(view: DateFieldView, fields: Array<keyof DateFieldView>): void {\n for (const f of fields) {\n if (typeof view[f] !== 'number') {\n throw new Error(\n `temporal-fmt: calendar helper requires \"${String(f)}\", which this value doesn't have. ` +\n `Pass a Temporal.PlainDate / PlainDateTime / ZonedDateTime.`\n );\n }\n }\n}\n\nexport function daysInMonth(view: DateFieldView): number {\n requireFields(view, ['year', 'month']);\n const { year, month } = view;\n // Standard Gregorian month lengths. February's length depends on\n // whether `year` is a leap year — the same isGregorianLeapYear check\n // isoWeek.ts uses for dayOfYear arithmetic.\n const LENGTHS = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];\n if (month === 2 && isGregorianLeapYear(year!)) return 29;\n return LENGTHS[(month! - 1)!]!;\n}\n\nexport function daysInYear(view: DateFieldView): 365 | 366 {\n requireFields(view, ['year']);\n return isGregorianLeapYear(view.year!) ? 366 : 365;\n}\n\nexport function monthsInYear(_view: DateFieldView): 12 {\n // Gregorian always has 12 months. Other calendars (Hebrew leap years\n // have 13) need calendar-aware logic this module doesn't carry — see\n // the file-level comment. The `_view` parameter is kept so the\n // signature mirrors the other helpers and a future calendar-aware\n // implementation can use it without changing call sites.\n return 12;\n}\n\nexport function isLeapYear(view: DateFieldView): boolean {\n requireFields(view, ['year']);\n return isGregorianLeapYear(view.year!);\n}\n\n// `isLeapMonth` would require knowing which month of a leap-year-aware\n// calendar is the leap month — Gregorian doesn't have one, so this\n// returns false unconditionally. Kept here so the public surface\n// matches the plan's section L listing; non-Gregorian calendars need\n// a different implementation.\nexport function isLeapMonth(_view: DateFieldView): boolean {\n return false;\n}\n\nexport function dayOfYearHelper(view: DateFieldView): number {\n requireFields(view, ['year', 'month', 'day']);\n return dayOfYear(view.year!, view.month!, view.day!);\n}\n\n// ISO 8601 week and week-year. Delegates to isoWeek.ts's\n// isoWeekYearAndWeek, which does the full Thursday-of-week\n// computation to handle the year-boundary cases (Dec 29-31 belonging\n// to week 1 of next year, Jan 1-3 belonging to week 52/53 of the\n// previous year).\nexport function weekOfYear(view: DateFieldView): number {\n requireFields(view, ['year', 'month', 'day', 'dayOfWeek']);\n return isoWeekYearAndWeek(view.year!, view.month!, view.day!, view.dayOfWeek!).week;\n}\n\nexport function weekYear(view: DateFieldView): number {\n requireFields(view, ['year', 'month', 'day', 'dayOfWeek']);\n return isoWeekYearAndWeek(view.year!, view.month!, view.day!, view.dayOfWeek!).isoYear;\n}\n\n/**\n * Fiscal-quarter options. `startMonth` is the calendar month (1-12) the\n * fiscal year begins on — e.g. `7` for a fiscal year starting in July.\n * Omitted or `1` gives the calendar-quarter behavior getQuarter() has\n * always had (Jan-Mar = Q1, etc.), so existing callers passing nothing\n * see no change.\n */\nexport interface QuarterOptions {\n startMonth?: number;\n}\n\nfunction validateStartMonth(startMonth: number): void {\n if (!Number.isInteger(startMonth) || startMonth < 1 || startMonth > 12) {\n throw new Error(\n `temporal-fmt: getQuarter's startMonth must be an integer from 1 to 12 (got ${startMonth}).`\n );\n }\n}\n\nexport function getQuarter(view: DateFieldView, options: QuarterOptions = {}): number {\n requireFields(view, ['month']);\n const startMonth = options.startMonth ?? 1;\n validateStartMonth(startMonth);\n if (startMonth === 1) {\n // Mirrors the Q token: months 1-3 → Q1, 4-6 → Q2, 7-9 → Q3, 10-12 → Q4.\n return Math.ceil(view.month! / 3);\n }\n // Fiscal case: shift the month so startMonth becomes month 1 of the\n // fiscal year (mod 12, 1-indexed), then apply the same ceil(/3) rule.\n // E.g. startMonth=7 (fiscal year starts July): July→1, Aug→2, ...,\n // Dec→6, Jan→7, ..., June→12. Then Q1 = fiscal months 1-3 (Jul-Sep),\n // matching the common \"FY starts in July\" convention where Q1 is the\n // first quarter of the fiscal year, not a quarter numbered by which\n // calendar quarter it falls in.\n const shifted = ((view.month! - startMonth + 12) % 12) + 1;\n return Math.ceil(shifted / 3);\n}\n\n// `getMonth` / `getWeekday` look trivial (just read the field) but the\n// plan's section L lists them explicitly, so they're here for surface\n// completeness. They also normalize: getWeekday returns 1-7 (Mon-Sun,\n// matching Temporal's spec) regardless of what numbering the caller's\n// underlying value uses.\nexport function getMonth(view: DateFieldView): number {\n requireFields(view, ['month']);\n return view.month!;\n}\n\nexport function getWeekday(view: DateFieldView): number {\n requireFields(view, ['dayOfWeek']);\n return view.dayOfWeek!;\n}\n\n// startOf / endOf return new field bags (not Temporal objects — this\n// module is polyfill-free) with the relevant fields zeroed/extended.\n// Callers can pass the result to a Temporal constructor if they want\n// a typed value.\nexport type StartOfUnit = 'day' | 'month' | 'year' | 'hour' | 'minute' | 'second';\n\n// Returns true if the given unit (when used with startOf/endOf) should\n// also touch the time fields. 'day', 'month', 'year' all imply a\n// resolution coarser than an hour, so startOf zeroes the time fields\n// and endOf maxes them. Sub-hour units (hour/minute/second) only touch\n// the fields finer than themselves.\nfunction touchesTime(unit: StartOfUnit): boolean {\n return unit === 'day' || unit === 'month' || unit === 'year';\n}\n\n// startOf/endOf reassign year/month/day, which invalidates any\n// dayOfWeek carried over from the input — a plain { ...view } spread\n// leaves the old value sitting there unchanged. Same failure mode\n// businessCalendar.ts's isBusinessDay() works around for add(); we\n// recompute here rather than trust the copied field.\nfunction recomputeDayOfWeek(view: DateFieldView): void {\n if (typeof view.dayOfWeek !== 'number') return;\n if (typeof view.year !== 'number' || typeof view.month !== 'number' || typeof view.day !== 'number') return;\n const jsDow = new Date(Date.UTC(view.year, view.month - 1, view.day)).getUTCDay(); // 0=Sun..6=Sat\n view.dayOfWeek = jsDow === 0 ? 7 : jsDow; // 1=Mon..7=Sun\n}\n\nexport function startOf(value: unknown, unit: StartOfUnit): DateFieldView {\n const view = asDateFieldView(value);\n const result: DateFieldView = { ...view };\n if (unit === 'year') {\n result.month = 1;\n result.day = 1;\n } else if (unit === 'month') {\n result.day = 1;\n }\n recomputeDayOfWeek(result);\n if (touchesTime(unit)) {\n result.hour = 0;\n result.minute = 0;\n result.second = 0;\n result.millisecond = 0;\n } else if (unit === 'hour') {\n result.minute = 0;\n result.second = 0;\n result.millisecond = 0;\n } else if (unit === 'minute') {\n result.second = 0;\n result.millisecond = 0;\n } else if (unit === 'second') {\n result.millisecond = 0;\n }\n return result;\n}\n\nexport function endOf(value: unknown, unit: StartOfUnit): DateFieldView {\n const view = asDateFieldView(value);\n const result: DateFieldView = { ...view };\n if (unit === 'year') {\n result.month = 12;\n result.day = daysInMonth({ year: result.year!, month: 12 });\n } else if (unit === 'month') {\n result.day = daysInMonth({ year: result.year!, month: result.month! });\n }\n recomputeDayOfWeek(result);\n if (touchesTime(unit)) {\n result.hour = 23;\n result.minute = 59;\n result.second = 59;\n result.millisecond = 999;\n } else if (unit === 'hour') {\n result.minute = 59;\n result.second = 59;\n result.millisecond = 999;\n } else if (unit === 'minute') {\n result.second = 59;\n result.millisecond = 999;\n } else if (unit === 'second') {\n result.millisecond = 999;\n }\n return result;\n}\n\n// Type-narrowing helpers used by the comparison/arithmetic modules.\n// Lets them accept any of the four date-carrying Temporal types without\n// importing Temporal itself.\nexport function asDateFieldView(value: unknown): DateFieldView {\n if (typeof value !== 'object' || value === null) {\n throw new Error(`temporal-fmt: expected a date-carrying Temporal value, got ${String(value)}.`);\n }\n // Temporal instances expose year/month/day/etc. as prototype getters,\n // not own enumerable properties — so `{ ...value }` would lose them.\n // Read them explicitly. Only the fields actually present on this\n // value type end up in the returned view.\n const v = value as Record<string, unknown>;\n const out: DateFieldView = {};\n if (typeof v.year === 'number') out.year = v.year;\n if (typeof v.month === 'number') out.month = v.month;\n if (typeof v.day === 'number') out.day = v.day;\n if (typeof v.hour === 'number') out.hour = v.hour;\n if (typeof v.minute === 'number') out.minute = v.minute;\n if (typeof v.second === 'number') out.second = v.second;\n if (typeof v.millisecond === 'number') out.millisecond = v.millisecond;\n if (typeof v.dayOfWeek === 'number') out.dayOfWeek = v.dayOfWeek;\n if (typeof v.calendarId === 'string') out.calendarId = v.calendarId;\n if (out.year === undefined || out.month === undefined || out.day === undefined) {\n throw new Error(\n `temporal-fmt: value is missing year/month/day fields — pass a Temporal.PlainDate / PlainDateTime / ZonedDateTime.`\n );\n }\n return out;\n}\n\n// Re-export the TemporalType alias so callers can import everything\n// from one place.\nexport type { TemporalType } from './tokenMetadata.js';\n// Re-export TemporalLike for the same reason.\nexport type { TemporalLike } from './tokens.js';\n","// ISO 8601 week numbering: a week runs Monday–Sunday, and week 1 of a year\n// is the week containing the year's first Thursday (equivalently, the week\n// containing January 4). This means late-December dates can fall in week 1\n// of the *next* year, and early-January dates can fall in week 52 or 53 of\n// the *previous* year. The \"ISO week-numbering year\" (what `RRRR` formats)\n// is that adjacent year, not the calendar year.\n//\n// Computed here from the date's own year/month/day plus its ISO dayOfWeek\n// (1=Mon..7=Sun, matching Temporal's numbering) using plain Gregorian\n// arithmetic — no Temporal factory needed. format() only has the fields\n// the caller already put on the object, and requiring a Temporal\n// implementation just for ISO week would be a regression for callers who\n// use format() without setTemporal() on a non-26 Node.\n\nconst DAYS_IN_MONTH = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];\nconst CUMULATIVE_DAYS_BY_MONTH = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334];\n\nexport function isGregorianLeapYear(year: number): boolean {\n // Gregorian rule: divisible by 4, except centuries which must also be\n // divisible by 400. Temporal's iso8601 calendar is proleptic Gregorian\n // (no Julian cutover), so this applies for every year, including BCE.\n return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;\n}\n\nfunction daysInYear(year: number): 365 | 366 {\n return isGregorianLeapYear(year) ? 366 : 365;\n}\n\nexport function dayOfYear(year: number, month: number, day: number): number {\n let doy = CUMULATIVE_DAYS_BY_MONTH[month - 1]! + day;\n if (month > 2 && isGregorianLeapYear(year)) doy += 1;\n return doy;\n}\n\n// Jan 1, 2000 was a Saturday — ISO dayOfWeek 6. Anchoring day-of-week\n// computations to a known reference date is simpler and cheaper than\n// pulling in Zeller's congruence, and the reference never changes.\nconst REFERENCE_YEAR = 2000;\nconst REFERENCE_JAN1_DAY_OF_WEEK = 6;\n\nfunction dayOfWeekOfJan1(year: number): number {\n // Sum full-year deltas from the 2000 anchor rather than recomputing from\n // scratch each call — the per-call work is a single mod this way, and\n // the loop rarely runs far (a typical caller passes a current-era year).\n let offset = 0;\n if (year >= REFERENCE_YEAR) {\n for (let y = REFERENCE_YEAR; y < year; y++) offset += daysInYear(y);\n } else {\n for (let y = year; y < REFERENCE_YEAR; y++) offset -= daysInYear(y);\n }\n // Convert \"days since Jan 1, 2000\" into an ISO day-of-week (1=Mon..7=Sun).\n // Jan 1, 2000 was ISO 6 (Sat), so zero-indexed dow = (6-1 + offset) mod 7.\n const zeroIndexed = (((6 - 1 + offset) % 7) + 7) % 7;\n return zeroIndexed + 1;\n}\n\nexport interface IsoWeekDate {\n isoYear: number;\n week: number; // 1..53\n}\n\nexport function isoWeekYearAndWeek(year: number, month: number, day: number, dayOfWeek: number): IsoWeekDate {\n // Step 1: find the Thursday of the current ISO week. The ISO week-numbering\n // year is whichever calendar year that Thursday falls in. Computing it via\n // day-of-year offsets (rather than constructing a Temporal.PlainDate and\n // adding days) keeps this function pure-numeric.\n const doy = dayOfYear(year, month, day);\n const thursdayDoyRelative = doy + (4 - dayOfWeek); // may be <1 or >daysInYear\n\n let isoYear: number;\n let thursdayDoy: number;\n if (thursdayDoyRelative < 1) {\n isoYear = year - 1;\n thursdayDoy = thursdayDoyRelative + daysInYear(isoYear);\n } else if (thursdayDoyRelative > daysInYear(year)) {\n isoYear = year + 1;\n thursdayDoy = thursdayDoyRelative - daysInYear(year);\n } else {\n isoYear = year;\n thursdayDoy = thursdayDoyRelative;\n }\n\n // Step 2: locate the first Thursday of isoYear — its week is week 1. The\n // first Thursday's day-of-year depends on what weekday Jan 1 of isoYear is.\n const jan1Dow = dayOfWeekOfJan1(isoYear);\n const firstThursdayDoy = 1 + ((4 - jan1Dow + 7) % 7); // 1..7\n\n // Step 3: count full weeks between the two Thursdays.\n const week = 1 + Math.floor((thursdayDoy - firstThursdayDoy) / 7);\n return { isoYear, week };\n}\n"],"mappings":"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","names":["daysInYear","daysInYear"]}
1
+ {"version":3,"sources":["../src/calendarUtils.ts","../src/isoWeek.ts"],"sourcesContent":["// Calendar utility helpers (plan section L). These are pure functions\n// over the TemporalLike shape — no Temporal namespace needed, same\n// approach as isoWeek.ts and the field-reading helpers in format.ts.\n// Letting callers compute dayOfYear/weekOfYear/etc. without going\n// through format() means they can build their own derived values\n// without committing to a string format.\n//\n// Calendar-sensitivity: the helpers in this module assume the\n// iso8601 (Gregorian) calendar — that's what TemporalLike fields\n// carry for the overwhelming majority of callers. Non-Gregorian\n// calendars (hebrew, islamic, etc.) need their own helpers; this\n// module doesn't try to be calendar-polymorphic the way Temporal\n// itself is. Documented limitation, not a design choice — see\n// VERIFICATION.md for the rationale.\n\nimport { isGregorianLeapYear, dayOfYear, isoWeekYearAndWeek } from './isoWeek.js';\n\n// A subset of TemporalLike that has the date fields these helpers need,\n// plus optional time fields. PlainTime isn't a DateFieldView (no\n// year/month/day), but PlainDateTime / ZonedDateTime / PlainDate all\n// match. Time fields are optional so callers can pass a PlainDate\n// to startOf(value, 'month') without having to populate hour/minute/etc.\n// Exported so the comparison/arithmetic modules can use the same\n// narrowing.\nexport interface DateFieldView {\n year?: number;\n month?: number;\n day?: number;\n hour?: number;\n minute?: number;\n second?: number;\n millisecond?: number;\n dayOfWeek?: number;\n calendarId?: string;\n}\n\nfunction requireFields(view: DateFieldView, fields: Array<keyof DateFieldView>): void {\n for (const f of fields) {\n if (typeof view[f] !== 'number') {\n throw new Error(\n `temporal-fmt: calendar helper requires \"${String(f)}\", which this value doesn't have. ` +\n `Pass a Temporal.PlainDate / PlainDateTime / ZonedDateTime.`\n );\n }\n }\n}\n\nexport function daysInMonth(view: DateFieldView): number {\n requireFields(view, ['year', 'month']);\n const { year, month } = view;\n // Standard Gregorian month lengths. February's length depends on\n // whether `year` is a leap year — the same isGregorianLeapYear check\n // isoWeek.ts uses for dayOfYear arithmetic.\n const LENGTHS = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];\n if (month === 2 && isGregorianLeapYear(year!)) return 29;\n return LENGTHS[(month! - 1)!]!;\n}\n\nexport function daysInYear(view: DateFieldView): 365 | 366 {\n requireFields(view, ['year']);\n return isGregorianLeapYear(view.year!) ? 366 : 365;\n}\n\nexport function monthsInYear(_view: DateFieldView): 12 {\n // Gregorian always has 12 months. Other calendars (Hebrew leap years\n // have 13) need calendar-aware logic this module doesn't carry — see\n // the file-level comment. The `_view` parameter is kept so the\n // signature mirrors the other helpers and a future calendar-aware\n // implementation can use it without changing call sites.\n return 12;\n}\n\nexport function isLeapYear(view: DateFieldView): boolean {\n requireFields(view, ['year']);\n return isGregorianLeapYear(view.year!);\n}\n\n// `isLeapMonth` would require knowing which month of a leap-year-aware\n// calendar is the leap month — Gregorian doesn't have one, so this\n// returns false unconditionally. Kept here so the public surface\n// matches the plan's section L listing; non-Gregorian calendars need\n// a different implementation.\nexport function isLeapMonth(_view: DateFieldView): boolean {\n return false;\n}\n\nexport function dayOfYearHelper(view: DateFieldView): number {\n requireFields(view, ['year', 'month', 'day']);\n return dayOfYear(view.year!, view.month!, view.day!);\n}\n\n// ISO 8601 week and week-year. Delegates to isoWeek.ts's\n// isoWeekYearAndWeek, which does the full Thursday-of-week\n// computation to handle the year-boundary cases (Dec 29-31 belonging\n// to week 1 of next year, Jan 1-3 belonging to week 52/53 of the\n// previous year).\nexport function weekOfYear(view: DateFieldView): number {\n requireFields(view, ['year', 'month', 'day', 'dayOfWeek']);\n return isoWeekYearAndWeek(view.year!, view.month!, view.day!, view.dayOfWeek!).week;\n}\n\nexport function weekYear(view: DateFieldView): number {\n requireFields(view, ['year', 'month', 'day', 'dayOfWeek']);\n return isoWeekYearAndWeek(view.year!, view.month!, view.day!, view.dayOfWeek!).isoYear;\n}\n\n/**\n * Fiscal-quarter options. `startMonth` is the calendar month (1-12) the\n * fiscal year begins on — e.g. `7` for a fiscal year starting in July.\n * Omitted or `1` gives the calendar-quarter behavior getQuarter() has\n * always had (Jan-Mar = Q1, etc.), so existing callers passing nothing\n * see no change.\n */\nexport interface QuarterOptions {\n startMonth?: number;\n}\n\nfunction validateStartMonth(startMonth: number): void {\n if (!Number.isInteger(startMonth) || startMonth < 1 || startMonth > 12) {\n throw new Error(\n `temporal-fmt: getQuarter's startMonth must be an integer from 1 to 12 (got ${startMonth}).`\n );\n }\n}\n\nexport function getQuarter(view: DateFieldView, options: QuarterOptions = {}): number {\n requireFields(view, ['month']);\n const startMonth = options.startMonth ?? 1;\n validateStartMonth(startMonth);\n if (startMonth === 1) {\n // Mirrors the Q token: months 1-3 → Q1, 4-6 → Q2, 7-9 → Q3, 10-12 → Q4.\n return Math.ceil(view.month! / 3);\n }\n // Fiscal case: shift the month so startMonth becomes month 1 of the\n // fiscal year (mod 12, 1-indexed), then apply the same ceil(/3) rule.\n // E.g. startMonth=7 (fiscal year starts July): July→1, Aug→2, ...,\n // Dec→6, Jan→7, ..., June→12. Then Q1 = fiscal months 1-3 (Jul-Sep),\n // matching the common \"FY starts in July\" convention where Q1 is the\n // first quarter of the fiscal year, not a quarter numbered by which\n // calendar quarter it falls in.\n const shifted = ((view.month! - startMonth + 12) % 12) + 1;\n return Math.ceil(shifted / 3);\n}\n\n// `getMonth` / `getWeekday` look trivial (just read the field) but the\n// plan's section L lists them explicitly, so they're here for surface\n// completeness. They also normalize: getWeekday returns 1-7 (Mon-Sun,\n// matching Temporal's spec) regardless of what numbering the caller's\n// underlying value uses.\nexport function getMonth(view: DateFieldView): number {\n requireFields(view, ['month']);\n return view.month!;\n}\n\nexport function getWeekday(view: DateFieldView): number {\n requireFields(view, ['dayOfWeek']);\n return view.dayOfWeek!;\n}\n\n// startOf / endOf return new field bags (not Temporal objects — this\n// module is polyfill-free) with the relevant fields zeroed/extended.\n// Callers can pass the result to a Temporal constructor if they want\n// a typed value.\nexport type StartOfUnit = 'day' | 'month' | 'year' | 'hour' | 'minute' | 'second';\n\n// Returns true if the given unit (when used with startOf/endOf) should\n// also touch the time fields. 'day', 'month', 'year' all imply a\n// resolution coarser than an hour, so startOf zeroes the time fields\n// and endOf maxes them. Sub-hour units (hour/minute/second) only touch\n// the fields finer than themselves.\nfunction touchesTime(unit: StartOfUnit): boolean {\n return unit === 'day' || unit === 'month' || unit === 'year';\n}\n\n// startOf/endOf reassign year/month/day, which invalidates any\n// dayOfWeek carried over from the input — a plain { ...view } spread\n// leaves the old value sitting there unchanged. Same failure mode\n// businessCalendar.ts's isBusinessDay() works around for add(); we\n// recompute here rather than trust the copied field.\nfunction recomputeDayOfWeek(view: DateFieldView): void {\n if (typeof view.dayOfWeek !== 'number') return;\n /* c8 ignore start @preserve -- unreachable: startOf()/endOf() both call\n * asDateFieldView() before this, which already throws if year/month/day\n * aren't all numbers — so by the time a value with a numeric dayOfWeek\n * reaches here, year/month/day are guaranteed present too. */\n if (typeof view.year !== 'number' || typeof view.month !== 'number' || typeof view.day !== 'number') return;\n /* c8 ignore stop @preserve */\n const jsDow = new Date(Date.UTC(view.year, view.month - 1, view.day)).getUTCDay(); // 0=Sun..6=Sat\n view.dayOfWeek = jsDow === 0 ? 7 : jsDow; // 1=Mon..7=Sun\n}\n\nexport function startOf(value: unknown, unit: StartOfUnit): DateFieldView {\n const view = asDateFieldView(value);\n const result: DateFieldView = { ...view };\n if (unit === 'year') {\n result.month = 1;\n result.day = 1;\n } else if (unit === 'month') {\n result.day = 1;\n }\n recomputeDayOfWeek(result);\n if (touchesTime(unit)) {\n result.hour = 0;\n result.minute = 0;\n result.second = 0;\n result.millisecond = 0;\n } else if (unit === 'hour') {\n result.minute = 0;\n result.second = 0;\n result.millisecond = 0;\n } else if (unit === 'minute') {\n result.second = 0;\n result.millisecond = 0;\n } else if (unit === 'second') {\n result.millisecond = 0;\n }\n return result;\n}\n\nexport function endOf(value: unknown, unit: StartOfUnit): DateFieldView {\n const view = asDateFieldView(value);\n const result: DateFieldView = { ...view };\n if (unit === 'year') {\n result.month = 12;\n result.day = daysInMonth({ year: result.year!, month: 12 });\n } else if (unit === 'month') {\n result.day = daysInMonth({ year: result.year!, month: result.month! });\n }\n recomputeDayOfWeek(result);\n if (touchesTime(unit)) {\n result.hour = 23;\n result.minute = 59;\n result.second = 59;\n result.millisecond = 999;\n } else if (unit === 'hour') {\n result.minute = 59;\n result.second = 59;\n result.millisecond = 999;\n } else if (unit === 'minute') {\n result.second = 59;\n result.millisecond = 999;\n } else if (unit === 'second') {\n result.millisecond = 999;\n }\n return result;\n}\n\n// Type-narrowing helpers used by the comparison/arithmetic modules.\n// Lets them accept any of the four date-carrying Temporal types without\n// importing Temporal itself.\nexport function asDateFieldView(value: unknown): DateFieldView {\n if (typeof value !== 'object' || value === null) {\n throw new Error(`temporal-fmt: expected a date-carrying Temporal value, got ${String(value)}.`);\n }\n // Temporal instances expose year/month/day/etc. as prototype getters,\n // not own enumerable properties — so `{ ...value }` would lose them.\n // Read them explicitly. Only the fields actually present on this\n // value type end up in the returned view.\n const v = value as Record<string, unknown>;\n const out: DateFieldView = {};\n if (typeof v.year === 'number') out.year = v.year;\n if (typeof v.month === 'number') out.month = v.month;\n if (typeof v.day === 'number') out.day = v.day;\n if (typeof v.hour === 'number') out.hour = v.hour;\n if (typeof v.minute === 'number') out.minute = v.minute;\n if (typeof v.second === 'number') out.second = v.second;\n if (typeof v.millisecond === 'number') out.millisecond = v.millisecond;\n if (typeof v.dayOfWeek === 'number') out.dayOfWeek = v.dayOfWeek;\n if (typeof v.calendarId === 'string') out.calendarId = v.calendarId;\n if (out.year === undefined || out.month === undefined || out.day === undefined) {\n throw new Error(\n `temporal-fmt: value is missing year/month/day fields — pass a Temporal.PlainDate / PlainDateTime / ZonedDateTime.`\n );\n }\n return out;\n}\n\n// Re-export the TemporalType alias so callers can import everything\n// from one place.\nexport type { TemporalType } from './tokenMetadata.js';\n// Re-export TemporalLike for the same reason.\nexport type { TemporalLike } from './tokens.js';","// ISO 8601 week numbering: a week runs Monday–Sunday, and week 1 of a year\n// is the week containing the year's first Thursday (equivalently, the week\n// containing January 4). This means late-December dates can fall in week 1\n// of the *next* year, and early-January dates can fall in week 52 or 53 of\n// the *previous* year. The \"ISO week-numbering year\" (what `RRRR` formats)\n// is that adjacent year, not the calendar year.\n//\n// Computed here from the date's own year/month/day plus its ISO dayOfWeek\n// (1=Mon..7=Sun, matching Temporal's numbering) using plain Gregorian\n// arithmetic — no Temporal factory needed. format() only has the fields\n// the caller already put on the object, and requiring a Temporal\n// implementation just for ISO week would be a regression for callers who\n// use format() without setTemporal() on a non-26 Node.\n\nconst DAYS_IN_MONTH = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];\nconst CUMULATIVE_DAYS_BY_MONTH = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334];\n\nexport function isGregorianLeapYear(year: number): boolean {\n // Gregorian rule: divisible by 4, except centuries which must also be\n // divisible by 400. Temporal's iso8601 calendar is proleptic Gregorian\n // (no Julian cutover), so this applies for every year, including BCE.\n return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;\n}\n\nfunction daysInYear(year: number): 365 | 366 {\n return isGregorianLeapYear(year) ? 366 : 365;\n}\n\nexport function dayOfYear(year: number, month: number, day: number): number {\n let doy = CUMULATIVE_DAYS_BY_MONTH[month - 1]! + day;\n if (month > 2 && isGregorianLeapYear(year)) doy += 1;\n return doy;\n}\n\n// Jan 1, 2000 was a Saturday — ISO dayOfWeek 6. Anchoring day-of-week\n// computations to a known reference date is simpler and cheaper than\n// pulling in Zeller's congruence, and the reference never changes.\nconst REFERENCE_YEAR = 2000;\nconst REFERENCE_JAN1_DAY_OF_WEEK = 6;\n\nfunction dayOfWeekOfJan1(year: number): number {\n // Sum full-year deltas from the 2000 anchor rather than recomputing from\n // scratch each call — the per-call work is a single mod this way, and\n // the loop rarely runs far (a typical caller passes a current-era year).\n let offset = 0;\n if (year >= REFERENCE_YEAR) {\n for (let y = REFERENCE_YEAR; y < year; y++) offset += daysInYear(y);\n } else {\n for (let y = year; y < REFERENCE_YEAR; y++) offset -= daysInYear(y);\n }\n // Convert \"days since Jan 1, 2000\" into an ISO day-of-week (1=Mon..7=Sun).\n // Jan 1, 2000 was ISO 6 (Sat), so zero-indexed dow = (6-1 + offset) mod 7.\n const zeroIndexed = (((6 - 1 + offset) % 7) + 7) % 7;\n return zeroIndexed + 1;\n}\n\nexport interface IsoWeekDate {\n isoYear: number;\n week: number; // 1..53\n}\n\nexport function isoWeekYearAndWeek(year: number, month: number, day: number, dayOfWeek: number): IsoWeekDate {\n // Step 1: find the Thursday of the current ISO week. The ISO week-numbering\n // year is whichever calendar year that Thursday falls in. Computing it via\n // day-of-year offsets (rather than constructing a Temporal.PlainDate and\n // adding days) keeps this function pure-numeric.\n const doy = dayOfYear(year, month, day);\n const thursdayDoyRelative = doy + (4 - dayOfWeek); // may be <1 or >daysInYear\n\n let isoYear: number;\n let thursdayDoy: number;\n if (thursdayDoyRelative < 1) {\n isoYear = year - 1;\n thursdayDoy = thursdayDoyRelative + daysInYear(isoYear);\n } else if (thursdayDoyRelative > daysInYear(year)) {\n isoYear = year + 1;\n thursdayDoy = thursdayDoyRelative - daysInYear(year);\n } else {\n isoYear = year;\n thursdayDoy = thursdayDoyRelative;\n }\n\n // Step 2: locate the first Thursday of isoYear — its week is week 1. The\n // first Thursday's day-of-year depends on what weekday Jan 1 of isoYear is.\n const jan1Dow = dayOfWeekOfJan1(isoYear);\n const firstThursdayDoy = 1 + ((4 - jan1Dow + 7) % 7); // 1..7\n\n // Step 3: count full weeks between the two Thursdays.\n const week = 1 + Math.floor((thursdayDoy - firstThursdayDoy) / 7);\n return { isoYear, week };\n}\n"],"mappings":"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","names":["daysInYear","daysInYear"]}
@@ -13,7 +13,7 @@ import {
13
13
  startOf,
14
14
  weekOfYear,
15
15
  weekYear
16
- } from "./chunk-ASLNQ4DG.js";
16
+ } from "./chunk-7LAIYTPE.js";
17
17
  import "./chunk-YS52LOMJ.js";
18
18
  export {
19
19
  asDateFieldView,
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  DEFAULT_LOCALE
3
- } from "./chunk-7SFQUOID.js";
3
+ } from "./chunk-XIKLQMF7.js";
4
4
  import {
5
5
  differenceInDays
6
- } from "./chunk-AC66SDAS.js";
6
+ } from "./chunk-WZC43XGU.js";
7
7
 
8
8
  // src/relativeTime.ts
9
9
  var rtfCache = /* @__PURE__ */ new Map();
@@ -54,4 +54,4 @@ export {
54
54
  formatRelative,
55
55
  formatRelativeToNow
56
56
  };
57
- //# sourceMappingURL=chunk-H5ZLOWP5.js.map
57
+ //# sourceMappingURL=chunk-2R46FDWW.js.map
@@ -1,5 +1,8 @@
1
1
  // src/localeVocab.ts
2
2
  var customVocabs = /* @__PURE__ */ new Map();
3
+ var MAX_CUSTOM_VOCABS = 500;
4
+ var MAX_LOCALE_TAG_LENGTH = 256;
5
+ var MAX_VOCAB_ENTRY_LENGTH = 256;
3
6
  function assertValidVocab(vocab, locale) {
4
7
  const required = [
5
8
  { key: "monthLong", length: 12, label: "long month names" },
@@ -31,6 +34,11 @@ function assertValidVocab(vocab, locale) {
31
34
  `temporal-fmt: registerLocaleVocab for locale "${locale}": "${key}[${i}]" must be a non-empty string, got ${String(entry)}.`
32
35
  );
33
36
  }
37
+ if (entry.length > MAX_VOCAB_ENTRY_LENGTH) {
38
+ throw new RangeError(
39
+ `temporal-fmt: registerLocaleVocab for locale "${locale}": "${key}[${i}]" is too long (maximum ${MAX_VOCAB_ENTRY_LENGTH} characters).`
40
+ );
41
+ }
34
42
  });
35
43
  }
36
44
  assertNoCollision(vocab.monthLong, "MMMM month", locale);
@@ -47,8 +55,14 @@ function registerLocaleVocab(locale, vocab) {
47
55
  if (typeof locale !== "string" || locale.length === 0) {
48
56
  throw new Error(`temporal-fmt: registerLocaleVocab requires a non-empty locale string, got ${String(locale)}.`);
49
57
  }
58
+ if (locale.length > MAX_LOCALE_TAG_LENGTH) {
59
+ throw new RangeError(`temporal-fmt: registerLocaleVocab locale is too long (maximum ${MAX_LOCALE_TAG_LENGTH} characters).`);
60
+ }
50
61
  assertValidVocab(vocab, locale);
51
62
  const cacheKey = canonicalCacheKey(locale);
63
+ if (!customVocabs.has(cacheKey) && customVocabs.size >= MAX_CUSTOM_VOCABS) {
64
+ throw new RangeError(`temporal-fmt: registerLocaleVocab reached the ${MAX_CUSTOM_VOCABS}-locale limit.`);
65
+ }
52
66
  customVocabs.set(cacheKey, {
53
67
  monthLong: [...vocab.monthLong],
54
68
  monthShort: [...vocab.monthShort],
@@ -70,9 +84,23 @@ var MAX_VOCAB_CACHE_SIZE = 500;
70
84
  function partValue(formatter, date, type) {
71
85
  const parts = formatter.formatToParts(date);
72
86
  const index = parts.findIndex((p) => p.type === type);
87
+ /* c8 ignore start @preserve -- defensive guard against a real but
88
+ unreproducible failure mode: an Intl implementation that omits the
89
+ requested part type entirely for some locale. Checked every locale
90
+ with unusual dayPeriod/weekday/month rendering available in this
91
+ runtime's ICU data (ja-JP, zh-CN, th-TH, he-IL, ar-SA, ko-KR, fa-IR)
92
+ against the exact formatter options getLocaleVocab uses (notably
93
+ hour12: true for the dayPeriod formatter, which is what makes every
94
+ locale here actually emit a dayPeriod part — omitting it is what
95
+ produced a false "gap" during investigation). None omit their part
96
+ on this runtime. A different ICU version or a non-Node Intl
97
+ implementation could plausibly behave differently, so this stays a
98
+ real check rather than an assertion. */
99
+ /* c8 ignore start @preserve */
73
100
  if (index === -1) {
74
101
  throw new Error(`temporal-fmt: locale produced no "${type}" part while building match vocabulary.`);
75
102
  }
103
+ /* c8 ignore stop @preserve */
76
104
  let value = parts[index].value;
77
105
  const prev = parts[index - 1];
78
106
  const next = parts[index + 1];
@@ -147,4 +175,4 @@ export {
147
175
  getCustomVocab,
148
176
  getLocaleVocab
149
177
  };
150
- //# sourceMappingURL=chunk-J3YC66TB.js.map
178
+ //# sourceMappingURL=chunk-3RLYULUQ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/localeVocab.ts"],"sourcesContent":["// Name lists for the locale-aware tokens (MMMM, MMM, EEEE, EEE, a). Each\n// list is small and fixed (12 months, 7 weekdays, 2 day periods), so we\n// generate the real Intl strings for a locale once and cache them.\n\nexport interface LocaleVocab {\n monthLong: string[]; // index 0 = January\n monthShort: string[];\n weekdayLong: string[]; // index 0 = Monday, per Temporal's dayOfWeek numbering\n weekdayShort: string[];\n dayPeriod: string[]; // typically [AM-ish, PM-ish], deduped\n}\n\n// Custom vocabs registered by callers for locales Intl doesn't cover well\n// (e.g. a 13-month Hebrew leap year, where Intl's 12-month vocabulary\n// silently loses a whole month). Keyed by canonical cache key so the\n// same locale string spelling variants fold together — same convention\n// as the Intl-derived vocab cache above.\nconst customVocabs = new Map<string, LocaleVocab>();\nconst MAX_CUSTOM_VOCABS = 500;\nconst MAX_LOCALE_TAG_LENGTH = 256;\nconst MAX_VOCAB_ENTRY_LENGTH = 256;\n\nfunction assertValidVocab(vocab: Partial<LocaleVocab>, locale: string): void {\n // Strict shape validation at registration time, not lazily on first\n // use — the README's promise is that a malformed registration throws\n // descriptively here, rather than failing later inside format()/parse()\n // with a confusing \"no month part\" or wrong-month error the caller\n // can't trace back to the bad registration.\n const required: Array<{ key: keyof LocaleVocab; length: number; label: string }> = [\n { key: 'monthLong', length: 12, label: 'long month names' },\n { key: 'monthShort', length: 12, label: 'short month names' },\n { key: 'weekdayLong', length: 7, label: 'long weekday names' },\n { key: 'weekdayShort', length: 7, label: 'short weekday names' },\n { key: 'dayPeriod', length: 2, label: 'day period markers (AM/PM-equivalent)' },\n ];\n\n for (const { key, length, label } of required) {\n const value = vocab[key];\n if (value === undefined) {\n throw new Error(\n `temporal-fmt: registerLocaleVocab for locale \"${locale}\" is missing required field \"${key}\" (${label}).`\n );\n }\n if (!Array.isArray(value)) {\n throw new Error(\n `temporal-fmt: registerLocaleVocab for locale \"${locale}\": \"${key}\" must be an array, got ${typeof value}.`\n );\n }\n if (value.length !== length) {\n throw new Error(\n `temporal-fmt: registerLocaleVocab for locale \"${locale}\": \"${key}\" must have exactly ${length} entries (got ${value.length}) — ${label}.`\n );\n }\n value.forEach((entry, i) => {\n if (typeof entry !== 'string' || entry.length === 0) {\n throw new Error(\n `temporal-fmt: registerLocaleVocab for locale \"${locale}\": \"${key}[${i}]\" must be a non-empty string, got ${String(entry)}.`\n );\n }\n if (entry.length > MAX_VOCAB_ENTRY_LENGTH) {\n throw new RangeError(\n `temporal-fmt: registerLocaleVocab for locale \"${locale}\": \"${key}[${i}]\" is too long (maximum ${MAX_VOCAB_ENTRY_LENGTH} characters).`\n );\n }\n });\n }\n\n // Reuse the same collision check the Intl-derived path uses — a\n // duplicate month name is just as ambiguous when supplied by a caller\n // as when produced by Intl.\n assertNoCollision(vocab.monthLong!, 'MMMM month', locale);\n assertNoCollision(vocab.monthShort!, 'MMM month', locale);\n assertNoCollision(vocab.weekdayLong!, 'EEEE weekday', locale);\n assertNoCollision(vocab.weekdayShort!, 'EEE weekday', locale);\n\n // dayPeriod entries must differ from each other, or parse()'s\n // isPM check (raw === vocab.dayPeriod[1]) can never return true and\n // every 12-hour parse silently resolves to AM. The Intl-derived path\n // dedupes a same-AM/PM collision to length 1, but a caller passing\n // both entries identical is a real bug to surface — not something to\n // dedupe around.\n if (vocab.dayPeriod![0] === vocab.dayPeriod![1]) {\n throw new Error(\n `temporal-fmt: registerLocaleVocab for locale \"${locale}\": dayPeriod entries must differ ` +\n `(both are \"${vocab.dayPeriod![0]}\"); otherwise parse() can't tell AM from PM.`\n );\n }\n}\n\n/**\n * Supply a custom month/weekday/day-period vocabulary for a locale key,\n * overriding the Intl-derived vocab this library would otherwise build\n * for that key. Useful for locales Intl doesn't cover well — the README's\n * known-limitations section calls out the Hebrew leap-month gap as a\n * specific case this addresses.\n *\n * Throws descriptively on malformed input (wrong array lengths, empty\n * strings, duplicate entries, missing fields) rather than failing later\n * during format/parse.\n *\n * Registered vocab takes precedence over the Intl-derived vocab for that\n * locale key, including for already-cached entries — registering\n * invalidates the prior cache entry for that locale so the next call\n * picks up the new vocab.\n *\n * @example\n * registerLocaleVocab('en-u-ca-hebrew-leap', {\n * monthLong: ['Nisan','Iyar','Sivan','Tammuz','Av','Elul','Tishrei','Marcheshvan','Kislev','Tevet','Shevat','Adar I','Adar II'],\n * monthShort: ['Nis','Iyy','Siv','Tam','Av','Elu','Tish','Chesh','Kis','Tev','Shv','Ad1','Ad2'],\n * weekdayLong: ['Monday','Tuesday','Wednesday','Thursday','Friday','Saturday','Sunday'],\n * weekdayShort: ['Mon','Tue','Wed','Thu','Fri','Sat','Sun'],\n * dayPeriod: ['AM','PM'],\n * });\n */\nexport function registerLocaleVocab(locale: string, vocab: Partial<LocaleVocab>): void {\n if (typeof locale !== 'string' || locale.length === 0) {\n throw new Error(`temporal-fmt: registerLocaleVocab requires a non-empty locale string, got ${String(locale)}.`);\n }\n if (locale.length > MAX_LOCALE_TAG_LENGTH) {\n throw new RangeError(`temporal-fmt: registerLocaleVocab locale is too long (maximum ${MAX_LOCALE_TAG_LENGTH} characters).`);\n }\n assertValidVocab(vocab, locale);\n\n const cacheKey = canonicalCacheKey(locale);\n if (!customVocabs.has(cacheKey) && customVocabs.size >= MAX_CUSTOM_VOCABS) {\n throw new RangeError(`temporal-fmt: registerLocaleVocab reached the ${MAX_CUSTOM_VOCABS}-locale limit.`);\n }\n customVocabs.set(cacheKey, {\n monthLong: [...vocab.monthLong!],\n monthShort: [...vocab.monthShort!],\n weekdayLong: [...vocab.weekdayLong!],\n weekdayShort: [...vocab.weekdayShort!],\n dayPeriod: [...vocab.dayPeriod!],\n });\n // Invalidate the Intl-derived cache entry so any prior format/parse\n // result cached for this locale is rebuilt against the new vocab. Not\n // strictly necessary (getLocaleVocab checks customVocabs first), but\n // cheap and keeps the two caches from drifting out of sync.\n vocabCache.delete(cacheKey);\n}\n\n// Every locale-keyed cache in this library (this one, formatterCache in\n// tokens.ts, patternCache/calendarCache in parse.ts) used to key on the\n// exact locale string a caller passed in. Intl treats spelling variants of\n// the same locale as equivalent ('en-US' / 'en-us' / 'en_US' all resolve\n// the same way), but a plain string-keyed Map doesn't — so callers mixing\n// spellings for what's really one locale would silently fragment across\n// separate cache entries instead of sharing one, making the bounded\n// eviction limits (MAX_*_CACHE_SIZE) less effective than they look. This\n// doesn't change any cache's *correctness* (each entry is still built from\n// -- and valid for -- whatever locale string produced it), only how many\n// distinct entries equivalent spellings end up costing. Falls back to the\n// original string on a malformed/unrecognized tag rather than throwing —\n// cache-key normalization shouldn't be where a bad locale first surfaces\n// as an error; whatever actually calls `new Intl.DateTimeFormat(locale)`\n// downstream is the right place for that.\nexport function canonicalCacheKey(locale: string): string {\n try {\n // Intl.Locale requires BCP-47 hyphens and rejects underscore-separated\n // tags like 'en_US' outright (RangeError), rather than normalizing\n // them — so without this replace, that spelling would just fall\n // through to the catch below and never fold with 'en-US'.\n return new Intl.Locale(locale.replace(/_/g, '-')).toString().toLowerCase();\n } catch {\n return locale;\n }\n}\n\nconst vocabCache = new Map<string, LocaleVocab>();\nconst MAX_VOCAB_CACHE_SIZE = 500;\n\n// Some locales (ja-JP) split a field across two parts — month \"8\" plus a\n// counter suffix \"月\" as a separate sibling \"literal\" — while format()'s\n// tokens go through toLocaleString(), which concatenates everything into\n// \"8月\". Reading only the type-tagged part used to drop that suffix, so\n// this locale's vocab never matched what format() actually produced.\n// Only merges *adjacent* literals, not the whole string, since the\n// dayPeriod/weekday formatters below carry an extra hour part that a\n// join-everything approach would wrongly absorb.\nfunction partValue(formatter: Intl.DateTimeFormat, date: Date, type: Intl.DateTimeFormatPartTypes): string {\n const parts = formatter.formatToParts(date);\n const index = parts.findIndex((p) => p.type === type);\n /* c8 ignore start @preserve -- defensive guard against a real but\n unreproducible failure mode: an Intl implementation that omits the\n requested part type entirely for some locale. Checked every locale\n with unusual dayPeriod/weekday/month rendering available in this\n runtime's ICU data (ja-JP, zh-CN, th-TH, he-IL, ar-SA, ko-KR, fa-IR)\n against the exact formatter options getLocaleVocab uses (notably\n hour12: true for the dayPeriod formatter, which is what makes every\n locale here actually emit a dayPeriod part — omitting it is what\n produced a false \"gap\" during investigation). None omit their part\n on this runtime. A different ICU version or a non-Node Intl\n implementation could plausibly behave differently, so this stays a\n real check rather than an assertion. */\n /* c8 ignore start @preserve */\n if (index === -1) {\n throw new Error(`temporal-fmt: locale produced no \"${type}\" part while building match vocabulary.`);\n }\n /* c8 ignore stop @preserve */\n let value = parts[index]!.value;\n const prev = parts[index - 1];\n const next = parts[index + 1];\n // skip whitespace literals (the separator before \"AM\") — only a\n // no-space suffix like ja-JP's \"月\" should get folded in\n if (prev?.type === 'literal' && !/\\s/.test(prev.value)) value = prev.value + value;\n if (next?.type === 'literal' && !/\\s/.test(next.value)) value = value + next.value;\n return value;\n}\n\n// Two entries rendering identically means parse()'s reverse lookup\n// (indexOf) can never tell them apart. Weekday collisions already surface\n// via parse()'s dayOfWeek cross-check, but with a confusing same-string\n// error; months have no equivalent cross-check, so a collision there would\n// otherwise resolve silently to the wrong month. Catching both here, once\n// at build time, gives one clear error instead.\nfunction assertNoCollision(names: string[], label: string, locale: string): void {\n const seen = new Map<string, number>();\n for (let i = 0; i < names.length; i++) {\n const prior = seen.get(names[i]!);\n if (prior !== undefined) {\n throw new Error(\n `temporal-fmt: locale \"${locale}\" renders ${label} index ${prior} and ${i} identically ` +\n `(\"${names[i]}\"). parse() can't reliably tell these apart for this locale/token, so this ` +\n `combination isn't supported.`\n );\n }\n seen.set(names[i]!, i);\n }\n}\n\n// Exposed for tokens.ts: when a custom vocab is registered for this\n// locale, format()'s locale-aware tokens (MMMM/MMM/EEEE/EEE/a) read\n// straight from the registered array instead of going through\n// Intl.DateTimeFormat. Without this override, format() would silently\n// keep producing Intl's strings while parse() matched against the\n// registered vocab — the two would round-trip-fail.\nexport function getCustomVocab(locale: string): LocaleVocab | undefined {\n const cacheKey = canonicalCacheKey(locale);\n return customVocabs.get(cacheKey);\n}\n\nexport function getLocaleVocab(locale: string): LocaleVocab {\n const cacheKey = canonicalCacheKey(locale);\n // Registered vocabs take precedence over the Intl-derived one — this\n // is the override mechanism registerLocaleVocab() promises. Checking\n // here, before the Intl cache lookup, means a registration that\n // happens *after* the first Intl-derived vocab was built still takes\n // effect on the next call.\n const custom = customVocabs.get(cacheKey);\n if (custom) {\n return custom;\n }\n const cached = vocabCache.get(cacheKey);\n if (cached) {\n return cached;\n }\n\n const monthLongFmt = new Intl.DateTimeFormat(locale, { month: 'long', timeZone: 'UTC' });\n const monthShortFmt = new Intl.DateTimeFormat(locale, { month: 'short', timeZone: 'UTC' });\n const monthLong: string[] = [];\n const monthShort: string[] = [];\n for (let m = 0; m < 12; m++) {\n const date = new Date(Date.UTC(2020, m, 1));\n monthLong.push(partValue(monthLongFmt, date, 'month'));\n monthShort.push(partValue(monthShortFmt, date, 'month'));\n }\n assertNoCollision(monthLong, 'MMMM month', locale);\n assertNoCollision(monthShort, 'MMM month', locale);\n\n const weekdayLongFmt = new Intl.DateTimeFormat(locale, { weekday: 'long', timeZone: 'UTC' });\n const weekdayShortFmt = new Intl.DateTimeFormat(locale, { weekday: 'short', timeZone: 'UTC' });\n const weekdayLong: string[] = [];\n const weekdayShort: string[] = [];\n // 2024-01-01 is a Monday (UTC) — walk 7 days from there for weekday names\n for (let d = 0; d < 7; d++) {\n const date = new Date(Date.UTC(2024, 0, 1 + d));\n weekdayLong.push(partValue(weekdayLongFmt, date, 'weekday'));\n weekdayShort.push(partValue(weekdayShortFmt, date, 'weekday'));\n }\n // redundant with parse()'s dayOfWeek cross-check, but gives a clearer error\n assertNoCollision(weekdayLong, 'EEEE weekday', locale);\n assertNoCollision(weekdayShort, 'EEE weekday', locale);\n\n const dayPeriodFmt = new Intl.DateTimeFormat(locale, { hour: 'numeric', hour12: true, timeZone: 'UTC' });\n const am = partValue(dayPeriodFmt, new Date(Date.UTC(2020, 0, 1, 1)), 'dayPeriod');\n const pm = partValue(dayPeriodFmt, new Date(Date.UTC(2020, 0, 1, 13)), 'dayPeriod');\n const dayPeriod = [...new Set([am, pm])];\n\n const vocab: LocaleVocab = { monthLong, monthShort, weekdayLong, weekdayShort, dayPeriod };\n if (vocabCache.size >= MAX_VOCAB_CACHE_SIZE) {\n const oldestKey = vocabCache.keys().next().value;\n if (oldestKey !== undefined) vocabCache.delete(oldestKey);\n }\n vocabCache.set(cacheKey, vocab);\n return vocab;\n}"],"mappings":"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","names":[]}
@@ -77,7 +77,12 @@ function touchesTime(unit) {
77
77
  }
78
78
  function recomputeDayOfWeek(view) {
79
79
  if (typeof view.dayOfWeek !== "number") return;
80
+ /* c8 ignore start @preserve -- unreachable: startOf()/endOf() both call
81
+ * asDateFieldView() before this, which already throws if year/month/day
82
+ * aren't all numbers — so by the time a value with a numeric dayOfWeek
83
+ * reaches here, year/month/day are guaranteed present too. */
80
84
  if (typeof view.year !== "number" || typeof view.month !== "number" || typeof view.day !== "number") return;
85
+ /* c8 ignore stop @preserve */
81
86
  const jsDow = new Date(Date.UTC(view.year, view.month - 1, view.day)).getUTCDay();
82
87
  view.dayOfWeek = jsDow === 0 ? 7 : jsDow;
83
88
  }
@@ -174,4 +179,4 @@ export {
174
179
  endOf,
175
180
  asDateFieldView
176
181
  };
177
- //# sourceMappingURL=chunk-ASLNQ4DG.js.map
182
+ //# sourceMappingURL=chunk-7LAIYTPE.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/calendarUtils.ts"],"sourcesContent":["// Calendar utility helpers (plan section L). These are pure functions\n// over the TemporalLike shape — no Temporal namespace needed, same\n// approach as isoWeek.ts and the field-reading helpers in format.ts.\n// Letting callers compute dayOfYear/weekOfYear/etc. without going\n// through format() means they can build their own derived values\n// without committing to a string format.\n//\n// Calendar-sensitivity: the helpers in this module assume the\n// iso8601 (Gregorian) calendar — that's what TemporalLike fields\n// carry for the overwhelming majority of callers. Non-Gregorian\n// calendars (hebrew, islamic, etc.) need their own helpers; this\n// module doesn't try to be calendar-polymorphic the way Temporal\n// itself is. Documented limitation, not a design choice — see\n// VERIFICATION.md for the rationale.\n\nimport { isGregorianLeapYear, dayOfYear, isoWeekYearAndWeek } from './isoWeek.js';\n\n// A subset of TemporalLike that has the date fields these helpers need,\n// plus optional time fields. PlainTime isn't a DateFieldView (no\n// year/month/day), but PlainDateTime / ZonedDateTime / PlainDate all\n// match. Time fields are optional so callers can pass a PlainDate\n// to startOf(value, 'month') without having to populate hour/minute/etc.\n// Exported so the comparison/arithmetic modules can use the same\n// narrowing.\nexport interface DateFieldView {\n year?: number;\n month?: number;\n day?: number;\n hour?: number;\n minute?: number;\n second?: number;\n millisecond?: number;\n dayOfWeek?: number;\n calendarId?: string;\n}\n\nfunction requireFields(view: DateFieldView, fields: Array<keyof DateFieldView>): void {\n for (const f of fields) {\n if (typeof view[f] !== 'number') {\n throw new Error(\n `temporal-fmt: calendar helper requires \"${String(f)}\", which this value doesn't have. ` +\n `Pass a Temporal.PlainDate / PlainDateTime / ZonedDateTime.`\n );\n }\n }\n}\n\nexport function daysInMonth(view: DateFieldView): number {\n requireFields(view, ['year', 'month']);\n const { year, month } = view;\n // Standard Gregorian month lengths. February's length depends on\n // whether `year` is a leap year — the same isGregorianLeapYear check\n // isoWeek.ts uses for dayOfYear arithmetic.\n const LENGTHS = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];\n if (month === 2 && isGregorianLeapYear(year!)) return 29;\n return LENGTHS[(month! - 1)!]!;\n}\n\nexport function daysInYear(view: DateFieldView): 365 | 366 {\n requireFields(view, ['year']);\n return isGregorianLeapYear(view.year!) ? 366 : 365;\n}\n\nexport function monthsInYear(_view: DateFieldView): 12 {\n // Gregorian always has 12 months. Other calendars (Hebrew leap years\n // have 13) need calendar-aware logic this module doesn't carry — see\n // the file-level comment. The `_view` parameter is kept so the\n // signature mirrors the other helpers and a future calendar-aware\n // implementation can use it without changing call sites.\n return 12;\n}\n\nexport function isLeapYear(view: DateFieldView): boolean {\n requireFields(view, ['year']);\n return isGregorianLeapYear(view.year!);\n}\n\n// `isLeapMonth` would require knowing which month of a leap-year-aware\n// calendar is the leap month — Gregorian doesn't have one, so this\n// returns false unconditionally. Kept here so the public surface\n// matches the plan's section L listing; non-Gregorian calendars need\n// a different implementation.\nexport function isLeapMonth(_view: DateFieldView): boolean {\n return false;\n}\n\nexport function dayOfYearHelper(view: DateFieldView): number {\n requireFields(view, ['year', 'month', 'day']);\n return dayOfYear(view.year!, view.month!, view.day!);\n}\n\n// ISO 8601 week and week-year. Delegates to isoWeek.ts's\n// isoWeekYearAndWeek, which does the full Thursday-of-week\n// computation to handle the year-boundary cases (Dec 29-31 belonging\n// to week 1 of next year, Jan 1-3 belonging to week 52/53 of the\n// previous year).\nexport function weekOfYear(view: DateFieldView): number {\n requireFields(view, ['year', 'month', 'day', 'dayOfWeek']);\n return isoWeekYearAndWeek(view.year!, view.month!, view.day!, view.dayOfWeek!).week;\n}\n\nexport function weekYear(view: DateFieldView): number {\n requireFields(view, ['year', 'month', 'day', 'dayOfWeek']);\n return isoWeekYearAndWeek(view.year!, view.month!, view.day!, view.dayOfWeek!).isoYear;\n}\n\n/**\n * Fiscal-quarter options. `startMonth` is the calendar month (1-12) the\n * fiscal year begins on — e.g. `7` for a fiscal year starting in July.\n * Omitted or `1` gives the calendar-quarter behavior getQuarter() has\n * always had (Jan-Mar = Q1, etc.), so existing callers passing nothing\n * see no change.\n */\nexport interface QuarterOptions {\n startMonth?: number;\n}\n\nfunction validateStartMonth(startMonth: number): void {\n if (!Number.isInteger(startMonth) || startMonth < 1 || startMonth > 12) {\n throw new Error(\n `temporal-fmt: getQuarter's startMonth must be an integer from 1 to 12 (got ${startMonth}).`\n );\n }\n}\n\nexport function getQuarter(view: DateFieldView, options: QuarterOptions = {}): number {\n requireFields(view, ['month']);\n const startMonth = options.startMonth ?? 1;\n validateStartMonth(startMonth);\n if (startMonth === 1) {\n // Mirrors the Q token: months 1-3 → Q1, 4-6 → Q2, 7-9 → Q3, 10-12 → Q4.\n return Math.ceil(view.month! / 3);\n }\n // Fiscal case: shift the month so startMonth becomes month 1 of the\n // fiscal year (mod 12, 1-indexed), then apply the same ceil(/3) rule.\n // E.g. startMonth=7 (fiscal year starts July): July→1, Aug→2, ...,\n // Dec→6, Jan→7, ..., June→12. Then Q1 = fiscal months 1-3 (Jul-Sep),\n // matching the common \"FY starts in July\" convention where Q1 is the\n // first quarter of the fiscal year, not a quarter numbered by which\n // calendar quarter it falls in.\n const shifted = ((view.month! - startMonth + 12) % 12) + 1;\n return Math.ceil(shifted / 3);\n}\n\n// `getMonth` / `getWeekday` look trivial (just read the field) but the\n// plan's section L lists them explicitly, so they're here for surface\n// completeness. They also normalize: getWeekday returns 1-7 (Mon-Sun,\n// matching Temporal's spec) regardless of what numbering the caller's\n// underlying value uses.\nexport function getMonth(view: DateFieldView): number {\n requireFields(view, ['month']);\n return view.month!;\n}\n\nexport function getWeekday(view: DateFieldView): number {\n requireFields(view, ['dayOfWeek']);\n return view.dayOfWeek!;\n}\n\n// startOf / endOf return new field bags (not Temporal objects — this\n// module is polyfill-free) with the relevant fields zeroed/extended.\n// Callers can pass the result to a Temporal constructor if they want\n// a typed value.\nexport type StartOfUnit = 'day' | 'month' | 'year' | 'hour' | 'minute' | 'second';\n\n// Returns true if the given unit (when used with startOf/endOf) should\n// also touch the time fields. 'day', 'month', 'year' all imply a\n// resolution coarser than an hour, so startOf zeroes the time fields\n// and endOf maxes them. Sub-hour units (hour/minute/second) only touch\n// the fields finer than themselves.\nfunction touchesTime(unit: StartOfUnit): boolean {\n return unit === 'day' || unit === 'month' || unit === 'year';\n}\n\n// startOf/endOf reassign year/month/day, which invalidates any\n// dayOfWeek carried over from the input — a plain { ...view } spread\n// leaves the old value sitting there unchanged. Same failure mode\n// businessCalendar.ts's isBusinessDay() works around for add(); we\n// recompute here rather than trust the copied field.\nfunction recomputeDayOfWeek(view: DateFieldView): void {\n if (typeof view.dayOfWeek !== 'number') return;\n if (typeof view.year !== 'number' || typeof view.month !== 'number' || typeof view.day !== 'number') return;\n const jsDow = new Date(Date.UTC(view.year, view.month - 1, view.day)).getUTCDay(); // 0=Sun..6=Sat\n view.dayOfWeek = jsDow === 0 ? 7 : jsDow; // 1=Mon..7=Sun\n}\n\nexport function startOf(value: unknown, unit: StartOfUnit): DateFieldView {\n const view = asDateFieldView(value);\n const result: DateFieldView = { ...view };\n if (unit === 'year') {\n result.month = 1;\n result.day = 1;\n } else if (unit === 'month') {\n result.day = 1;\n }\n recomputeDayOfWeek(result);\n if (touchesTime(unit)) {\n result.hour = 0;\n result.minute = 0;\n result.second = 0;\n result.millisecond = 0;\n } else if (unit === 'hour') {\n result.minute = 0;\n result.second = 0;\n result.millisecond = 0;\n } else if (unit === 'minute') {\n result.second = 0;\n result.millisecond = 0;\n } else if (unit === 'second') {\n result.millisecond = 0;\n }\n return result;\n}\n\nexport function endOf(value: unknown, unit: StartOfUnit): DateFieldView {\n const view = asDateFieldView(value);\n const result: DateFieldView = { ...view };\n if (unit === 'year') {\n result.month = 12;\n result.day = daysInMonth({ year: result.year!, month: 12 });\n } else if (unit === 'month') {\n result.day = daysInMonth({ year: result.year!, month: result.month! });\n }\n recomputeDayOfWeek(result);\n if (touchesTime(unit)) {\n result.hour = 23;\n result.minute = 59;\n result.second = 59;\n result.millisecond = 999;\n } else if (unit === 'hour') {\n result.minute = 59;\n result.second = 59;\n result.millisecond = 999;\n } else if (unit === 'minute') {\n result.second = 59;\n result.millisecond = 999;\n } else if (unit === 'second') {\n result.millisecond = 999;\n }\n return result;\n}\n\n// Type-narrowing helpers used by the comparison/arithmetic modules.\n// Lets them accept any of the four date-carrying Temporal types without\n// importing Temporal itself.\nexport function asDateFieldView(value: unknown): DateFieldView {\n if (typeof value !== 'object' || value === null) {\n throw new Error(`temporal-fmt: expected a date-carrying Temporal value, got ${String(value)}.`);\n }\n // Temporal instances expose year/month/day/etc. as prototype getters,\n // not own enumerable properties — so `{ ...value }` would lose them.\n // Read them explicitly. Only the fields actually present on this\n // value type end up in the returned view.\n const v = value as Record<string, unknown>;\n const out: DateFieldView = {};\n if (typeof v.year === 'number') out.year = v.year;\n if (typeof v.month === 'number') out.month = v.month;\n if (typeof v.day === 'number') out.day = v.day;\n if (typeof v.hour === 'number') out.hour = v.hour;\n if (typeof v.minute === 'number') out.minute = v.minute;\n if (typeof v.second === 'number') out.second = v.second;\n if (typeof v.millisecond === 'number') out.millisecond = v.millisecond;\n if (typeof v.dayOfWeek === 'number') out.dayOfWeek = v.dayOfWeek;\n if (typeof v.calendarId === 'string') out.calendarId = v.calendarId;\n if (out.year === undefined || out.month === undefined || out.day === undefined) {\n throw new Error(\n `temporal-fmt: value is missing year/month/day fields — pass a Temporal.PlainDate / PlainDateTime / ZonedDateTime.`\n );\n }\n return out;\n}\n\n// Re-export the TemporalType alias so callers can import everything\n// from one place.\nexport type { TemporalType } from './tokenMetadata.js';\n// Re-export TemporalLike for the same reason.\nexport type { TemporalLike } from './tokens.js';\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/calendarUtils.ts"],"sourcesContent":["// Calendar utility helpers (plan section L). These are pure functions\n// over the TemporalLike shape — no Temporal namespace needed, same\n// approach as isoWeek.ts and the field-reading helpers in format.ts.\n// Letting callers compute dayOfYear/weekOfYear/etc. without going\n// through format() means they can build their own derived values\n// without committing to a string format.\n//\n// Calendar-sensitivity: the helpers in this module assume the\n// iso8601 (Gregorian) calendar — that's what TemporalLike fields\n// carry for the overwhelming majority of callers. Non-Gregorian\n// calendars (hebrew, islamic, etc.) need their own helpers; this\n// module doesn't try to be calendar-polymorphic the way Temporal\n// itself is. Documented limitation, not a design choice — see\n// VERIFICATION.md for the rationale.\n\nimport { isGregorianLeapYear, dayOfYear, isoWeekYearAndWeek } from './isoWeek.js';\n\n// A subset of TemporalLike that has the date fields these helpers need,\n// plus optional time fields. PlainTime isn't a DateFieldView (no\n// year/month/day), but PlainDateTime / ZonedDateTime / PlainDate all\n// match. Time fields are optional so callers can pass a PlainDate\n// to startOf(value, 'month') without having to populate hour/minute/etc.\n// Exported so the comparison/arithmetic modules can use the same\n// narrowing.\nexport interface DateFieldView {\n year?: number;\n month?: number;\n day?: number;\n hour?: number;\n minute?: number;\n second?: number;\n millisecond?: number;\n dayOfWeek?: number;\n calendarId?: string;\n}\n\nfunction requireFields(view: DateFieldView, fields: Array<keyof DateFieldView>): void {\n for (const f of fields) {\n if (typeof view[f] !== 'number') {\n throw new Error(\n `temporal-fmt: calendar helper requires \"${String(f)}\", which this value doesn't have. ` +\n `Pass a Temporal.PlainDate / PlainDateTime / ZonedDateTime.`\n );\n }\n }\n}\n\nexport function daysInMonth(view: DateFieldView): number {\n requireFields(view, ['year', 'month']);\n const { year, month } = view;\n // Standard Gregorian month lengths. February's length depends on\n // whether `year` is a leap year — the same isGregorianLeapYear check\n // isoWeek.ts uses for dayOfYear arithmetic.\n const LENGTHS = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];\n if (month === 2 && isGregorianLeapYear(year!)) return 29;\n return LENGTHS[(month! - 1)!]!;\n}\n\nexport function daysInYear(view: DateFieldView): 365 | 366 {\n requireFields(view, ['year']);\n return isGregorianLeapYear(view.year!) ? 366 : 365;\n}\n\nexport function monthsInYear(_view: DateFieldView): 12 {\n // Gregorian always has 12 months. Other calendars (Hebrew leap years\n // have 13) need calendar-aware logic this module doesn't carry — see\n // the file-level comment. The `_view` parameter is kept so the\n // signature mirrors the other helpers and a future calendar-aware\n // implementation can use it without changing call sites.\n return 12;\n}\n\nexport function isLeapYear(view: DateFieldView): boolean {\n requireFields(view, ['year']);\n return isGregorianLeapYear(view.year!);\n}\n\n// `isLeapMonth` would require knowing which month of a leap-year-aware\n// calendar is the leap month — Gregorian doesn't have one, so this\n// returns false unconditionally. Kept here so the public surface\n// matches the plan's section L listing; non-Gregorian calendars need\n// a different implementation.\nexport function isLeapMonth(_view: DateFieldView): boolean {\n return false;\n}\n\nexport function dayOfYearHelper(view: DateFieldView): number {\n requireFields(view, ['year', 'month', 'day']);\n return dayOfYear(view.year!, view.month!, view.day!);\n}\n\n// ISO 8601 week and week-year. Delegates to isoWeek.ts's\n// isoWeekYearAndWeek, which does the full Thursday-of-week\n// computation to handle the year-boundary cases (Dec 29-31 belonging\n// to week 1 of next year, Jan 1-3 belonging to week 52/53 of the\n// previous year).\nexport function weekOfYear(view: DateFieldView): number {\n requireFields(view, ['year', 'month', 'day', 'dayOfWeek']);\n return isoWeekYearAndWeek(view.year!, view.month!, view.day!, view.dayOfWeek!).week;\n}\n\nexport function weekYear(view: DateFieldView): number {\n requireFields(view, ['year', 'month', 'day', 'dayOfWeek']);\n return isoWeekYearAndWeek(view.year!, view.month!, view.day!, view.dayOfWeek!).isoYear;\n}\n\n/**\n * Fiscal-quarter options. `startMonth` is the calendar month (1-12) the\n * fiscal year begins on — e.g. `7` for a fiscal year starting in July.\n * Omitted or `1` gives the calendar-quarter behavior getQuarter() has\n * always had (Jan-Mar = Q1, etc.), so existing callers passing nothing\n * see no change.\n */\nexport interface QuarterOptions {\n startMonth?: number;\n}\n\nfunction validateStartMonth(startMonth: number): void {\n if (!Number.isInteger(startMonth) || startMonth < 1 || startMonth > 12) {\n throw new Error(\n `temporal-fmt: getQuarter's startMonth must be an integer from 1 to 12 (got ${startMonth}).`\n );\n }\n}\n\nexport function getQuarter(view: DateFieldView, options: QuarterOptions = {}): number {\n requireFields(view, ['month']);\n const startMonth = options.startMonth ?? 1;\n validateStartMonth(startMonth);\n if (startMonth === 1) {\n // Mirrors the Q token: months 1-3 → Q1, 4-6 → Q2, 7-9 → Q3, 10-12 → Q4.\n return Math.ceil(view.month! / 3);\n }\n // Fiscal case: shift the month so startMonth becomes month 1 of the\n // fiscal year (mod 12, 1-indexed), then apply the same ceil(/3) rule.\n // E.g. startMonth=7 (fiscal year starts July): July→1, Aug→2, ...,\n // Dec→6, Jan→7, ..., June→12. Then Q1 = fiscal months 1-3 (Jul-Sep),\n // matching the common \"FY starts in July\" convention where Q1 is the\n // first quarter of the fiscal year, not a quarter numbered by which\n // calendar quarter it falls in.\n const shifted = ((view.month! - startMonth + 12) % 12) + 1;\n return Math.ceil(shifted / 3);\n}\n\n// `getMonth` / `getWeekday` look trivial (just read the field) but the\n// plan's section L lists them explicitly, so they're here for surface\n// completeness. They also normalize: getWeekday returns 1-7 (Mon-Sun,\n// matching Temporal's spec) regardless of what numbering the caller's\n// underlying value uses.\nexport function getMonth(view: DateFieldView): number {\n requireFields(view, ['month']);\n return view.month!;\n}\n\nexport function getWeekday(view: DateFieldView): number {\n requireFields(view, ['dayOfWeek']);\n return view.dayOfWeek!;\n}\n\n// startOf / endOf return new field bags (not Temporal objects — this\n// module is polyfill-free) with the relevant fields zeroed/extended.\n// Callers can pass the result to a Temporal constructor if they want\n// a typed value.\nexport type StartOfUnit = 'day' | 'month' | 'year' | 'hour' | 'minute' | 'second';\n\n// Returns true if the given unit (when used with startOf/endOf) should\n// also touch the time fields. 'day', 'month', 'year' all imply a\n// resolution coarser than an hour, so startOf zeroes the time fields\n// and endOf maxes them. Sub-hour units (hour/minute/second) only touch\n// the fields finer than themselves.\nfunction touchesTime(unit: StartOfUnit): boolean {\n return unit === 'day' || unit === 'month' || unit === 'year';\n}\n\n// startOf/endOf reassign year/month/day, which invalidates any\n// dayOfWeek carried over from the input — a plain { ...view } spread\n// leaves the old value sitting there unchanged. Same failure mode\n// businessCalendar.ts's isBusinessDay() works around for add(); we\n// recompute here rather than trust the copied field.\nfunction recomputeDayOfWeek(view: DateFieldView): void {\n if (typeof view.dayOfWeek !== 'number') return;\n /* c8 ignore start @preserve -- unreachable: startOf()/endOf() both call\n * asDateFieldView() before this, which already throws if year/month/day\n * aren't all numbers — so by the time a value with a numeric dayOfWeek\n * reaches here, year/month/day are guaranteed present too. */\n if (typeof view.year !== 'number' || typeof view.month !== 'number' || typeof view.day !== 'number') return;\n /* c8 ignore stop @preserve */\n const jsDow = new Date(Date.UTC(view.year, view.month - 1, view.day)).getUTCDay(); // 0=Sun..6=Sat\n view.dayOfWeek = jsDow === 0 ? 7 : jsDow; // 1=Mon..7=Sun\n}\n\nexport function startOf(value: unknown, unit: StartOfUnit): DateFieldView {\n const view = asDateFieldView(value);\n const result: DateFieldView = { ...view };\n if (unit === 'year') {\n result.month = 1;\n result.day = 1;\n } else if (unit === 'month') {\n result.day = 1;\n }\n recomputeDayOfWeek(result);\n if (touchesTime(unit)) {\n result.hour = 0;\n result.minute = 0;\n result.second = 0;\n result.millisecond = 0;\n } else if (unit === 'hour') {\n result.minute = 0;\n result.second = 0;\n result.millisecond = 0;\n } else if (unit === 'minute') {\n result.second = 0;\n result.millisecond = 0;\n } else if (unit === 'second') {\n result.millisecond = 0;\n }\n return result;\n}\n\nexport function endOf(value: unknown, unit: StartOfUnit): DateFieldView {\n const view = asDateFieldView(value);\n const result: DateFieldView = { ...view };\n if (unit === 'year') {\n result.month = 12;\n result.day = daysInMonth({ year: result.year!, month: 12 });\n } else if (unit === 'month') {\n result.day = daysInMonth({ year: result.year!, month: result.month! });\n }\n recomputeDayOfWeek(result);\n if (touchesTime(unit)) {\n result.hour = 23;\n result.minute = 59;\n result.second = 59;\n result.millisecond = 999;\n } else if (unit === 'hour') {\n result.minute = 59;\n result.second = 59;\n result.millisecond = 999;\n } else if (unit === 'minute') {\n result.second = 59;\n result.millisecond = 999;\n } else if (unit === 'second') {\n result.millisecond = 999;\n }\n return result;\n}\n\n// Type-narrowing helpers used by the comparison/arithmetic modules.\n// Lets them accept any of the four date-carrying Temporal types without\n// importing Temporal itself.\nexport function asDateFieldView(value: unknown): DateFieldView {\n if (typeof value !== 'object' || value === null) {\n throw new Error(`temporal-fmt: expected a date-carrying Temporal value, got ${String(value)}.`);\n }\n // Temporal instances expose year/month/day/etc. as prototype getters,\n // not own enumerable properties — so `{ ...value }` would lose them.\n // Read them explicitly. Only the fields actually present on this\n // value type end up in the returned view.\n const v = value as Record<string, unknown>;\n const out: DateFieldView = {};\n if (typeof v.year === 'number') out.year = v.year;\n if (typeof v.month === 'number') out.month = v.month;\n if (typeof v.day === 'number') out.day = v.day;\n if (typeof v.hour === 'number') out.hour = v.hour;\n if (typeof v.minute === 'number') out.minute = v.minute;\n if (typeof v.second === 'number') out.second = v.second;\n if (typeof v.millisecond === 'number') out.millisecond = v.millisecond;\n if (typeof v.dayOfWeek === 'number') out.dayOfWeek = v.dayOfWeek;\n if (typeof v.calendarId === 'string') out.calendarId = v.calendarId;\n if (out.year === undefined || out.month === undefined || out.day === undefined) {\n throw new Error(\n `temporal-fmt: value is missing year/month/day fields — pass a Temporal.PlainDate / PlainDateTime / ZonedDateTime.`\n );\n }\n return out;\n}\n\n// Re-export the TemporalType alias so callers can import everything\n// from one place.\nexport type { TemporalType } from './tokenMetadata.js';\n// Re-export TemporalLike for the same reason.\nexport type { TemporalLike } from './tokens.js';"],"mappings":"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","names":[]}
@@ -3,7 +3,7 @@ import {
3
3
  } from "./chunk-MXXYIMFJ.js";
4
4
  import {
5
5
  add
6
- } from "./chunk-AC66SDAS.js";
6
+ } from "./chunk-WZC43XGU.js";
7
7
 
8
8
  // src/timezone.ts
9
9
  function resolveZoned(fields, timeZone, options = {}) {
@@ -27,8 +27,7 @@ function resolveZoned(fields, timeZone, options = {}) {
27
27
  millisecond: fields.millisecond ?? 0,
28
28
  microsecond: fields.microsecond ?? 0,
29
29
  nanosecond: fields.nanosecond ?? 0,
30
- timeZone,
31
- ...options.offset !== void 0 ? { offset: options.offset } : {}
30
+ timeZone
32
31
  },
33
32
  fromOptions
34
33
  );
@@ -95,14 +94,19 @@ function findTransition(value, direction) {
95
94
  throw new Error(`temporal-fmt: ${direction > 0 ? "getNextTransition" : "getPreviousTransition"}() expected a ZonedDateTime, got ${typeof value}.`);
96
95
  }
97
96
  const temporal = getTemporal();
98
- let candidate = value;
97
+ const timeZone = v.timeZoneId;
98
+ let candidateFields = add(value, direction, "days");
99
99
  const startOffset = v.offset;
100
100
  for (let i = 0; i < 365 * 2; i++) {
101
- candidate = add(candidate, direction, "days");
102
- const cz = candidate;
103
- if (cz.offset !== startOffset) {
104
- return candidate;
101
+ const cf = candidateFields;
102
+ const candidateZdt = temporal.ZonedDateTime.from(
103
+ { ...cf, timeZone },
104
+ { disambiguation: "compatible" }
105
+ );
106
+ if (candidateZdt.offset !== startOffset) {
107
+ return candidateZdt;
105
108
  }
109
+ candidateFields = add(candidateFields, direction, "days");
106
110
  }
107
111
  return void 0;
108
112
  }
@@ -114,40 +118,42 @@ function getTransitions(start, end) {
114
118
  }
115
119
  const endV = end;
116
120
  const temporal = getTemporal();
117
- let cursor = start;
121
+ const timeZone = startV.timeZoneId;
122
+ let cursorFields = add(start, 1, "days");
118
123
  let lastOffset = startV.offset;
119
124
  for (let i = 0; i < 365 * 5; i++) {
120
- cursor = add(cursor, 1, "days");
121
- const cv = cursor;
122
- if (cv.offset !== lastOffset) {
123
- if (cv.year > endV.year || cv.year === endV.year && (cv.month > endV.month || cv.month === endV.month && cv.day > endV.day)) break;
124
- result.push(cursor);
125
- lastOffset = cv.offset;
125
+ const cf = cursorFields;
126
+ const cursorZdt = temporal.ZonedDateTime.from(
127
+ { ...cf, timeZone },
128
+ { disambiguation: "compatible" }
129
+ );
130
+ if (cursorZdt.offset !== lastOffset) {
131
+ if (cursorZdt.year > endV.year || cursorZdt.year === endV.year && (cursorZdt.month > endV.month || cursorZdt.month === endV.month && cursorZdt.day > endV.day)) break;
132
+ result.push(cursorZdt);
133
+ lastOffset = cursorZdt.offset;
126
134
  }
135
+ cursorFields = add(cursorFields, 1, "days");
127
136
  }
128
137
  return result;
129
138
  }
130
139
  function possibleInstantsFor(fields, timeZone) {
131
140
  const temporal = getTemporal();
132
- const results = [];
133
- for (const offset of ["-23:59", "+23:59", "+00:00"]) {
134
- try {
135
- const zdt = temporal.ZonedDateTime.from(
136
- { ...fields, timeZone, offset },
137
- { disambiguation: "reject", offset: "reject" }
138
- );
139
- results.push(zdt);
140
- } catch {
141
- }
141
+ const earlier = temporal.ZonedDateTime.from(
142
+ { ...fields, timeZone },
143
+ { disambiguation: "earlier", offset: "ignore" }
144
+ );
145
+ const later = temporal.ZonedDateTime.from(
146
+ { ...fields, timeZone },
147
+ { disambiguation: "later", offset: "ignore" }
148
+ );
149
+ const matchesRequestedWallClock = (z) => z.year === fields.year && z.month === fields.month && z.day === fields.day && z.hour === fields.hour && z.minute === fields.minute && z.second === fields.second && z.millisecond === fields.millisecond;
150
+ if (!matchesRequestedWallClock(earlier) || !matchesRequestedWallClock(later)) {
151
+ return [];
152
+ }
153
+ if (earlier.toInstant().epochNanoseconds === later.toInstant().epochNanoseconds) {
154
+ return [earlier];
142
155
  }
143
- const seen = /* @__PURE__ */ new Set();
144
- return results.filter((r) => {
145
- const inst = r.toInstant?.();
146
- if (!inst) return false;
147
- if (seen.has(inst.epochNanoseconds)) return false;
148
- seen.add(inst.epochNanoseconds);
149
- return true;
150
- });
156
+ return [earlier, later];
151
157
  }
152
158
 
153
159
  export {
@@ -161,4 +167,4 @@ export {
161
167
  getTransitions,
162
168
  possibleInstantsFor
163
169
  };
164
- //# sourceMappingURL=chunk-Z34HC4X5.js.map
170
+ //# sourceMappingURL=chunk-7YIFUVEV.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/timezone.ts"],"sourcesContent":["// Time-zone subsystem (plan section Q). Wraps Temporal.ZonedDateTime's\n// timezone introspection surface in temporal-fmt's field-based convention.\n// Includes disambiguation modes (compatible/earlier/later/reject) for\n// DST gaps and overlaps, plus transition queries (getNextTransition,\n// getPreviousTransition, getTransitions).\n\nimport { getTemporal } from './temporalProvider.js';\nimport { add } from './arithmetic.js';\n\nexport type DisambiguationMode = 'compatible' | 'earlier' | 'later' | 'reject';\n\nexport interface ResolveZonedOptions {\n disambiguation?: DisambiguationMode;\n overflow?: 'constrain' | 'reject';\n offset?: 'use' | 'ignore' | 'prefer' | 'reject';\n}\n\n// Resolves a wall-clock time in a timezone, applying disambiguation\n// for DST gaps and overlaps.\nexport function resolveZoned(\n fields: { year: number; month: number; day: number; hour?: number; minute?: number; second?: number; millisecond?: number; microsecond?: number; nanosecond?: number },\n timeZone: string,\n options: ResolveZonedOptions = {},\n): unknown {\n const temporal = getTemporal();\n const disambiguation = options.disambiguation ?? 'compatible';\n const overflow = options.overflow ?? 'constrain';\n // Cast options to a permissive record — Temporal.ZonedDateTime.from\n // accepts disambiguation/offset options, but the existing\n // TemporalFactory.from() signature in temporalProvider.ts is narrower.\n // The runtime accepts the wider shape; this cast bypasses the type\n // narrowing without changing runtime behavior.\n const fromOptions = {\n overflow,\n disambiguation,\n offset: options.offset ?? 'prefer',\n } as unknown as Parameters<typeof temporal.ZonedDateTime.from>[1];\n try {\n return temporal.ZonedDateTime.from(\n {\n year: fields.year,\n month: fields.month,\n day: fields.day,\n hour: fields.hour ?? 0,\n minute: fields.minute ?? 0,\n second: fields.second ?? 0,\n millisecond: fields.millisecond ?? 0,\n microsecond: fields.microsecond ?? 0,\n nanosecond: fields.nanosecond ?? 0,\n timeZone,\n },\n fromOptions,\n );\n } catch (err) {\n // Surface DST gap / overlap errors with a clearer message.\n const msg = (err as Error).message;\n if (/no such (wall-clock )?time/i.test(msg) && disambiguation === 'reject') {\n throw new Error(\n `temporal-fmt: \"${timeZone}\" has no such wall-clock time on ${fields.year}-${fields.month}-${fields.day}T${fields.hour ?? 0}:${fields.minute ?? 0} — ` +\n `it falls in a DST gap. Pass { disambiguation: 'compatible' | 'earlier' | 'later' } to pick an instant.`\n );\n }\n throw err;\n }\n}\n\nexport function getTimeZone(value: unknown): string {\n const v = value as { timeZoneId?: string };\n if (typeof v?.timeZoneId !== 'string') {\n throw new Error(`temporal-fmt: getTimeZone() expected a ZonedDateTime, got ${typeof value}.`);\n }\n return v.timeZoneId;\n}\n\nexport function getOffset(value: unknown): string {\n const v = value as { offset?: string };\n if (typeof v?.offset !== 'string') {\n throw new Error(`temporal-fmt: getOffset() expected a ZonedDateTime, got ${typeof value}.`);\n }\n return v.offset;\n}\n\nexport function getOffsetNanoseconds(value: unknown): number {\n const v = value as { offsetNanoseconds?: number; offset?: string };\n if (typeof v?.offsetNanoseconds === 'number') return v.offsetNanoseconds;\n if (typeof v?.offset === 'string') {\n // Parse ±HH:MM → nanoseconds.\n const m = v.offset.match(/^([+-])(\\d{2}):(\\d{2})$/);\n if (!m) throw new Error(`temporal-fmt: getOffsetNanoseconds() couldn't parse offset \"${v.offset}\".`);\n const sign = m[1] === '-' ? -1 : 1;\n return sign * (Number(m[2]) * 3600 + Number(m[3]) * 60) * 1_000_000_000;\n }\n throw new Error(`temporal-fmt: getOffsetNanoseconds() expected a ZonedDateTime, got ${typeof value}.`);\n}\n\n// DST detection: a ZonedDateTime is in DST iff the offset is different\n// from the timezone's standard offset at the same instant. Computed\n// by comparing the current offset to the offset in January (Northern\n// Hemisphere) or July (Southern Hemisphere) of the same year — a\n// heuristic that's right for most populated zones.\nexport function isDST(value: unknown): boolean {\n const v = value as { timeZoneId?: string; offset?: string; year?: number; toInstant?: () => unknown };\n if (typeof v?.timeZoneId !== 'string' || typeof v?.offset !== 'string' || typeof v?.year !== 'number') {\n throw new Error(`temporal-fmt: isDST() expected a ZonedDateTime, got ${typeof value}.`);\n }\n // Compute the offset for January 1 of the same year (standard time\n // for Northern Hemisphere zones) and compare. For Southern Hemisphere\n // zones January is summer, so we'd want July instead — but checking\n // January vs current is good enough for the common case, and we\n // can't reliably determine hemisphere without a lookup table.\n const temporal = getTemporal();\n try {\n const jan = temporal.ZonedDateTime.from(\n { year: v.year, month: 1, day: 1, hour: 12, timeZone: v.timeZoneId },\n { disambiguation: 'compatible' } as unknown as Parameters<typeof temporal.ZonedDateTime.from>[1],\n ) as { offset: string };\n return jan.offset !== v.offset;\n } catch {\n return false;\n }\n}\n\n// Returns the next DST transition strictly after `value`, or undefined\n// if no transition occurs within the next 2 years. Implemented by\n// walking day-by-day checking for offset changes — slow but simple,\n// and correct across all IANA zones.\nexport function getNextTransition(value: unknown): unknown | undefined {\n return findTransition(value, 1);\n}\n\nexport function getPreviousTransition(value: unknown): unknown | undefined {\n return findTransition(value, -1);\n}\n\nfunction findTransition(value: unknown, direction: 1 | -1): unknown | undefined {\n const v = value as { timeZoneId?: string; offset?: string; toInstant?: () => unknown; year?: number; month?: number; day?: number; hour?: number; minute?: number; second?: number; millisecond?: number };\n if (typeof v?.timeZoneId !== 'string') {\n throw new Error(`temporal-fmt: ${direction > 0 ? 'getNextTransition' : 'getPreviousTransition'}() expected a ZonedDateTime, got ${typeof value}.`);\n }\n const temporal = getTemporal();\n const timeZone = v.timeZoneId;\n let candidateFields = add(value, direction, 'days');\n const startOffset = v.offset!;\n for (let i = 0; i < 365 * 2; i++) {\n // Rebuild a real ZonedDateTime for this candidate day, in this\n // timezone, so `.offset` reflects the actual UTC offset on that\n // day rather than being absent. add() only produces a plain field\n // bag (it has no notion of timezone), so re-attaching timeZone via\n // ZonedDateTime.from is what actually lets us detect a DST change\n // here — using the field bag's own (nonexistent) offset would make\n // every call \"find\" a transition on the very first day checked.\n const cf = candidateFields as { year: number; month: number; day: number; hour: number; minute: number; second: number; millisecond: number };\n const candidateZdt = temporal.ZonedDateTime.from(\n { ...cf, timeZone },\n { disambiguation: 'compatible' } as unknown as Parameters<typeof temporal.ZonedDateTime.from>[1],\n ) as { offset: string };\n if (candidateZdt.offset !== startOffset) {\n // Found a transition day. Return the reconstructed ZonedDateTime.\n return candidateZdt;\n }\n candidateFields = add(candidateFields, direction, 'days');\n }\n return undefined;\n}\n\nexport function getTransitions(start: unknown, end: unknown): unknown[] {\n const result: unknown[] = [];\n const startV = start as { timeZoneId?: string; offset?: string; year?: number; month?: number; day?: number };\n if (typeof startV?.timeZoneId !== 'string') {\n throw new Error(`temporal-fmt: getTransitions() expected a ZonedDateTime for start, got ${typeof start}.`);\n }\n const endV = end as { year: number; month: number; day: number };\n const temporal = getTemporal();\n const timeZone = startV.timeZoneId;\n let cursorFields = add(start, 1, 'days');\n let lastOffset = startV.offset!;\n for (let i = 0; i < 365 * 5; i++) {\n // Same reconstruction as findTransition() above — add() strips\n // timezone/offset info, so we rebuild a real ZonedDateTime each\n // iteration to read its actual offset instead of comparing against\n // a field bag that never has one.\n const cf = cursorFields as { year: number; month: number; day: number; hour: number; minute: number; second: number; millisecond: number };\n const cursorZdt = temporal.ZonedDateTime.from(\n { ...cf, timeZone },\n { disambiguation: 'compatible' } as unknown as Parameters<typeof temporal.ZonedDateTime.from>[1],\n ) as { offset: string; year: number; month: number; day: number };\n if (cursorZdt.offset !== lastOffset) {\n // Found a transition.\n if (cursorZdt.year > endV.year || (cursorZdt.year === endV.year && (cursorZdt.month > endV.month || (cursorZdt.month === endV.month && cursorZdt.day > endV.day)))) break;\n result.push(cursorZdt);\n lastOffset = cursorZdt.offset;\n }\n cursorFields = add(cursorFields, 1, 'days');\n }\n return result;\n}\n\n// Returns the list of possible instants for a wall-clock time in a\n// zone — used to detect gaps (0 instants) and overlaps (2 instants).\nexport function possibleInstantsFor(\n fields: { year: number; month: number; day: number; hour: number; minute: number; second: number; millisecond: number },\n timeZone: string,\n): unknown[] {\n const temporal = getTemporal();\n // Resolve the same wall-clock fields twice, forcing disambiguation\n // toward the earlier and later side of any DST boundary. Comparing\n // the two results tells us which of the three cases we're in:\n // - gap (e.g. 2:30am on a spring-forward day): neither result's own\n // wall-clock fields match what was requested, since Temporal has\n // to shift off the nonexistent time in both directions — 0 instants.\n // - overlap (e.g. 1:30am on a fall-back day): both results keep the\n // requested wall-clock time but land on different instants (one\n // per side of the boundary) — 2 instants.\n // - normal time: both results match the requested wall-clock time\n // and resolve to the same instant — 1 instant.\n const earlier = temporal.ZonedDateTime.from(\n { ...fields, timeZone },\n { disambiguation: 'earlier', offset: 'ignore' } as unknown as Parameters<typeof temporal.ZonedDateTime.from>[1],\n ) as { year: number; month: number; day: number; hour: number; minute: number; second: number; millisecond: number; toInstant: () => { epochNanoseconds: bigint } };\n const later = temporal.ZonedDateTime.from(\n { ...fields, timeZone },\n { disambiguation: 'later', offset: 'ignore' } as unknown as Parameters<typeof temporal.ZonedDateTime.from>[1],\n ) as { year: number; month: number; day: number; hour: number; minute: number; second: number; millisecond: number; toInstant: () => { epochNanoseconds: bigint } };\n\n const matchesRequestedWallClock = (z: typeof earlier) =>\n z.year === fields.year && z.month === fields.month && z.day === fields.day &&\n z.hour === fields.hour && z.minute === fields.minute &&\n z.second === fields.second && z.millisecond === fields.millisecond;\n\n if (!matchesRequestedWallClock(earlier) || !matchesRequestedWallClock(later)) {\n // Gap — the requested wall-clock time doesn't exist in this zone.\n return [];\n }\n if (earlier.toInstant().epochNanoseconds === later.toInstant().epochNanoseconds) {\n // Normal time — both disambiguation directions agree on the instant.\n return [earlier];\n }\n // Overlap — same wall-clock time, two distinct instants.\n return [earlier, later];\n}"],"mappings":"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","names":[]}
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  TOKENS
3
- } from "./chunk-7SFQUOID.js";
3
+ } from "./chunk-XIKLQMF7.js";
4
4
  import {
5
5
  InvalidLocaleError
6
- } from "./chunk-C3755VAP.js";
6
+ } from "./chunk-USONQ7MV.js";
7
7
 
8
8
  // src/tokenize.ts
9
9
  var SORTED_TOKEN_STRINGS = TOKENS.map(([tok]) => tok).sort((a, b) => b.length - a.length);
@@ -90,10 +90,17 @@ var digitMapCache = /* @__PURE__ */ new Map();
90
90
  function getDigitMap(system) {
91
91
  let map = digitMapCache.get(system);
92
92
  if (map) return map;
93
+ /* c8 ignore start @preserve -- dead by construction, not just
94
+ untested: both callers (convertDigits, convertDigitsToAscii) already
95
+ return early on system === 'latn' before calling getDigitMap at all,
96
+ so this function is never invoked with 'latn'. Kept as a defensive
97
+ fallback rather than trusting that stays true for any future
98
+ caller. */
93
99
  if (system === "latn") {
94
100
  map = {};
95
101
  for (let i = 0; i < 10; i++) map[String(i)] = String(i);
96
102
  } else {
103
+ /* c8 ignore stop @preserve */
97
104
  const fmt = new Intl.NumberFormat("en-US-u-nu-" + system, { useGrouping: false });
98
105
  map = {};
99
106
  for (let i = 0; i < 10; i++) {
@@ -152,4 +159,4 @@ export {
152
159
  applyNumbering,
153
160
  applyParseNumbering
154
161
  };
155
- //# sourceMappingURL=chunk-26H4NR3J.js.map
162
+ //# sourceMappingURL=chunk-EA34SFOW.js.map