temporal-fmt 0.9.70 → 0.9.71

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 (52) hide show
  1. package/README.md +15 -10
  2. package/dist/{chunk-VXG3VD3D.js → chunk-3SC5AVSY.js} +3 -3
  3. package/dist/{chunk-BQDOTFGC.js → chunk-BQET6RUR.js} +20 -3
  4. package/dist/chunk-BQET6RUR.js.map +1 -0
  5. package/dist/{chunk-UU2XN6N2.js → chunk-CQJYB7E7.js} +4 -4
  6. package/dist/{chunk-CGI4FL6H.js → chunk-FLMOMOAQ.js} +2 -2
  7. package/dist/{chunk-BZPNSQHI.js → chunk-LHR5PCP4.js} +2 -2
  8. package/dist/{chunk-6ZIYGBFN.js → chunk-MXXTJ5CW.js} +4 -4
  9. package/dist/{chunk-OLNU4LON.js → chunk-QRLQ7RR7.js} +2 -1
  10. package/dist/chunk-QRLQ7RR7.js.map +1 -0
  11. package/dist/{chunk-N4SYRZIP.js → chunk-ZT3UXRAA.js} +34 -6
  12. package/dist/chunk-ZT3UXRAA.js.map +1 -0
  13. package/dist/{chunk-6WKSAP4Y.js → chunk-ZTIF4ZHX.js} +8 -8
  14. package/dist/chunk-ZTIF4ZHX.js.map +1 -0
  15. package/dist/config.d.cts +2 -2
  16. package/dist/config.d.ts +2 -2
  17. package/dist/duration.cjs.map +1 -1
  18. package/dist/duration.js +2 -2
  19. package/dist/format.cjs +71 -1
  20. package/dist/format.cjs.map +1 -1
  21. package/dist/format.js +4 -4
  22. package/dist/index.cjs +43 -2
  23. package/dist/index.cjs.map +1 -1
  24. package/dist/index.d.cts +1 -1
  25. package/dist/index.d.ts +1 -1
  26. package/dist/index.js +9 -9
  27. package/dist/index.js.map +1 -1
  28. package/dist/interval.cjs +43 -2
  29. package/dist/interval.cjs.map +1 -1
  30. package/dist/interval.js +8 -8
  31. package/dist/localeRegistry.cjs.map +1 -1
  32. package/dist/localeRegistry.js +2 -2
  33. package/dist/localeVocab.d.cts +4 -3
  34. package/dist/localeVocab.d.ts +4 -3
  35. package/dist/numbering.d.cts +4 -2
  36. package/dist/numbering.d.ts +4 -2
  37. package/dist/parse.cjs +42 -1
  38. package/dist/parse.cjs.map +1 -1
  39. package/dist/parse.js +4 -4
  40. package/dist/relativeTime.cjs.map +1 -1
  41. package/dist/relativeTime.js +3 -3
  42. package/package.json +2 -2
  43. package/scripts/versions.json +5 -0
  44. package/dist/chunk-6WKSAP4Y.js.map +0 -1
  45. package/dist/chunk-BQDOTFGC.js.map +0 -1
  46. package/dist/chunk-N4SYRZIP.js.map +0 -1
  47. package/dist/chunk-OLNU4LON.js.map +0 -1
  48. /package/dist/{chunk-VXG3VD3D.js.map → chunk-3SC5AVSY.js.map} +0 -0
  49. /package/dist/{chunk-UU2XN6N2.js.map → chunk-CQJYB7E7.js.map} +0 -0
  50. /package/dist/{chunk-CGI4FL6H.js.map → chunk-FLMOMOAQ.js.map} +0 -0
  51. /package/dist/{chunk-BZPNSQHI.js.map → chunk-LHR5PCP4.js.map} +0 -0
  52. /package/dist/{chunk-6ZIYGBFN.js.map → chunk-MXXTJ5CW.js.map} +0 -0
package/README.md CHANGED
@@ -79,7 +79,6 @@ The package looks large on npm — locales, recurrence, business calendars, time
79
79
  - [Mods (advanced, optional)](#mods-advanced-optional) — full guide in [MODS.md](./MODS.md)
80
80
  - [Subpath imports](#subpath-imports)
81
81
  - [Migrating from Day.js or date-fns](#migrating-from-dayjs-or-date-fns)
82
- - [Known limitations](#known-limitations)
83
82
  - [Related tools](#related-tools)
84
83
  - [Testing](#testing)
85
84
  - [Contributing](#contributing)
@@ -199,6 +198,8 @@ parse('yyyy-Md', '2026-121', { lenient: true }).toString() // '2026-12-01'
199
198
 
200
199
  **Heuristic**: if one of the tokens in the ambiguous run is `d` (day), prefer the split where the day value is ≤ 12. The reasoning: someone who glues a run like `"121"` into an `Md` format is more likely to mean Dec 1 (M=12, d=1) than Jan 21 (M=1, d=21) — if they meant Jan 21, they'd more often write it with a separator or padding (`"1/21"`, `"01/21"`). It's not a guarantee, which is exactly why it's opt-in. When the heuristic doesn't narrow it down (both splits have day ≤ 12), or when there's no `d` token in the run at all (e.g. `Hm`), it falls back to the first valid split — deterministic, but arbitrary. Default behavior (lenient unset or `false`) is unchanged either way.
201
200
 
201
+ Ambiguity is also easy to avoid outright — the same fixes the heuristic's reasoning hints at: zero-pad the tokens (`MM`/`dd`) or put a literal separator between them (`M/d`), and there's only ever one split to find. Unambiguous glued runs parse fine in strict mode: `"85"` against `yyyy-Md` has exactly one valid split (month 8, day 5), so only genuinely ambiguous input throws. One structural case always throws regardless of ambiguity: a glued run in a format string with no `yyyy` at all (`Md`, `dM`, `Hm` on their own) can never produce a date, since `parse()` needs year, month, and day together to build one (the incomplete-date error in [Typed errors](#typed-errors)).
202
+
202
203
  ### Offset tokens (`X`/`XX`/`XXX`/`x`/`xx`/`xxx`)
203
204
 
204
205
  The six offset tokens only work on `ZonedDateTime`. On a `PlainDate`/`PlainTime`/`PlainDateTime` they throw the same "requires offset, which this Temporal object doesn't have" error every other field-typed token throws when its field is missing.
@@ -207,6 +208,8 @@ Uppercase (`X`/`XX`/`XXX`) collapses `+00:00` to `Z` for UTC. Lowercase (`x`/`xx
207
208
 
208
209
  On parse, an offset token needs a full date and time to anchor the instant — same rule `zzz` enforces. With an offset token and no `zzz`, the resulting `ZonedDateTime`'s `timeZoneId` is the offset string itself (e.g. `"+09:00"`). With **both** `zzz` and an offset token, it's a cross-check: `zzz` wins for the result's `timeZoneId` (the IANA name is the meaningful label), and the offset token's value must match that zone's actual offset at the parsed instant. Disagreement throws rather than silently picking one — `parse('yyyy-MM-dd HH:mm zzz XXX', '2026-08-04 15:45 America/New_York +09:00')` throws, because August in New York is `-04:00`, not `+09:00`.
209
210
 
211
+ Sub-minute historical offsets: the tokens read `ZonedDateTime`'s `offset` field, which is `±HH:MM` for any modern date but carries a seconds component for pre-railway local-mean-time zones (Europe/London before 1847 surfaces `-00:01:15`). `xxx` is the one variant with a slot for that — it passes a sub-minute offset through verbatim — while the other five throw a descriptive error recommending `xxx` rather than silently truncating the seconds away. Parse-side offset shapes have no seconds group, so a sub-minute offset can't be parsed back through a token; construct the `ZonedDateTime` directly if you need to round-trip one of those.
212
+
210
213
  Range: `-12:00` to `+14:00`, the IANA-supported range. Out-of-range values throw a descriptive error naming the bound.
211
214
 
212
215
  ## Locales
@@ -832,6 +835,17 @@ parse('yyyy-MM-dd', '٢٠٢٦-٠٨-٠٤', { parseNumberingSystem: 'arab' }).toSt
832
835
 
833
836
  The two option names are deliberately different (`numberingSystem` vs. `parseNumberingSystem`), not a naming inconsistency — the two directions aren't always symmetric. You might want Arabic-Indic digits in your UI output without expecting Arabic-Indic digits back on input, or the reverse, so a caller mixing `format()` and `parse()` options in one config object can set each independently. `formatToParts()` applies numbering per-part rather than once at the end, so a caller styling individual token parts (one `<span>` per token, say) still gets correctly-transliterated digits in each part instead of plain ASCII. An unsupported system name throws immediately rather than silently falling back to `'latn'`.
834
837
 
838
+ Rather than hardcoding a system code, pass `'auto'` to use whichever numeral system the call's locale itself uses, resolved through `Intl.NumberFormat(locale).resolvedOptions().numberingSystem`:
839
+
840
+ ```js
841
+ format(date, 'yyyy-MM-dd', { numberingSystem: 'auto', locale: 'ar-EG' }); // "٢٠٢٦-٠٨-٠٤" — ar-EG's native digits
842
+ format(date, 'yyyy-MM-dd', { numberingSystem: 'auto', locale: 'bn-BD' }); // "২০২৬-০৮-০৪"
843
+ format(date, 'yyyy-MM-dd', { numberingSystem: 'auto' }); // "2026-08-04" — en-US defaults to latn
844
+ parse('yyyy-MM-dd', '٢٠٢٦-٠٨-٠٤', { parseNumberingSystem: 'auto', locale: 'ar-EG' }); // same resolution, input direction
845
+ ```
846
+
847
+ `'auto'` works the same on both sides and through `createConfig()` (a config's `numberingSystem: 'auto'` resolves per call, against whichever locale that call uses). When the locale's native system isn't one this library can transliterate (Thai digits, Lao, Myanmar, ...), `'auto'` falls back to `'latn'` rather than throwing — only a malformed locale tag throws. Unset still means `'latn'` everywhere; `'auto'` is an opt-in, not a default change.
848
+
835
849
  If you'd rather convert digits yourself instead of going through `format()`/`parse()`'s options — say, transliterating a string that came from somewhere else entirely — the underlying conversion is available directly:
836
850
 
837
851
  ```js
@@ -1018,15 +1032,6 @@ function formatDate(date, formatStr, opts) {
1018
1032
 
1019
1033
  Migrate file by file, dropping the wrapper once nothing calls the old path anymore.
1020
1034
 
1021
- ## Known limitations
1022
-
1023
- - **Numerals default to Western digits** in numeric tokens, regardless of locale, unless you opt into `{ numberingSystem }` / `{ parseNumberingSystem }` — see [Numbering systems](#numbering-systems).
1024
- - **Locale-aware tokens need Node 20+**, native or polyfilled. Untested below that.
1025
- - **You must provide a Temporal implementation** on anything below Node 26 — see [Providing `Temporal`](#providing-temporal).
1026
- - **Pre-1582 dates and locale-aware tokens don't mix well on native Temporal (Node 26+).** `MMMM`/`MMM`/`EEEE`/`EEE` can render the wrong month or weekday for dates before roughly 1582 CE. This is an ICU limitation, not a bug here: ICU's default Gregorian calendar cutover is October 15, 1582, so `Intl.DateTimeFormat.formatToParts()` silently reinterprets earlier dates under the Julian calendar even though `Temporal` itself uses a proleptic Gregorian calendar throughout — see [tc39/ecma402#1003](https://github.com/tc39/ecma402/issues/1003). Numeric tokens never touch `Intl` and aren't affected.
1027
- - **Gluing two unpadded numeric tokens with no separator is ambiguous for some inputs**, and `parse()` throws rather than guessing (`Md`, `dM`, `Hm` against certain input). `"121"` against `yyyy-Md` could mean month 1/day 21 or month 12/day 1 — both valid, no single correct reading. Unambiguous inputs against the same format string parse fine (`"85"` against `yyyy-Md` has only one valid split). Fix it by zero-padding (`MM`/`dd`), adding a separator, or opting into `{ lenient: true }`. Note that `Md` (or `dM`/`Hm`) with no `yyyy` present always throws regardless of ambiguity — `parse()` needs year, month, and day together to build a date at all.
1028
- - **Offset tokens can't express sub-minute historical offsets.** They read `ZonedDateTime.prototype.offset`, which Temporal exposes as `+HH:MM` for any modern date. Historical LMT offsets with seconds (Europe/London before 1847 was `+00:01:15`) aren't reachable through that field, and the offset tokens' regex shapes don't include a seconds group either. Construct the `ZonedDateTime` directly if you need to round-trip one of those. Offset range is bounded to `-12:00` through `+14:00` (Baker Island to Kiritimati) — `+14:01`/`-12:01` throw even though each digit is individually plausible, since no real zone uses an offset past that range.
1029
-
1030
1035
  ## Related tools
1031
1036
 
1032
1037
  Neither of these ships as part of this repository — separate packages. **Both are now deprecated and archived** — single-maintainer bandwidth, no further updates — but the last published versions still work if you want them:
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  DEFAULT_LOCALE
3
- } from "./chunk-BQDOTFGC.js";
3
+ } from "./chunk-BQET6RUR.js";
4
4
  import {
5
5
  InvalidLocaleError,
6
6
  normalizeLocaleTag
7
- } from "./chunk-OLNU4LON.js";
7
+ } from "./chunk-QRLQ7RR7.js";
8
8
  import {
9
9
  differenceInDays
10
10
  } from "./chunk-SC6ENLEB.js";
@@ -62,4 +62,4 @@ export {
62
62
  formatRelative,
63
63
  formatRelativeToNow
64
64
  };
65
- //# sourceMappingURL=chunk-VXG3VD3D.js.map
65
+ //# sourceMappingURL=chunk-3SC5AVSY.js.map
@@ -7,8 +7,9 @@ import {
7
7
  InvalidLocaleError,
8
8
  canonicalCacheKey,
9
9
  getCustomVocab,
10
- normalizeLocaleTag
11
- } from "./chunk-OLNU4LON.js";
10
+ normalizeLocaleTag,
11
+ partValue
12
+ } from "./chunk-QRLQ7RR7.js";
12
13
  import {
13
14
  dayOfYear,
14
15
  isoWeekYearAndWeek
@@ -71,6 +72,19 @@ function intlSupportsNativeTemporal() {
71
72
  }
72
73
  return nativeSupport;
73
74
  }
75
+ var GREGORIAN_CUTOVER_YEAR = 1582;
76
+ var GREGORIAN_CUTOVER_MONTH = 10;
77
+ var GREGORIAN_CUTOVER_DAY = 15;
78
+ function isBeforeGregorianCutover(t) {
79
+ if (t.year !== GREGORIAN_CUTOVER_YEAR) return Number(t.year) < GREGORIAN_CUTOVER_YEAR;
80
+ if (t.month !== GREGORIAN_CUTOVER_MONTH) return Number(t.month) < GREGORIAN_CUTOVER_MONTH;
81
+ return Number(t.day) < GREGORIAN_CUTOVER_DAY;
82
+ }
83
+ function preCutoverGregorianName(temporal, locale, formatterOptions, partType) {
84
+ const reference = partType === "month" ? new Date(Date.UTC(2020, temporal.month - 1, 1)) : new Date(Date.UTC(2024, 0, temporal.dayOfWeek));
85
+ const formatter = getFormatter(locale, { ...formatterOptions, timeZone: "UTC" });
86
+ return partValue(formatter, reference, partType);
87
+ }
74
88
  function intlPart(temporal, locale, options, partType) {
75
89
  const calendar = temporal?.calendarId;
76
90
  const formatterOptions = {
@@ -83,6 +97,9 @@ function intlPart(temporal, locale, options, partType) {
83
97
  `temporal-fmt: locale-aware part "${partType}" needs a value that implements toLocaleString (a real Temporal object). A plain field bag cannot render locale-aware names \u2014 pass a Temporal.PlainDate/PlainDateTime/ZonedDateTime.`
84
98
  );
85
99
  }
100
+ if ((partType === "month" || partType === "weekday") && formatterOptions.calendar === "gregory" && isBeforeGregorianCutover(temporal)) {
101
+ return preCutoverGregorianName(temporal, locale, formatterOptions, partType);
102
+ }
86
103
  if (!intlSupportsNativeTemporal()) {
87
104
  try {
88
105
  return ls.call(temporal, normalizeLocaleTag(locale), formatterOptions);
@@ -364,4 +381,4 @@ export {
364
381
  getEffectiveTokens,
365
382
  registerToken
366
383
  };
367
- //# sourceMappingURL=chunk-BQDOTFGC.js.map
384
+ //# sourceMappingURL=chunk-BQET6RUR.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/tokens.ts"],"sourcesContent":["/*\n * Copyright 2026 DirazCoder\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { getTemporal, subscribeToTemporalChanges } from './temporalProvider.js';\nimport { canonicalCacheKey, getCustomVocab, normalizeLocaleTag, partValue } from './localeVocab.js';\nimport { InvalidLocaleError, FormatSyntaxError } from './errors.js';\nimport { isoWeekYearAndWeek, dayOfYear } from './isoWeek.js';\n\n// Throws the library's standard typed missing-field error for tokens\n// that READ more fields than their TOKENS-table `field` entry declares\n// (the table only drives format()'s single-field precheck). ww/RRRR read\n// year/month/day/dayOfWeek but precheck only dayOfWeek; D/DD/DDD read\n// year/month/day but precheck only day. Without this, a bag carrying the\n// declared field but missing the rest fed undefined into the math and\n// produced literal \"NaN\" output instead of the descriptive error every\n// other token throws.\nfunction requireFields(t: TemporalLike, token: string, ...fields: Array<keyof TemporalLike>): void {\n for (const field of fields) {\n if (t[field] === undefined) {\n throw new FormatSyntaxError({\n token,\n message:\n `temporal-fmt: token \"${token}\" requires \"${field}\", ` +\n `which this Temporal object doesn't have. ` +\n `(e.g. PlainDate has no time fields, PlainTime has no date fields)`,\n });\n }\n }\n}\n\nexport function pad(n: number, len: number): string {\n // padStart pads the whole string, sign included, so pad(-45, 4) used to\n // come out \"0-45\" instead of \"-045\" — split the sign off first.\n const negative = n < 0;\n const digits = String(Math.abs(n)).padStart(len, '0');\n return negative ? '-' + digits : digits;\n}\n\n// Combines the three sub-second fields Temporal exposes into one 9-digit\n// nanosecond-of-second value, then truncates (never rounds) to the\n// requested width. Truncating matches what every digit-width token in\n// this library already does elsewhere (yy, MM, dd, ...) — the token\n// asked for N digits of precision, not a rounded N-digit approximation.\n// A caller asking for SSS on a value with nanosecond precision gets the\n// leading 3 digits of it, same as they'd get the leading 3 digits of any\n// other multi-digit field this library formats.\n//\n// Attached to pad() rather than declared standalone: the bundler's\n// per-function coverage instrumentation attributes hits to each of the\n// 9 fraction-token arrow functions individually but not to a shared\n// helper they all close over, so a correctly-exercised helper still\n// shows as 0 hits under c8. pad() itself is called directly all over\n// this file and is reliably attributed — routing through it here keeps\n// the coverage numbers honest without duplicating the slice logic\n// across every token entry below.\npad.fraction = function formatFraction(t: TemporalLike, width: number): string {\n const nanoOfSecond = t.millisecond! * 1_000_000 + (t.microsecond ?? 0) * 1_000 + (t.nanosecond ?? 0);\n return pad(nanoOfSecond, 9).slice(0, width);\n};\n\n// Not every field exists on every Temporal type (PlainDate has no .hour,\n// etc). Callers check for undefined before formatting a token.\nexport interface TemporalLike {\n year?: number;\n month?: number;\n day?: number;\n hour?: number;\n minute?: number;\n second?: number;\n millisecond?: number;\n microsecond?: number;\n nanosecond?: number;\n timeZoneId?: string;\n // ZonedDateTime.prototype.offset — `±HH:MM` (6 chars) for any modern\n // date, but pre-1900 local-mean-time zones (e.g. America/New_York\n // before 1883) surface a seconds component too: `±HH:MM:SS` (9\n // chars). Format-side offset tokens must check the length rather\n // than assume 6. Parse-side writes a canonicalized `+HH:MM` here\n // before handing it to Temporal.ZonedDateTime.from as the\n // `timeZone` value.\n offset?: string;\n dayOfWeek?: number; // 1=Mon, 7=Sun, per Temporal spec\n calendarId?: string;\n toInstant?: () => unknown;\n toLocaleString?: (locale: string, options: Intl.DateTimeFormatOptions) => string;\n}\n\nexport interface FormatOptions {\n /** BCP 47 locale tag, e.g. 'en-US', 'fr-FR', 'ar-EG'. Defaults to 'en-US'. */\n locale?: string;\n /**\n * When set on parse(), opts into the lenient split heuristic for ambiguous\n * glued numeric runs (e.g. \"121\" against \"Md\"). Default (false) keeps\n * parse()'s strict behavior — throw on ambiguity rather than guess.\n * See README \"Lenient parse mode\" for the heuristic and why it's opt-in.\n */\n lenient?: boolean;\n}\n\nexport const DEFAULT_LOCALE = 'en-US';\n\n// Intl.DateTimeFormat is expensive to construct and format() can run in a\n// loop (rendering a table of dates), so cache by (locale, options).\nconst formatterCache = new Map<string, Intl.DateTimeFormat>();\nconst MAX_CACHE_SIZE = 500;\n\nfunction getFormatter(locale: string, options: Intl.DateTimeFormatOptions): Intl.DateTimeFormat {\n const key = JSON.stringify([canonicalCacheKey(locale), options]);\n let formatter = formatterCache.get(key);\n if (formatter) {\n return formatter;\n }\n if (formatterCache.size >= MAX_CACHE_SIZE) {\n // not real LRU, just evicts oldest insertion — fine for this key space\n const oldestKey = formatterCache.keys().next().value;\n if (oldestKey !== undefined) formatterCache.delete(oldestKey);\n }\n try {\n formatter = new Intl.DateTimeFormat(normalizeLocaleTag(locale), options);\n } catch (err) {\n // Malformed locale tags reach Intl as a bare RangeError; surface the\n // library's typed error instead (reached via dayPeriodPart — the\n // 'a' token — on any runtime, and the native-Intl path for the rest).\n // Every failure mode of this constructor with a string locale is a\n // RangeError, so converting unconditionally preserves the original\n // message in `reason` either way.\n throw new InvalidLocaleError({ actual: locale, reason: (err as Error).message });\n }\n formatterCache.set(key, formatter);\n return formatter;\n}\n\n// Passing a Temporal object straight into `new Intl.DateTimeFormat().formatToParts()`\n// only works when the engine's Intl implementation has special-cased support for\n// *native* Temporal instances (checked via internal slots and/or gated behind a V8 flag,\n// not tied to a specific Node version).\n//\n// A Temporal polyfill's instances don't have those slots, so the engine falls back to ToNumber() -> .valueOf(),\n// which the polyfill deliberately throws on (\"Cannot use valueOf\").\n// Probed once and memoized and only from intlPart(), so it never\n// runs unless a format string actually uses a locale-aware token.\nlet nativeSupport: boolean | undefined;\n// Invalidate the memoized probe whenever setTemporal() swaps the active\n// implementation — otherwise a probe result from \"is native Temporal\n// supported\" could keep being used after the active implementation is\n// no longer the one that was probed. See setTemporal() in\n// temporalProvider.ts for the other half of this.\nsubscribeToTemporalChanges(() => { nativeSupport = undefined; });\n\nfunction intlSupportsNativeTemporal(): boolean {\n if (nativeSupport === undefined) {\n nativeSupport = false;\n try {\n const temporal = getTemporal();\n new Intl.DateTimeFormat('en-US', { day: 'numeric' })\n .formatToParts(temporal.PlainDate.from({ year: 1970, month: 1, day: 1 }) as Date);\n // Version-gated, not dead — see the matching note on the\n // !intlSupportsNativeTemporal() branch in intlPart() below for\n // why this can't be exercised from this environment.\n /* c8 ignore next */\n nativeSupport = true;\n /* c8 ignore start */\n } catch {\n // native Temporal absent, or present but not recognized by Intl — fall back.\n // Version-gated, not dead: this catch only fires on runtimes where the\n // probe above throws (no native Temporal, or Intl doesn't recognize it).\n // On a runtime with full native support (e.g. Node builds where Intl\n // accepts native Temporal instances directly) the try succeeds and this\n // branch is unreachable — mirror case of the block below.\n }\n /* c8 ignore stop */\n }\n return nativeSupport;\n}\n\n// The Julian → Gregorian cutover, as ICU applies it. ICU's gregory\n// calendar (unlike Temporal's, which is proleptic Gregorian throughout)\n// treats every date before October 15, 1582 as a Julian-calendar date:\n// the Julian calendar ran ~10 days behind proleptic Gregorian in that\n// era, so a date Temporal correctly calls 1500-07-05 gets silently\n// reinterpreted by Intl as Julian 1500-06-25 — the wrong month near\n// boundaries, and a weekday shifted by 10 mod 7 = 3 slots everywhere.\n// See tc39/ecma402#1003. Whether the cutover is even observable varies\n// by engine and entry point (V8's plain-Date path disables it; the\n// native-Temporal formatToParts path on some Node 26 builds does not),\n// so this can't be probed reliably at runtime — pre-cutover dates have\n// to be routed around Intl's calendar math entirely.\nconst GREGORIAN_CUTOVER_YEAR = 1582;\nconst GREGORIAN_CUTOVER_MONTH = 10;\nconst GREGORIAN_CUTOVER_DAY = 15;\n\n// True when the Temporal object's own (proleptic-Gregorian, hence\n// trustworthy) fields place it before the ICU cutover. Deliberately\n// NaN-tolerant rather than undefined-checking: a Temporal type that\n// lacks one of these fields (PlainMonthDay has no year, PlainYearMonth\n// no day) feeds undefined into Number(), every NaN comparison is false,\n// and the object falls out as \"not before the cutover\" onto its existing\n// formatting path — no undefined-specific branches to keep covered, and\n// no behavior change for field-partial types.\nfunction isBeforeGregorianCutover(t: TemporalLike): boolean {\n if (t.year !== GREGORIAN_CUTOVER_YEAR) return Number(t.year) < GREGORIAN_CUTOVER_YEAR;\n if (t.month !== GREGORIAN_CUTOVER_MONTH) return Number(t.month) < GREGORIAN_CUTOVER_MONTH;\n return Number(t.day) < GREGORIAN_CUTOVER_DAY;\n}\n\n// Renders a locale-aware month/weekday name for a pre-cutover date\n// WITHOUT ever handing the date itself to Intl. Intl is used purely as a\n// name lookup table indexed by month/weekday number — never as the thing\n// that computes which month/weekday a historical date falls on, which is\n// the computation the Julian cutover corrupts. The number comes straight\n// off the Temporal object's own fields (proleptic-Gregorian-correct by\n// construction, since that's what Temporal is), and the name comes from\n// formatting a safe modern reference date carrying that same number.\n//\n// The reference dates are deliberately the same ones getLocaleVocab()\n// (localeVocab.ts) uses to build the parse-side vocabulary — 2020-mm-01\n// for months, the Monday-anchored 2024-01-01..07 week for weekdays — so\n// format() output for a pre-cutover date is byte-identical to what\n// parse() matches against, and round-trips keep working. partValue()\n// gives us the same adjacent-literal merging (ja-JP's \"8月\") the vocab\n// builder uses, for the same reason.\nfunction preCutoverGregorianName(\n temporal: TemporalLike,\n locale: string,\n formatterOptions: Intl.DateTimeFormatOptions,\n partType: 'month' | 'weekday'\n): string {\n // timeZone: 'UTC' matters: the references are built via Date.UTC\n // (midnight UTC), and without pinning the formatter's zone a host\n // timezone behind UTC would shift them to the previous local day.\n // month/dayOfWeek are guaranteed present — format()'s field precheck\n // for MMMM/MMM ('month') and EEEE/EEE ('dayOfWeek') ran before the\n // token handler was invoked at all.\n const reference = partType === 'month'\n ? new Date(Date.UTC(2020, temporal.month! - 1, 1))\n : new Date(Date.UTC(2024, 0, temporal.dayOfWeek!));\n const formatter = getFormatter(locale, { ...formatterOptions, timeZone: 'UTC' });\n return partValue(formatter, reference, partType);\n}\n\nfunction intlPart(\n temporal: TemporalLike,\n locale: string,\n options: Intl.DateTimeFormatOptions,\n partType: Intl.DateTimeFormatPartTypes\n): string {\n // Intl throws \"Mismatching Calendars\" if the formatter's calendar doesn't\n // match the object's own (e.g. en-US formatter defaults to gregory, but\n // a hebrew/islamic PlainDate needs its own calendar passed through).\n //\n // For iso8601 specifically, force 'gregory' rather than leaving calendar\n // unset: numeric fields (yyyy/dd, see tokens' pad()-based handlers) are\n // always pulled straight off the object's own ISO fields — so if the\n // *locale* carries a `-u-ca-*` extension (e.g. 'en-u-ca-hebrew') and this\n // step left calendar unset, the formatter would resolve its own default\n // calendar from the locale and format MMMM/EEEE in that calendar while\n // yyyy/dd stay ISO, producing a date that looks internally consistent\n // (a real Hebrew month name next to a real-looking day/year) but names a\n // completely different day than the object actually represents. Forcing\n // 'gregory' here keeps every field of an ISO object's output anchored to\n // the same (ISO/Gregorian) calendar — a locale's calendar extension only\n // takes effect when the object itself already carries a non-ISO calendar\n // (via `.withCalendar()`), matching what the README documents.\n //\n // 'gregory' specifically, not 'iso8601' — passing `calendar: 'iso8601'`\n // explicitly alongside a single-field options object makes\n // formatToParts() come back empty for some reason, but 'gregory' doesn't\n // have that problem and Temporal's iso8601 calendar is Gregorian-shaped\n // (proleptic Gregorian throughout, no Julian cutover) so the two agree\n // on every numeric field this library ever reads.\n const calendar = temporal?.calendarId;\n const formatterOptions: Intl.DateTimeFormatOptions = {\n ...options,\n calendar: calendar && calendar !== 'iso8601' ? calendar : 'gregory',\n };\n\n // Field-bag guard: a plain { year, month, day } object only has\n // Object.prototype.toLocaleString, which ignores both arguments and\n // returns \"[object Object]\". On engines without native Temporal\n // support in Intl this used to be caught below, but on engines where\n // Intl *does* recognize native Temporal instances (Node 26+), a bag\n // skips that branch entirely and reaches formatToParts() directly —\n // which doesn't throw \"[object Object]\", it throws a bare\n // \"RangeError: Invalid time value\" once the bag fails ToNumber()\n // coercion. Neither failure mode is useful to a caller, so check for\n // a real toLocaleString up front, before branching on native support,\n // so the descriptive error fires on every engine.\n const ls = temporal.toLocaleString;\n if (typeof ls !== 'function' || ls === Object.prototype.toLocaleString) {\n throw new Error(\n `temporal-fmt: locale-aware part \"${partType}\" needs a value that implements ` +\n `toLocaleString (a real Temporal object). A plain field bag cannot render ` +\n `locale-aware names — pass a Temporal.PlainDate/PlainDateTime/ZonedDateTime.`\n );\n }\n\n // Pre-1582 cutover guard. ICU's gregory calendar reinterprets dates\n // before October 15, 1582 under Julian-calendar rules (see the long\n // comment on isBeforeGregorianCutover above for the why), so for those\n // dates Intl must never be handed the Temporal object itself — neither\n // through formatToParts() below nor through toLocaleString() in the\n // polyfill branch — since either route lets ICU's calendar math decide\n // which month/weekday the date falls on, and that's exactly what the\n // cutover corrupts. Route month/weekday name lookups through a safe\n // modern reference date instead (preCutoverGregorianName above). Only\n // month/weekday parts are affected: era is AD/CE either way for CE\n // dates, and timeZoneName depends on the instant, not the calendar.\n // Only Gregorian-shaped objects take this path — a Temporal object\n // carrying a non-Gregorian calendar (hebrew, islamic, ...) has its\n // month/weekday fields in that calendar already, and ICU's\n // non-Gregorian calendars don't apply the Julian cutover at all, so\n // feeding one through a gregory-keyed reference lookup would index the\n // wrong month number into the wrong calendar. Custom vocabs never\n // reach here (localeAwareName resolves them before calling intlPart).\n if (\n (partType === 'month' || partType === 'weekday') &&\n formatterOptions.calendar === 'gregory' &&\n isBeforeGregorianCutover(temporal)\n ) {\n return preCutoverGregorianName(temporal, locale, formatterOptions, partType);\n }\n\n // Temporal.prototype.toLocaleString() is part of the Temporal spec itself:\n // polyfills implement the ICU formatting internally without needing the\n // engine to recognize the object, so it works without native Intl support.\n //\n // Everything from here to the end of this function is genuinely\n // reachable — NOT dead code — but only on a Node build where a global\n // `Temporal` exists AND Intl.DateTimeFormat.formatToParts() recognizes\n // native Temporal instances directly (this is real, observed to vary\n // across Node versions: absent on the Node 22/24 builds this suite has\n // been run against, present on at least one Node 26 build). This\n // environment has no native Temporal (`typeof globalThis.Temporal ===\n // 'undefined'`), so intlSupportsNativeTemporal() always returns false\n // here and this branch can't be exercised from this test suite without\n // faking native-instance recognition, which turned out to be\n // impractical (Intl's native-Temporal detection isn't spoofable via a\n // Proxy or valueOf() shim — see the M-02 regression test in\n // temporalProvider.test.js for the same conclusion reached about the\n // sibling probe function). Coverage numbers for this block will differ\n // between Node versions for that reason; that's expected, not a\n // regression.\n /* c8 ignore start */\n if (!intlSupportsNativeTemporal()) {\n // normalizeLocaleTag: the active Temporal implementation's\n // toLocaleString forwards the locale to Intl.DateTimeFormat, which\n // (unlike this library's cache keys) rejects underscore-separated\n // tags like 'en_US' outright.\n // (Field-bag guard now runs unconditionally above, before this\n // native-support branch, so it's not repeated here.)\n try {\n return ls.call(temporal, normalizeLocaleTag(locale), formatterOptions);\n } catch (err) {\n // The active Temporal implementation forwards the tag to Intl, which\n // throws a bare RangeError for a malformed one — rethrow typed.\n if (err instanceof RangeError) {\n throw new InvalidLocaleError({ actual: locale, reason: err.message });\n }\n throw err;\n }\n }\n\n // formatToParts() throws on ZonedDateTime directly (per spec), so convert\n // to Instant and pass the zone via `timeZone` instead. Don't convert to\n // PlainDateTime — that drops the zone, which breaks 'MMMM' + 'zzz' combos.\n const { toInstant, timeZoneId } = temporal;\n const isZoned = typeof toInstant === 'function' && typeof timeZoneId === 'string';\n // has to be called as temporal.toInstant() because destructuring it off breaks\n // the receiver and throws\n const intlSafeTemporal = isZoned ? temporal.toInstant!() : temporal;\n const nativeOptions: Intl.DateTimeFormatOptions = {\n ...formatterOptions,\n ...(isZoned ? { timeZone: timeZoneId } : {}),\n };\n\n const formatter = getFormatter(locale, nativeOptions);\n const parts = formatter.formatToParts(intlSafeTemporal as Date | number);\n const index = parts.findIndex((p) => p.type === partType);\n if (index === -1) {\n throw new Error(\n `temporal-fmt: locale \"${locale}\" produced no \"${partType}\" part for this token. ` +\n `This usually means the Temporal object is missing the field the token needs.`\n );\n }\n // some locales (ja-JP) split a field across two parts — e.g. month \"8\"\n // plus a counter suffix \"月\" as a separate sibling literal part. Merge in\n // an adjacent literal only if it has no whitespace, so a genuine suffix\n // gets folded in but an ordinary separator (the space before \"AM\") stays\n // a separator. Mirrors partValue() in localeVocab.ts, which builds the\n // vocab this token's output needs to match for parse() to round-trip.\n let value = parts[index]!.value;\n const prev = parts[index - 1];\n const next = parts[index + 1];\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/* c8 ignore stop */\n\n// Temporal.prototype.toLocaleString() can't isolate a single field the way\n// formatToParts() can — asking for `hour` + `dayPeriod` together returns one\n// joined string (e.g. \"3 in the afternoon\"), and `dayPeriod` alone resolves\n// against a different, non-hour-anchored set of periods (\"in the\n// afternoon\"/\"昼\" instead of \"PM\"/\"午後\"). Day period only depends on the\n// hour anyway, so route it through a plain UTC Date and Intl.DateTimeFormat\n// instead — that's worked the same on every engine regardless of whether\n// Temporal itself is native or polyfilled.\nfunction dayPeriodPart(hour: number, locale: string): string {\n // Custom vocab (when registered) takes precedence over Intl — same\n // contract as the other locale-aware tokens. Intl won't know about a\n // caller-supplied AM/PM string for a made-up locale key, so going\n // through Intl would produce something other than what the caller\n // registered.\n const custom = getCustomVocab(locale);\n if (custom) {\n return hour < 12 ? custom.dayPeriod[0]! : custom.dayPeriod[1]!;\n }\n const date = new Date(Date.UTC(1970, 0, 1, hour));\n const formatter = getFormatter(locale, { hour: 'numeric', hour12: true, timeZone: 'UTC' });\n const part = formatter.formatToParts(date).find((p) => p.type === 'dayPeriod');\n // Defensive guard, confirmed unreachable on this ICU build: forcing\n // hour12: true (as this call always does) produces a dayPeriod part\n // for every locale checked, including 24-hour-clock locales (ja-JP,\n // zh-CN, th-TH, he-IL) and a wide sweep of less-common tags (dz-BT,\n // bo-CN, am-ET, etc.). Same finding as partValue()'s twin guard in\n // localeVocab.ts. Kept in case a future ICU/locale-data update\n // produces a locale that genuinely omits it.\n /* c8 ignore start */\n if (!part) {\n throw new Error(`temporal-fmt: locale \"${locale}\" produced no \"dayPeriod\" part for token \"a\".`);\n }\n /* c8 ignore stop */\n return part.value;\n}\n\n// Resolves a locale-aware month/weekday name from the registered custom\n// vocab when one exists for this locale, falling through to Intl otherwise.\n// Without this, format() would silently keep producing Intl's strings while\n// parse() matched against the registered vocab — the two would round-trip-fail\n// against each other.\nfunction localeAwareName(\n temporal: TemporalLike,\n locale: string,\n options: Intl.DateTimeFormatOptions,\n partType: Intl.DateTimeFormatPartTypes,\n customArray: string[] | undefined,\n customIndex: number | undefined,\n): string {\n if (customArray && customIndex !== undefined && customIndex >= 0 && customIndex < customArray.length) {\n return customArray[customIndex]!;\n }\n return intlPart(temporal, locale, options, partType);\n}\n\n// Formats a `±HH:MM` offset string (the shape Temporal exposes on\n// ZonedDateTime.prototype.offset) into one of the six offset-token widths.\n// Width and Z-handling come from the variant letter+case:\n//\n// X / x — short form: minutes omitted when zero, no colon otherwise\n// XX / xx — hours + minutes, no colon\n// XXX / xxx — hours + minutes, with colon\n//\n// Uppercase (X) collapses +00:00 to \"Z\"; lowercase (x) always emits a\n// numeric offset, even for UTC. Mirrors the date-fns/Unicode-LDML offset\n// family — see README for the full variant table.\nfunction formatOffset(offset: string, variant: 'X' | 'XX' | 'XXX' | 'x' | 'xx' | 'xxx'): string {\n if (offset === '+00:00' && (variant === 'X' || variant === 'XX' || variant === 'XXX')) {\n return 'Z';\n }\n // Most offsets are 6 chars: sign + HH + ':' + MM. Pre-1900 local-mean-time\n // zones can surface a seconds component too, 9 chars: sign + HH + ':' +\n // MM + ':' + SS. \"xxx\" is the one variant with a always-signed,\n // never-\"Z\" colon-separated shape wide enough to carry that unchanged,\n // so it passes a sub-minute offset through verbatim. Every other\n // variant has no seconds slot and must throw rather than silently\n // truncate them away.\n if (offset.length > 6) {\n if (variant === 'xxx') {\n return offset;\n }\n throw new Error(\n `temporal-fmt: token \"${variant}\" cannot represent the offset \"${offset}\", ` +\n `which has a seconds component. None of the X/XX/XXX/x/xx tokens ` +\n `support offset seconds; use \"xxx\" instead, which formats the full ` +\n `offset unchanged.`\n );\n }\n const sign = offset[0]!;\n const hours = offset.slice(1, 3);\n const minutes = offset.slice(4, 6);\n switch (variant) {\n case 'X': case 'x':\n // minutes only matter when they're non-zero — otherwise drop them\n // entirely. Matches LDML: \"With a single X, the hours field is\n // required. The minutes field is optional, but only if the\n // minutes value is 0.\"\n return minutes === '00' ? `${sign}${hours}` : `${sign}${hours}${minutes}`;\n case 'XX': case 'xx':\n return `${sign}${hours}${minutes}`;\n case 'XXX': case 'xxx':\n return `${sign}${hours}:${minutes}`;\n }\n}\n\ntype TokenHandler = (t: TemporalLike, locale: string) => string;\n\n// Longest-first — tokenizer is greedy, \"yyyy\" has to be tried before \"yy\".\n//\n// Numeric tokens always render in ASCII digits, never locale-native\n// (Arabic-Indic, Devanagari, etc). Padding non-ASCII digits isn't as simple\n// as padding \"3\", and most consumers parsing these back out want plain\n// digits anyway.\nexport const TOKENS: Array<[string, TokenHandler, keyof TemporalLike]> = [\n ['yyyy', (t) => pad(t.year!, 4), 'year'],\n ['yy', (t) => {\n // -45 % 100 === -45, so truncating negative years to 2 digits doesn't\n // work and Math.abs() would make 45 CE and 45 BCE render the same.\n if (t.year! < 0) {\n throw new Error(\n `temporal-fmt: token \"yy\" doesn't support negative years (got ${t.year}), ` +\n `since truncating to 2 digits would make it indistinguishable from a ` +\n `positive year. Use \"yyyy\" instead.`\n );\n }\n return pad(t.year! % 100, 2);\n }, 'year'],\n // Unpadded year — no minimum width, unlike yyyy's fixed 4 digits.\n // pad(n, 0) still does the right thing here: Math.abs(n) with no\n // padStart floor just yields the plain digit string, and the sign\n // handling (split off before padding) already covers negative years,\n // so this doesn't need its own sign branch the way \"yy\" does.\n ['y', (t) => pad(t.year!, 0), 'year'],\n ['MMMM', (t, locale) => {\n const custom = getCustomVocab(locale);\n return localeAwareName(t, locale, { month: 'long' }, 'month', custom?.monthLong, t.month! - 1);\n }, 'month'],\n ['MMM', (t, locale) => {\n const custom = getCustomVocab(locale);\n return localeAwareName(t, locale, { month: 'short' }, 'month', custom?.monthShort, t.month! - 1);\n }, 'month'],\n ['MM', (t) => pad(t.month!, 2), 'month'],\n ['M', (t) => String(t.month!), 'month'],\n ['dd', (t) => pad(t.day!, 2), 'day'],\n ['d', (t) => String(t.day!), 'day'],\n ['EEEE', (t, locale) => {\n const custom = getCustomVocab(locale);\n return localeAwareName(t, locale, { weekday: 'long' }, 'weekday', custom?.weekdayLong, t.dayOfWeek! - 1);\n }, 'dayOfWeek'],\n ['EEE', (t, locale) => {\n const custom = getCustomVocab(locale);\n return localeAwareName(t, locale, { weekday: 'short' }, 'weekday', custom?.weekdayShort, t.dayOfWeek! - 1);\n }, 'dayOfWeek'],\n ['HH', (t) => pad(t.hour!, 2), 'hour'],\n ['H', (t) => String(t.hour!), 'hour'],\n ['hh', (t) => pad(t.hour! % 12 || 12, 2), 'hour'],\n ['h', (t) => String(t.hour! % 12 || 12), 'hour'],\n ['mm', (t) => pad(t.minute!, 2), 'minute'],\n ['m', (t) => String(t.minute!), 'minute'],\n ['ss', (t) => pad(t.second!, 2), 'second'],\n ['s', (t) => String(t.second!), 'second'],\n // Fractional-second tokens, S through SSSSSSSSS (1-9 digits). Each token\n // formats a slice of the same underlying nanosecond-of-second value —\n // combining millisecond/microsecond/nanosecond into one 9-digit number\n // and truncating to the token's width — so \"SSS\" keeps meaning exactly\n // what it always meant (3-digit milliseconds) while wider tokens expose\n // the precision Temporal actually carries. formatFraction below is the\n // shared implementation; see its comment for the truncate-not-round\n // rule and why.\n ['SSSSSSSSS', (t) => pad.fraction(t, 9), 'millisecond'],\n ['SSSSSSSS', (t) => pad.fraction(t, 8), 'millisecond'],\n ['SSSSSSS', (t) => pad.fraction(t, 7), 'millisecond'],\n ['SSSSSS', (t) => pad.fraction(t, 6), 'millisecond'],\n ['SSSSS', (t) => pad.fraction(t, 5), 'millisecond'],\n ['SSSS', (t) => pad.fraction(t, 4), 'millisecond'],\n ['SSS', (t) => pad.fraction(t, 3), 'millisecond'],\n ['SS', (t) => pad.fraction(t, 2), 'millisecond'],\n ['S', (t) => pad.fraction(t, 1), 'millisecond'],\n // dayPeriod text is locale-specific (AM/PM in en-US, م/ص in ar-EG) but\n // still needs .hour on the input to compute which period it is\n ['a', (t, locale) => dayPeriodPart(t.hour!, locale), 'hour'],\n ['zzz', (t) => t.timeZoneId!, 'timeZoneId'],\n // Numeric UTC offset tokens (date-fns/Unicode-LDML family). Only\n // ZonedDateTime carries an offset, so the field check in format() throws\n // the same \"requires offset, which this Temporal object doesn't have\"\n // error zzz throws on PlainDate/PlainTime/PlainDateTime — same\n // validation path, just a different field name. See formatOffset above\n // for the per-variant width and Z/numeric distinction.\n ['xxx', (t) => formatOffset(t.offset!, 'xxx'), 'offset'],\n ['xx', (t) => formatOffset(t.offset!, 'xx'), 'offset'],\n ['X', (t) => formatOffset(t.offset!, 'X'), 'offset'],\n ['XX', (t) => formatOffset(t.offset!, 'XX'), 'offset'],\n ['XXX', (t) => formatOffset(t.offset!, 'XXX'), 'offset'],\n ['x', (t) => formatOffset(t.offset!, 'x'), 'offset'],\n\n // Ordinal day (1st, 2nd, 3rd, ... 21st). English suffix rules only —\n // locale-aware ordinals (\"2.\" in de-DE, \"2日\" in ja-JP) are out of scope,\n // since the rest of this library routes locale-specific names through\n // Intl.DateTimeFormat, and Intl has no part type for ordinals. Format-only:\n // the suffix isn't structurally distinguishable from a literal in a parse\n // context (a \"st\"/\"nd\"/\"rd\"/\"th\" suffix isn't a digit and would collide\n // with any adjacent literal text), so there's no good way to read it back.\n ['do', (t) => {\n const day = t.day!;\n const lastDigit = day % 10;\n // 11, 12, 13 are the exception — they'd otherwise match the 1/2/3 rule\n // and produce \"11st\"/\"12nd\"/\"13rd\", which is wrong. They always take \"th\".\n const lastTwoDigits = day % 100;\n if (lastTwoDigits >= 11 && lastTwoDigits <= 13) {\n return day + 'th';\n }\n if (lastDigit === 1) return day + 'st';\n if (lastDigit === 2) return day + 'nd';\n if (lastDigit === 3) return day + 'rd';\n return day + 'th';\n }, 'day'],\n\n // Quarter computed from month: 1-3=Q1, 4-6=Q2, 7-9=Q3, 10-12=Q4.\n // `Q` is plain numeric, `QQQ` renders as \"Q3\" — same convention as\n // date-fns's `Q` and `QQQ` for parity with the most common prior art.\n // Both format and parse; parse() cross-checks Q/QQQ against the parsed\n // month in the same spirit as the EEEE-vs-date cross-check.\n ['Q', (t) => String(Math.ceil(t.month! / 3)), 'month'],\n ['QQQ', (t) => 'Q' + Math.ceil(t.month! / 3), 'month'],\n\n // ISO 8601 week and week-numbering year. Both are format-only — parsing\n // \"ww\"/\"RRRR\" back into a real date requires resolving an ISO week + a\n // weekday (or some other disambiguator) to a specific date, which is a\n // different parsing surface than the token-based parse() here. The\n // ISO-week year (RRRR) can differ from the calendar year at the boundary:\n // Dec 29-31 often belong to week 1 of the *next* year; Jan 1-3 often\n // belong to week 52/53 of the *previous* year. See isoWeekYearAndWeek().\n ['ww', (t) => {\n requireFields(t, 'ww', 'year', 'month', 'day');\n const { week } = isoWeekYearAndWeek(t.year!, t.month!, t.day!, t.dayOfWeek!);\n return pad(week, 2);\n }, 'dayOfWeek'],\n ['RRRR', (t) => {\n requireFields(t, 'RRRR', 'year', 'month', 'day');\n const { isoYear } = isoWeekYearAndWeek(t.year!, t.month!, t.day!, t.dayOfWeek!);\n return pad(isoYear, 4);\n }, 'dayOfWeek'],\n\n // Day of year — number of days since Jan 1 (1-366). Three widths:\n // D — unpadded (1, 2, 366)\n // DD — 2-digit minimum (zero-padded if <100)\n // DDD — 3-digit zero-padded (001, 002, 366)\n // Format-only — parsing day-of-year requires resolving against a year,\n // which is a different shape from the token-based parse() surface.\n // The dayOfYearHelper() in calendarUtils.ts covers the same field for\n // callers who need the numeric value.\n ['D', (t) => {\n requireFields(t, 'D', 'year', 'month');\n return String(dayOfYear(t.year!, t.month!, t.day!));\n }, 'day'],\n ['DD', (t) => {\n requireFields(t, 'DD', 'year', 'month');\n return pad(dayOfYear(t.year!, t.month!, t.day!), 2);\n }, 'day'],\n ['DDD', (t) => {\n requireFields(t, 'DDD', 'year', 'month');\n return pad(dayOfYear(t.year!, t.month!, t.day!), 3);\n }, 'day'],\n\n // Stand-alone month — uses Intl's stand-alone form. In most locales\n // (en, fr, de, es) this is identical to MMMM/MMM. In Slavic/Baltic\n // locales (cs, sk, pl, ru) the stand-alone form differs from the\n // format form (nominative vs genitive case). LLLL = long, LLL = short.\n ['LLLL', (t, locale) => {\n const custom = getCustomVocab(locale);\n return localeAwareName(t, locale, { month: 'long' }, 'month', custom?.monthLong, t.month! - 1);\n }, 'month'],\n ['LLL', (t, locale) => {\n const custom = getCustomVocab(locale);\n return localeAwareName(t, locale, { month: 'short' }, 'month', custom?.monthShort, t.month! - 1);\n }, 'month'],\n\n // Stand-alone weekday — same pattern as stand-alone month but for\n // weekday names. cccc = long, ccc = short.\n ['cccc', (t, locale) => {\n const custom = getCustomVocab(locale);\n return localeAwareName(t, locale, { weekday: 'long' }, 'weekday', custom?.weekdayLong, t.dayOfWeek! - 1);\n }, 'dayOfWeek'],\n ['ccc', (t, locale) => {\n const custom = getCustomVocab(locale);\n return localeAwareName(t, locale, { weekday: 'short' }, 'weekday', custom?.weekdayShort, t.dayOfWeek! - 1);\n }, 'dayOfWeek'],\n\n // Era — locale-aware (\"AD\"/\"BC\" in en, \"ap. J.-C.\"/\"av. J.-C.\" in fr).\n // GGGG = long, G = short. Format-only.\n ['GGGG', (t, locale) => {\n return intlPart(t, locale, { era: 'long' }, 'era');\n }, 'year'],\n ['G', (t, locale) => {\n return intlPart(t, locale, { era: 'short' }, 'era');\n }, 'year'],\n\n // Localized timezone name — uses Intl's longLocalized/short timezone\n // name option. Format-only — these names are locale-dependent and\n // vary by season (EST vs EDT), so parsing them back requires a\n // lookup table that isn't practical to ship.\n ['zzzz', (t, locale) => {\n return intlPart(t, locale, { timeZoneName: 'longGeneric' as Intl.DateTimeFormatOptions['timeZoneName'] }, 'timeZoneName' as Intl.DateTimeFormatPartTypes);\n }, 'timeZoneId'],\n ['z', (t, locale) => {\n return intlPart(t, locale, { timeZoneName: 'short' as Intl.DateTimeFormatOptions['timeZoneName'] }, 'timeZoneName' as Intl.DateTimeFormatPartTypes);\n }, 'timeZoneId'],\n\n];\n\n// Mod-registered tokens, layered on top of the static TOKENS above.\n// Last-write-wins by name (same rule as registerLocale and\n// createFormatter's own token merging) — a second registerFormatToken()\n// call for a name already claimed, whether that name came from another\n// mod or from TOKENS itself, replaces the earlier entry. See\n// registerFormatToken() in runtime.ts for why this beats a hard error:\n// Mod.priority exists precisely so an author can control who wins a\n// shared key, and that only means something if collisions actually\n// resolve instead of throwing.\nconst registeredTokens = new Map<string, [string, TokenHandler, keyof TemporalLike]>();\n\n// tokenize.ts and format.ts each derive a lookup table from TOKENS at\n// import time (SORTED_TOKEN_STRINGS, HANDLER_BY_TOKEN). Once mods can\n// add tokens after those tables are built, something has to tell both\n// modules to rebuild — this is that hook. Both subscribers rebuild in\n// the same call so the tokenizer's idea of \"is this a token\" and\n// format()'s idea of \"what does this token do\" can never drift apart\n// (the c8-ignored \"impossible\" branches in format.ts assume exactly\n// that they can't).\nconst rebuildListeners: Array<() => void> = [];\n\nexport function onTokenTableChange(listener: () => void): void {\n rebuildListeners.push(listener);\n}\n\n// All tokens currently in effect: built-ins plus mod-registered,\n// mod-registered winning on a name clash. Recomputed on every call\n// rather than cached here — registration only happens at mod-load\n// time, so there's no hot-path cost to paying for the rebuild each time\n// a listener fires.\nexport function getEffectiveTokens(): Array<[string, TokenHandler, keyof TemporalLike]> {\n const merged = new Map(TOKENS.map((t) => [t[0], t] as const));\n for (const [name, entry] of registeredTokens) merged.set(name, entry);\n return [...merged.values()];\n}\n\nexport function registerToken(token: [string, TokenHandler, keyof TemporalLike]): void {\n registeredTokens.set(token[0], token);\n for (const listener of rebuildListeners) listener();\n}\n\n// Test-only: clears mod-registered tokens and notifies subscribers, so\n// one test file's registerFormatToken() call can't leak into the next.\n// Mirrors _resetOverridesForTesting() in runtime.ts.\nexport function _resetRegisteredTokensForTesting(): void {\n registeredTokens.clear();\n for (const listener of rebuildListeners) listener();\n}"],"mappings":"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","names":[]}
@@ -1,20 +1,20 @@
1
1
  import {
2
2
  applyNumbering,
3
3
  tokenize
4
- } from "./chunk-N4SYRZIP.js";
4
+ } from "./chunk-ZT3UXRAA.js";
5
5
  import {
6
6
  DEFAULT_LOCALE,
7
7
  TOKENS,
8
8
  getEffectiveTokens,
9
9
  onTokenTableChange
10
- } from "./chunk-BQDOTFGC.js";
10
+ } from "./chunk-BQET6RUR.js";
11
11
  import {
12
12
  MAX_FORMAT_LENGTH
13
13
  } from "./chunk-SOPTTXUI.js";
14
14
  import {
15
15
  FormatSyntaxError,
16
16
  UnknownTokenError
17
- } from "./chunk-OLNU4LON.js";
17
+ } from "./chunk-QRLQ7RR7.js";
18
18
 
19
19
  // src/format.ts
20
20
  var HANDLER_BY_TOKEN = new Map(TOKENS.map(([tok, fn, field]) => [tok, { fn, field }]));
@@ -195,4 +195,4 @@ export {
195
195
  _getPieces,
196
196
  _handlerFor
197
197
  };
198
- //# sourceMappingURL=chunk-UU2XN6N2.js.map
198
+ //# sourceMappingURL=chunk-CQJYB7E7.js.map
@@ -2,7 +2,7 @@ import {
2
2
  InvalidLocaleError,
3
3
  getLocaleVocab,
4
4
  registerLocaleVocab
5
- } from "./chunk-OLNU4LON.js";
5
+ } from "./chunk-QRLQ7RR7.js";
6
6
 
7
7
  // src/localeRegistry.ts
8
8
  var extendedVocabs = /* @__PURE__ */ new Map();
@@ -72,4 +72,4 @@ export {
72
72
  getLocale,
73
73
  hasLocale
74
74
  };
75
- //# sourceMappingURL=chunk-CGI4FL6H.js.map
75
+ //# sourceMappingURL=chunk-FLMOMOAQ.js.map
@@ -7,7 +7,7 @@ import {
7
7
  InvalidLocaleError,
8
8
  canonicalCacheKey,
9
9
  normalizeLocaleTag
10
- } from "./chunk-OLNU4LON.js";
10
+ } from "./chunk-QRLQ7RR7.js";
11
11
  import {
12
12
  asDateFieldView
13
13
  } from "./chunk-KEPIXQK7.js";
@@ -578,4 +578,4 @@ export {
578
578
  addDuration,
579
579
  subtractDuration
580
580
  };
581
- //# sourceMappingURL=chunk-BZPNSQHI.js.map
581
+ //# sourceMappingURL=chunk-LHR5PCP4.js.map
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  applyParseNumbering,
3
3
  tokenize
4
- } from "./chunk-N4SYRZIP.js";
4
+ } from "./chunk-ZT3UXRAA.js";
5
5
  import {
6
6
  DEFAULT_LOCALE
7
- } from "./chunk-BQDOTFGC.js";
7
+ } from "./chunk-BQET6RUR.js";
8
8
  import {
9
9
  getTemporal
10
10
  } from "./chunk-DFWTCRS3.js";
@@ -26,7 +26,7 @@ import {
26
26
  getLocaleVocab,
27
27
  subscribeToVocabChanges,
28
28
  wrapUntypedError
29
- } from "./chunk-OLNU4LON.js";
29
+ } from "./chunk-QRLQ7RR7.js";
30
30
 
31
31
  // src/pattern.ts
32
32
  function escapeRegExp(literal) {
@@ -981,4 +981,4 @@ export {
981
981
  parseToParts,
982
982
  compileParser
983
983
  };
984
- //# sourceMappingURL=chunk-6ZIYGBFN.js.map
984
+ //# sourceMappingURL=chunk-MXXTJ5CW.js.map
@@ -433,7 +433,8 @@ export {
433
433
  normalizeLocaleTag,
434
434
  canonicalCacheKey,
435
435
  assertValidLocaleTag,
436
+ partValue,
436
437
  getCustomVocab,
437
438
  getLocaleVocab
438
439
  };
439
- //# sourceMappingURL=chunk-OLNU4LON.js.map
440
+ //# sourceMappingURL=chunk-QRLQ7RR7.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/errors.ts","../src/localeVocab.ts"],"sourcesContent":["/*\n * Copyright 2026 DirazCoder\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// structured error classes for parse()/format() failures.\n//\n// as of 0.9.0 every throw site on the parse/format data path —\n// tokenize.ts, pattern.ts, format.ts, parse.ts (parse/safeParse/tryParse/\n// parseToParts/compileParser), plus the two data-path throws in\n// localeVocab.ts (partValue/assertNoCollision on the getLocaleVocab side)\n// — throws one of these typed classes directly instead of a plain\n// `new Error(message)`. every site we migrated kept its exact pre-0.9.0\n// message text, and since subclasses of TemporalFmtError still pass\n// `instanceof Error` and match the same message regexes (e.g.\n// `/token \"HH\" requires/`), this didn't need a semver-major bump for\n// either of those checks. what DOES break in 0.9.0: if anyone was\n// specifically checking `err.constructor === Error` or `err.name ===\n// 'Error'`, that'll now see a different name (e.g. 'FormatSyntaxError').\n// noted in the 0.9.0 changelog.\n//\n// deliberately did NOT migrate:\n// - localeVocab.ts's registration-time throws (assertValidVocab,\n// registerLocaleVocab itself) — these are config-time API-misuse\n// errors on data a developer hands in once at startup, not runtime\n// parse/format failures, so they don't really fit this module's\n// TemporalFmtErrorCode taxonomy as-is. assertNoCollision is shared\n// between the registration path and the data path (getLocaleVocab)\n// though, so its throw stays a plain Error for both until that gets\n// split apart — see the tracking note where it's called.\n//\n// wrapUntypedError() below is still around for localeVocab.ts's\n// registration throws, and for anything a caller passes into\n// safeParse()/tryParse() from outside this package. on the data path\n// itself, safeParse's `if (err instanceof TemporalFmtError) return { ok:\n// false, error: err }` catches everything before it'd even reach the\n// classifier here, so honestly the regex branches below are dead for\n// parse/format/tokenize/pattern at this point — kept as the fallback for\n// whatever hasn't been migrated to a typed throw site yet\n\nexport type TemporalFmtErrorCode =\n | 'FORMAT_SYNTAX_ERROR'\n | 'UNKNOWN_TOKEN'\n | 'PARSE_MISMATCH'\n | 'INVALID_DATE'\n | 'INVALID_TIME'\n | 'INVALID_OFFSET'\n | 'INVALID_TIME_ZONE'\n | 'INVALID_CALENDAR'\n | 'AMBIGUOUS_INPUT'\n | 'INVALID_LOCALE'\n | 'INVALID_DURATION';\n\nexport interface TemporalFmtErrorFields {\n code: TemporalFmtErrorCode;\n input?: string;\n format?: string;\n token?: string;\n position?: number;\n expected?: string;\n actual?: string;\n reason?: string;\n}\n\n// base class. `message` is the human-readable summary, the structured\n// fields are the machine-readable bits a linter or codemod would report\n// on. calling Error.captureStackTrace manually (where it exists) so the\n// stack points at wherever this got thrown from, not at this constructor\n// — same trick Node uses for its own error classes\nexport class TemporalFmtError extends Error {\n readonly code: TemporalFmtErrorCode;\n readonly input?: string;\n readonly format?: string;\n readonly token?: string;\n readonly position?: number;\n readonly expected?: string;\n readonly actual?: string;\n readonly reason?: string;\n\n constructor(message: string, fields: TemporalFmtErrorFields) {\n super(message);\n this.name = 'TemporalFmtError';\n this.code = fields.code;\n this.input = fields.input;\n this.format = fields.format;\n this.token = fields.token;\n this.position = fields.position;\n this.expected = fields.expected;\n this.actual = fields.actual;\n this.reason = fields.reason;\n // don't want the stack pointing at this constructor line — captureStackTrace\n // is V8-only though, so on other engines callers just get the default\n // stack (pointing here) since there's nothing better to do about it\n const capture = (Error as unknown as { captureStackTrace?: (target: Error, ctor?: Function) => void }).captureStackTrace;\n if (typeof capture === 'function') {\n capture(this, this.constructor);\n }\n }\n\n // lets callers just call err.toJSON() for logging. plain Error doesn't\n // serialize its non-enumerable fields on its own, so this picks them\n // up explicitly\n toJSON(): TemporalFmtErrorFields & { name: string; message: string } {\n return {\n name: this.name,\n message: this.message,\n code: this.code,\n input: this.input,\n format: this.format,\n token: this.token,\n position: this.position,\n expected: this.expected,\n actual: this.actual,\n reason: this.reason,\n };\n }\n}\n\n// each subclass just fixes `code` so callers can switch on it without\n// having to re-check message text. the constructor only takes the\n// fields that actually vary per call; `code` and the default message\n// template come from the subclass itself\n\nexport class FormatSyntaxError extends TemporalFmtError {\n constructor(fields: Omit<TemporalFmtErrorFields, 'code'> & { message?: string }) {\n const { message, ...rest } = fields;\n super(\n message ?? `format string \"${fields.format ?? ''}\" has a syntax error${fields.reason ? `: ${fields.reason}` : ''}.`,\n { code: 'FORMAT_SYNTAX_ERROR', ...rest },\n );\n this.name = 'FormatSyntaxError';\n }\n}\n\nexport class UnknownTokenError extends TemporalFmtError {\n constructor(fields: Omit<TemporalFmtErrorFields, 'code'> & { message?: string }) {\n const { message, ...rest } = fields;\n super(\n message ?? `token \"${fields.token ?? ''}\" is not a recognized temporal-fmt token${fields.format ? ` in format string \"${fields.format}\"` : ''}.`,\n { code: 'UNKNOWN_TOKEN', ...rest },\n );\n this.name = 'UnknownTokenError';\n }\n}\n\nexport class ParseMismatchError extends TemporalFmtError {\n constructor(fields: Omit<TemporalFmtErrorFields, 'code'> & { message?: string }) {\n const { message, ...rest } = fields;\n super(\n message ?? `input \"${fields.input ?? ''}\" does not match format \"${fields.format ?? ''}\"${fields.reason ? `: ${fields.reason}` : ''}.`,\n { code: 'PARSE_MISMATCH', ...rest },\n );\n this.name = 'ParseMismatchError';\n }\n}\n\nexport class InvalidDateError extends TemporalFmtError {\n constructor(fields: Omit<TemporalFmtErrorFields, 'code'> & { message?: string }) {\n const { message, ...rest } = fields;\n super(\n message ?? `input \"${fields.input ?? ''}\" does not describe a valid date${fields.reason ? `: ${fields.reason}` : ''}.`,\n { code: 'INVALID_DATE', ...rest },\n );\n this.name = 'InvalidDateError';\n }\n}\n\n/* c8 ignore start @preserve -- InvalidTimeError is part of the public\n error-class surface (exported from index.ts, code 'INVALID_TIME') but\n nothing in this package actually constructs one. went and checked\n whether it could get wired in the same way InvalidTimeZoneError just\n was (see parse.ts's zzz-validation loop) — does hour/minute/second have\n a post-match semantic range check the way zone ids do? nope.\n pattern.ts's regex fragments for HH/H/hh/h/mm/m/ss/s already enforce\n their valid ranges right at the regex level (e.g. HH is\n '(?:[01]\\d|2[0-3])', which literally can't match \"99\"), so an\n out-of-range time just gets rejected as a plain shape mismatch before\n any semantic check would even run. no live gap to hook this into\n without inventing a redundant check purely to give this class a body.\n leaving it unconstructed until the library actually hits a real\n invalid-time case worth reporting */\nexport class InvalidTimeError extends TemporalFmtError {\n constructor(fields: Omit<TemporalFmtErrorFields, 'code'> & { message?: string }) {\n const { message, ...rest } = fields;\n super(\n message ?? `input \"${fields.input ?? ''}\" does not describe a valid time${fields.reason ? `: ${fields.reason}` : ''}.`,\n { code: 'INVALID_TIME', ...rest },\n );\n this.name = 'InvalidTimeError';\n }\n}\n/* c8 ignore stop @preserve */\n\nexport class InvalidOffsetError extends TemporalFmtError {\n constructor(fields: Omit<TemporalFmtErrorFields, 'code'> & { message?: string }) {\n const { message, ...rest } = fields;\n super(\n message ?? `offset \"${fields.actual ?? ''}\" is invalid${fields.reason ? `: ${fields.reason}` : ''}.`,\n { code: 'INVALID_OFFSET', ...rest },\n );\n this.name = 'InvalidOffsetError';\n }\n}\n\nexport class InvalidTimeZoneError extends TemporalFmtError {\n constructor(fields: Omit<TemporalFmtErrorFields, 'code'> & { message?: string }) {\n const { message, ...rest } = fields;\n super(\n message ?? `time zone \"${fields.actual ?? ''}\" is not a recognized IANA time zone or fixed offset${fields.reason ? `: ${fields.reason}` : ''}.`,\n { code: 'INVALID_TIME_ZONE', ...rest },\n );\n this.name = 'InvalidTimeZoneError';\n }\n}\n\n/* c8 ignore start @preserve -- InvalidCalendarError is part of the\n public error-class surface (exported from index.ts, code\n 'INVALID_CALENDAR') but same story as InvalidTimeError above — nothing\n in this package constructs one. checked for a wiring opportunity the\n same way: there's no user-supplied calendar identifier anywhere in the\n library that we'd validate against a supported list. resolveCalendar()\n in parse.ts derives the calendar entirely from Intl's own resolution\n of the locale string — it's never handed an arbitrary \"calendar\" value\n a caller could actually get wrong. no live input to reject here.\n leaving it unconstructed until the library accepts an actual calendar\n parameter that could be invalid */\nexport class InvalidCalendarError extends TemporalFmtError {\n constructor(fields: Omit<TemporalFmtErrorFields, 'code'> & { message?: string }) {\n const { message, ...rest } = fields;\n super(\n message ?? `calendar \"${fields.actual ?? ''}\" is not supported${fields.reason ? `: ${fields.reason}` : ''}.`,\n { code: 'INVALID_CALENDAR', ...rest },\n );\n this.name = 'InvalidCalendarError';\n }\n}\n/* c8 ignore stop @preserve */\n\nexport class AmbiguousInputError extends TemporalFmtError {\n constructor(fields: Omit<TemporalFmtErrorFields, 'code'> & { message?: string }) {\n const { message, ...rest } = fields;\n super(\n message ?? `input \"${fields.input ?? ''}\" is ambiguous${fields.reason ? `: ${fields.reason}` : ''}.`,\n { code: 'AMBIGUOUS_INPUT', ...rest },\n );\n this.name = 'AmbiguousInputError';\n }\n}\n\nexport class InvalidLocaleError extends TemporalFmtError {\n constructor(fields: Omit<TemporalFmtErrorFields, 'code'> & { message?: string }) {\n const { message, ...rest } = fields;\n super(\n message ?? `locale \"${fields.actual ?? ''}\" is not a valid BCP-47 tag${fields.reason ? `: ${fields.reason}` : ''}.`,\n { code: 'INVALID_LOCALE', ...rest },\n );\n this.name = 'InvalidLocaleError';\n }\n}\n\nexport class InvalidDurationError extends TemporalFmtError {\n constructor(fields: Omit<TemporalFmtErrorFields, 'code'> & { message?: string }) {\n const { message, ...rest } = fields;\n super(\n message ?? `duration is invalid${fields.reason ? `: ${fields.reason}` : ''}.`,\n { code: 'INVALID_DURATION', ...rest },\n );\n this.name = 'InvalidDurationError';\n }\n}\n\n// wraps a plain Error thrown from a code path that hasn't been migrated\n// to typed errors yet. keeps the original message around in `reason` so\n// callers reading the typed surface still see what actually failed.\n// used by safeParse() in parse.ts.\n//\n// after the 0.9.0 migration, every throw site on the parse/format data\n// path throws a TemporalFmtError directly, so safeParse's `instanceof\n// TemporalFmtError` check always passes before this function would even\n// get called — nothing in the current test suite actually reaches any\n// branch below. kept as the safety net for whenever someone adds an\n// unmigrated throw site down the line (see the c8-ignored call in\n// parse.ts's safeParse) — same reasoning as that call site: removing\n// this would silently break the \"safeParse always returns a\n// TemporalFmtError\" contract the moment anyone adds a bare\n// `throw new Error(...)` without wiring up a typed class for it\n/* c8 ignore start @preserve -- unreachable from the current test suite,\n see rationale above */\nexport function wrapUntypedError(err: Error, context: { input?: string; format?: string }): TemporalFmtError {\n // try to classify by looking at the message — covers the existing\n // parse()/format() throw sites without having to touch them. anything\n // that doesn't match a known pattern falls through to a generic\n // ParseMismatchError, still with the structured fields intact\n const msg = err.message;\n if (/unknown token|isn't a recognized token/.test(msg)) {\n return new UnknownTokenError({ input: context.input, format: context.format, reason: msg });\n }\n if (/ambiguous/i.test(msg)) {\n return new AmbiguousInputError({ input: context.input, format: context.format, reason: msg });\n }\n if (/offset/.test(msg) && /out of range|exceeds|doesn't match the shape/i.test(msg)) {\n return new InvalidOffsetError({ input: context.input, format: context.format, reason: msg });\n }\n if (/no valid pattern matches/i.test(msg)) {\n return new ParseMismatchError({ input: context.input, format: context.format, reason: msg });\n }\n if (/doesn't describe a valid date\\/time|incomplete date|weekday token|quarter token/.test(msg)) {\n return new InvalidDateError({ input: context.input, format: context.format, reason: msg });\n }\n const lowerMsg = msg.toLowerCase();\n const mentionsLocale = lowerMsg.includes('locale');\n if (\n (mentionsLocale && (lowerMsg.includes('produced no') || lowerMsg.includes('not a valid'))) ||\n lowerMsg.includes('cutoffs must be')\n ) {\n return new InvalidLocaleError({ input: context.input, format: context.format, reason: msg });\n }\n if (/format string exceeds maximum length|input exceeds maximum length|unterminated quote|isn't a recognized token/i.test(msg)) {\n return new FormatSyntaxError({ input: context.input, format: context.format, reason: msg });\n }\n return new ParseMismatchError({ input: context.input, format: context.format, reason: msg });\n}\n/* c8 ignore stop @preserve */","/*\n * Copyright 2026 DirazCoder\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// 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\nimport { InvalidLocaleError } from './errors.js';\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// Anything that caches a result derived from *which* vocabulary is\n// active for a locale (right now: parse.ts's compiled pattern cache,\n// whose MMMM/MMM/EEEE/EEE/a fragments embed the vocab's alternations)\n// subscribes here so it gets invalidated when a registration swaps the\n// vocab — mirrors temporalProvider.ts's subscribeToTemporalChanges.\nconst onVocabChanged: Array<() => void> = [];\n\nexport function subscribeToVocabChanges(listener: () => void): void {\n onVocabChanged.push(listener);\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\n * Parsing section of the README (\"MMMM/MMM assume a 12-month calendar\")\n * calls out the Hebrew leap-month gap as a 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 // Also invalidate downstream caches that baked the previous vocab in\n // at build time (parse.ts's pattern cache is the one that matters:\n // without this, a cached pattern would keep matching the OLD month /\n // weekday names while format() renders the new ones, so the library's\n // own format() output would fail to parse back).\n for (const listener of onVocabChanged) listener();\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.\n// Intl constructors (DateTimeFormat/NumberFormat/RelativeTimeFormat)\n// reject underscore-separated tags like 'en_US' outright, while\n// canonicalCacheKey and every locale-parsing path in this library\n// tolerates them by normalizing to BCP-47 hyphens. Centralize that\n// normalization so every `new Intl.*(locale)` construction site\n// accepts the same spellings the cache keys do. Genuinely malformed\n// tags still throw downstream, unchanged.\nexport function normalizeLocaleTag(locale: string): string {\n return locale.replace(/_/g, '-');\n}\n\n// Memoized so hot paths (parse()'s resolveCalendar keys off this on\n// every call, and every locale-keyed cache re-derives it) don't build a\n// fresh Intl.Locale per invocation — construction is comparatively\n// expensive. Bounded like every other cache in this library.\nconst canonicalKeyCache = new Map<string, string>();\nconst MAX_CANONICAL_KEY_CACHE = 500;\n\nexport function canonicalCacheKey(locale: string): string {\n const hit = canonicalKeyCache.get(locale);\n if (hit !== undefined) return hit;\n let key: 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 key = new Intl.Locale(locale.replace(/_/g, '-')).toString().toLowerCase();\n } catch {\n // Malformed tag: key on the raw string. Callers that must reject\n // malformed tags do it via assertValidLocaleTag() before/instead of\n // relying on this function — cache-key normalization isn't where a\n // bad locale should surface as an error (unchanged behavior).\n key = locale;\n }\n if (canonicalKeyCache.size >= MAX_CANONICAL_KEY_CACHE) {\n const oldestKey = canonicalKeyCache.keys().next().value;\n if (oldestKey !== undefined) canonicalKeyCache.delete(oldestKey);\n }\n canonicalKeyCache.set(locale, key);\n return key;\n}\n\n// Single validation choke point for locale tags at public boundaries.\n// Intl constructors reject malformed tags with a bare engine RangeError\n// (\"Incorrect locale information provided\") that carries none of the\n// library's structured error context; this rethrows as the typed\n// InvalidLocaleError with the offending tag in its fields. Accepts the\n// same spellings as canonicalCacheKey (underscore tags normalized).\nexport function assertValidLocaleTag(locale: string): void {\n try {\n new Intl.Locale(normalizeLocaleTag(locale));\n } catch {\n throw new InvalidLocaleError({\n actual: locale,\n reason: 'not a valid BCP-47 locale tag',\n });\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// post-1582 tokens go through toLocaleString(), which concatenates\n// everything into \"8月\", and the pre-1582 path in tokens.ts\n// (preCutoverGregorianName) formats one field at a time the same way this\n// does. 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.\nexport function 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 InvalidLocaleError({\n message: `temporal-fmt: locale produced no \"${type}\" part while building match vocabulary.`,\n });\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 // Not migrated to a typed error: this function is shared between\n // getLocaleVocab's Intl-derived path (data-path error, would be a\n // good InvalidLocaleError candidate) and assertValidVocab's\n // registration-time check (out of scope for this pass — see the\n // localeVocab.ts registration-error follow-up). Splitting this into\n // two near-duplicate functions just to route error types\n // differently isn't worth it for one throw; revisit together with\n // the registration-error work instead.\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 // Validate before building vocab: a malformed tag used to surface as a\n // raw RangeError from the first new Intl.DateTimeFormat below.\n assertValidLocaleTag(locale);\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 intlLocale = normalizeLocaleTag(locale);\n const monthLongFmt = new Intl.DateTimeFormat(intlLocale, { month: 'long', timeZone: 'UTC' });\n const monthShortFmt = new Intl.DateTimeFormat(intlLocale, { 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(intlLocale, { weekday: 'long', timeZone: 'UTC' });\n const weekdayShortFmt = new Intl.DateTimeFormat(intlLocale, { 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(intlLocale, { 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":[]}
@@ -1,13 +1,16 @@
1
1
  import {
2
+ DEFAULT_LOCALE,
2
3
  TOKENS,
3
4
  getEffectiveTokens,
4
5
  onTokenTableChange
5
- } from "./chunk-BQDOTFGC.js";
6
+ } from "./chunk-BQET6RUR.js";
6
7
  import {
7
8
  FormatSyntaxError,
8
9
  InvalidLocaleError,
9
- UnknownTokenError
10
- } from "./chunk-OLNU4LON.js";
10
+ UnknownTokenError,
11
+ canonicalCacheKey,
12
+ normalizeLocaleTag
13
+ } from "./chunk-QRLQ7RR7.js";
11
14
 
12
15
  // src/tokenize.ts
13
16
  var SORTED_TOKEN_STRINGS = TOKENS.map(([tok]) => tok).sort((a, b) => b.length - a.length);
@@ -103,6 +106,31 @@ var SUPPORTED_NUMBERING_SYSTEMS = /* @__PURE__ */ new Set([
103
106
  "hanidec"
104
107
  ]);
105
108
  var digitMapCache = /* @__PURE__ */ new Map();
109
+ var autoNumberingCache = /* @__PURE__ */ new Map();
110
+ var MAX_AUTO_NUMBERING_CACHE = 500;
111
+ function resolveAutoNumberingSystem(locale) {
112
+ const key = canonicalCacheKey(locale);
113
+ const cached = autoNumberingCache.get(key);
114
+ if (cached !== void 0) return cached;
115
+ let resolved;
116
+ try {
117
+ resolved = new Intl.NumberFormat(normalizeLocaleTag(locale)).resolvedOptions().numberingSystem;
118
+ } catch (err) {
119
+ throw new InvalidLocaleError({ actual: locale, reason: err.message });
120
+ }
121
+ const system = SUPPORTED_NUMBERING_SYSTEMS.has(resolved) ? resolved : "latn";
122
+ if (autoNumberingCache.size >= MAX_AUTO_NUMBERING_CACHE) {
123
+ const oldestKey = autoNumberingCache.keys().next().value;
124
+ if (oldestKey !== void 0) autoNumberingCache.delete(oldestKey);
125
+ }
126
+ autoNumberingCache.set(key, system);
127
+ return system;
128
+ }
129
+ function resolveRequestedSystem(requested, locale) {
130
+ if (requested === void 0) return "latn";
131
+ if (requested === "auto") return resolveAutoNumberingSystem(locale ?? DEFAULT_LOCALE);
132
+ return requested;
133
+ }
106
134
  function getDigitMap(system) {
107
135
  let map = digitMapCache.get(system);
108
136
  if (map) return map;
@@ -157,12 +185,12 @@ function convertDigitsToAscii(s, system) {
157
185
  return result;
158
186
  }
159
187
  function applyNumbering(s, options) {
160
- const system = options.numberingSystem ?? "latn";
188
+ const system = resolveRequestedSystem(options.numberingSystem, options.locale);
161
189
  if (system === "latn") return s;
162
190
  return convertDigits(s, system);
163
191
  }
164
192
  function applyParseNumbering(s, options) {
165
- const system = options.parseNumberingSystem ?? "latn";
193
+ const system = resolveRequestedSystem(options.parseNumberingSystem, options.locale);
166
194
  if (system === "latn") return s;
167
195
  return convertDigitsToAscii(s, system);
168
196
  }
@@ -176,4 +204,4 @@ export {
176
204
  applyNumbering,
177
205
  applyParseNumbering
178
206
  };
179
- //# sourceMappingURL=chunk-N4SYRZIP.js.map
207
+ //# sourceMappingURL=chunk-ZT3UXRAA.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/tokenize.ts","../src/numbering.ts"],"sourcesContent":["/*\n * Copyright 2026 DirazCoder\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { TOKENS, getEffectiveTokens, onTokenTableChange } from './tokens.js';\nimport { FormatSyntaxError, UnknownTokenError } from './errors.js';\n\nexport type Piece =\n | { kind: 'token'; value: string }\n | { kind: 'literal'; value: string };\n\n// A piece plus its exact source span in the ORIGINAL format string.\n// `start`/`end` are UTF-16 code-unit indices, half-open [start, end).\n// Decoded literals can be shorter than their span ('' is 2 source\n// chars → 1 decoded quote), which is exactly why positions computed\n// from decoded lengths (the old approach in analyze.ts) drifted after\n// any quoted text.\nexport interface SpannedPiece {\n kind: 'token' | 'literal';\n value: string;\n start: number;\n end: number;\n}\n\n// gotta sort longest-first or the greedy scan grabs \"M\" when \"MMMM\" was actually there.\n// Starts from the static TOKENS (so this module works before any mod has\n// registered anything) and rebuilds from tokens.ts's getEffectiveTokens()\n// whenever a mod calls registerFormatToken — otherwise a token registered\n// after this module loaded would never be recognized here, and format()\n// would tokenize it as literal text even though HANDLER_BY_TOKEN in\n// format.ts knows about it. `let`, not `const`, precisely so the rebuild\n// below can replace the binding rather than mutate a shared array in place.\nlet SORTED_TOKEN_STRINGS = TOKENS.map(([tok]) => tok).sort((a, b) => b.length - a.length);\n\nonTokenTableChange(() => {\n SORTED_TOKEN_STRINGS = getEffectiveTokens()\n .map(([tok]) => tok)\n .sort((a, b) => b.length - a.length);\n});\n\n/**\n * Splits a format string like `\"yyyy-MM-dd 'at' HH:mm\"` into token/literal\n * pieces, each with its exact source span. Text in single quotes is always\n * literal (e.g. write 'rd' in \"3rd\" so it's not read as the day token). A\n * doubled quote ('') means a literal quote character, both inside a quoted\n * span and standalone.\n */\nexport function tokenizeWithSpans(format: string): SpannedPiece[] {\n const pieces: SpannedPiece[] = [];\n let i = 0;\n\n while (i < format.length) {\n const ch = format[i];\n\n if (ch === \"'\") {\n // gotta check the doubled-quote case first, otherwise \"''best''\" parses wrong\n if (format[i + 1] === \"'\") {\n appendLiteral(pieces, \"'\", i, i + 2);\n i += 2;\n continue;\n }\n\n let j = i + 1;\n let literal = '';\n let closed = false;\n while (j < format.length) {\n if (format[j] === \"'\") {\n if (format[j + 1] === \"'\") {\n literal += \"'\";\n j += 2;\n continue;\n }\n closed = true;\n j += 1;\n break;\n }\n literal += format[j];\n j += 1;\n }\n\n if (!closed) {\n throw new FormatSyntaxError({\n format,\n message: `temporal-fmt: unterminated quote in format string \"${format}\"`,\n });\n }\n\n appendLiteral(pieces, literal, i, j);\n i = j;\n continue;\n }\n\n const match = SORTED_TOKEN_STRINGS.find((tok) => format.startsWith(tok, i));\n if (match) {\n // this is already the longest token that starts here, so if there's\n // one more of the same char after it, that's not a real token — it'd\n // just silently fall through to whatever handles that next char and\n // get glued onto this one. found this the hard way: \"zzzz\" was\n // parsing as zzz + literal \"z\", \"MMMMM\" as MMMM + the M token. so now\n // we just treat the whole overlong run as one bad token instead\n const runChar = match[match.length - 1];\n if (format[i + match.length] === runChar) {\n let end = i + match.length;\n while (format[end] === runChar) end += 1;\n // UnknownTokenError, not FormatSyntaxError: wrapUntypedError's\n // classifier already treats this exact message (\"isn't a\n // recognized token\") as UNKNOWN_TOKEN (see errors.test.js's\n // \"overlong token run classifies as UnknownTokenError\"), and a\n // direct throw here needs to agree with what safeParse's fallback\n // path would have classified it as.\n throw new UnknownTokenError({\n format,\n token: format.slice(i, end),\n message:\n `temporal-fmt: \"${format.slice(i, end)}\" in format string \"${format}\" isn't a recognized token — ` +\n `did you mean \"${match}\"?`,\n });\n }\n pieces.push({ kind: 'token', value: match, start: i, end: i + match.length });\n i += match.length;\n continue;\n }\n\n // not a token or quote — pass through as-is\n appendLiteral(pieces, ch, i, i + 1);\n i += 1;\n }\n\n return pieces;\n}\n\n/**\n * Splits a format string like `\"yyyy-MM-dd 'at' HH:mm\"` into token/literal\n * pieces. Text in single quotes is always literal (e.g. write 'rd' in\n * \"3rd\" so it's not read as the day token). A doubled quote ('') means a\n * literal quote character, both inside a quoted span and standalone.\n */\nexport function tokenize(format: string): Piece[] {\n return tokenizeWithSpans(format).map(({ kind, value }) => ({ kind, value }));\n}\n\n// merges into the previous piece if it's also a literal, so \"---\" is one\n// piece instead of three (spans track the full merged range)\nfunction appendLiteral(pieces: SpannedPiece[], value: string, start: number, end: number): void {\n const last = pieces[pieces.length - 1];\n if (last && last.kind === 'literal' && last.end === start) {\n last.value += value;\n last.end = end;\n } else {\n pieces.push({ kind: 'literal', value, start, end });\n }\n}\n","/*\n * Copyright 2026 DirazCoder\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// numbering systems. latn (ASCII digits) is the default, but arab, deva,\n// beng, etc are all options, configurable per format call.\n//\n// the tokens in this lib always spit out ASCII digits normally (check\n// tokens.ts's pad() — just String(n), which is ASCII). this module bolts\n// on the ability to convert that output to a locale's native digits via\n// Intl.NumberFormat, since that's the standard way JS does digit\n// transliteration anyway.\n//\n// parse side is stricter: parse() only accepts ASCII digits, matches how\n// NUMERIC_FRAGMENTS is already built. parseNumberingSystem converts\n// input digits to ASCII first — an explicit opt-in per call, never\n// silently accepting any numeral system that shows up.\n//\n// both directions also accept 'auto': instead of requiring the caller to\n// know the ICU numbering-system code for their locale, ask\n// Intl.NumberFormat(locale).resolvedOptions().numberingSystem what the\n// locale itself uses and transliterate with that.\n\nimport { DEFAULT_LOCALE, type FormatOptions } from './tokens.js';\nimport { InvalidLocaleError } from './errors.js';\nimport { canonicalCacheKey, normalizeLocaleTag } from './localeVocab.js';\n\nexport type NumberingSystem = 'latn' | 'arab' | 'deva' | 'beng' | 'guru' | 'gujr' | 'orya' | 'tamldec' | 'telu' | 'knda' | 'mlym' | 'fullwide' | 'hanidec';\n\n// What a caller may pass as numberingSystem / parseNumberingSystem: an\n// explicit system name, or 'auto' to derive the system from the call's\n// locale. The `(string & {})` keeps arbitrary strings compiling (the\n// runtime still validates them against SUPPORTED_NUMBERING_SYSTEMS and\n// throws on unknown names, unchanged) while editors still offer the\n// known literals — 'latn', 'arab', ..., 'auto' — as completions.\nexport type NumberingSystemOption = NumberingSystem | 'auto' | (string & {});\n\n// every NumberingSystem value we support. latn's the default and\n// what the rest of this lib naturally produces\nexport const SUPPORTED_NUMBERING_SYSTEMS: ReadonlySet<string> = new Set([\n 'latn', 'arab', 'deva', 'beng', 'guru', 'gujr', 'orya', 'tamldec',\n 'telu', 'knda', 'mlym', 'fullwide', 'hanidec',\n]);\n\nconst digitMapCache = new Map<string, Record<string, string>>();\n\n// 'auto' resolutions, keyed by canonical locale tag (same key discipline\n// as every other locale-keyed cache in this library — 'ar_EG' and 'ar-EG'\n// fold together instead of costing two entries). Intl.NumberFormat\n// construction isn't free and format() runs in loops (a table of dates, one\n// row per record), so resolve each locale once. Bounded, evicting the\n// oldest insertion, mirroring formatterCache in tokens.ts.\nconst autoNumberingCache = new Map<string, string>();\nconst MAX_AUTO_NUMBERING_CACHE = 500;\n\n// Turns 'auto' into a concrete system by asking Intl what the locale\n// itself defaults to (ar-EG -> arab, bn-BD -> beng, en-US -> latn). When\n// the locale's native system isn't one this library can transliterate\n// (thai, laoo, mymr, ... — anything outside SUPPORTED_NUMBERING_SYSTEMS),\n// fall back to 'latn' rather than throwing: 'auto' means \"use whatever\n// this locale naturally uses, if you can\", not \"throw on locales with\n// numerals this library doesn't cover\". A malformed locale tag DOES\n// throw — typed InvalidLocaleError, matching getFormatter() in tokens.ts —\n// since asking to derive from a locale that doesn't exist is a caller bug,\n// not a data condition to paper over.\nfunction resolveAutoNumberingSystem(locale: string): string {\n const key = canonicalCacheKey(locale);\n const cached = autoNumberingCache.get(key);\n if (cached !== undefined) return cached;\n let resolved: string;\n try {\n resolved = new Intl.NumberFormat(normalizeLocaleTag(locale)).resolvedOptions().numberingSystem;\n } catch (err) {\n throw new InvalidLocaleError({ actual: locale, reason: (err as Error).message });\n }\n const system = SUPPORTED_NUMBERING_SYSTEMS.has(resolved) ? resolved : 'latn';\n if (autoNumberingCache.size >= MAX_AUTO_NUMBERING_CACHE) {\n // not real LRU, just evicts oldest insertion — fine for this key space\n const oldestKey = autoNumberingCache.keys().next().value;\n if (oldestKey !== undefined) autoNumberingCache.delete(oldestKey);\n }\n autoNumberingCache.set(key, system);\n return system;\n}\n\n// Shared front door for both option names. undefined stays 'latn' — the\n// default is unchanged; 'auto' is an opt-in, not a new default. Anything\n// else passes through untouched for convertDigits/convertDigitsToAscii\n// to validate against the supported set (they throw on unknown names).\nfunction resolveRequestedSystem(requested: string | undefined, locale: string | undefined): string {\n if (requested === undefined) return 'latn';\n if (requested === 'auto') return resolveAutoNumberingSystem(locale ?? DEFAULT_LOCALE);\n return requested;\n}\n\n// builds a digit-transliteration map per numbering system. renders 0-9\n// through Intl.NumberFormat in the target system, then builds the lookup\n// table from that. caching it since spinning up a formatter isn't free\n// and we reuse the same map for every digit we convert\nfunction getDigitMap(system: string): Record<string, string> {\n let map = digitMapCache.get(system);\n if (map) return map;\n /* c8 ignore start @preserve -- this branch is dead by construction, not\n just untested: both callers (convertDigits, convertDigitsToAscii)\n already bail out early on system === 'latn' before ever calling\n getDigitMap, so it never actually gets invoked with 'latn'. keeping\n it anyway as a defensive fallback rather than betting that stays\n true forever */\n if (system === 'latn') {\n map = {};\n for (let i = 0; i < 10; i++) map[String(i)] = String(i);\n } else {\n /* c8 ignore stop @preserve */\n const fmt = new Intl.NumberFormat('en-US-u-nu-' + system, { useGrouping: false });\n map = {};\n for (let i = 0; i < 10; i++) {\n map[String(i)] = fmt.format(i);\n }\n }\n digitMapCache.set(system, map);\n return map;\n}\n\n// swaps every ASCII digit in `s` for its equivalent in the target\n// numbering system. anything that's not a digit passes through untouched\nexport function convertDigits(s: string, system: string): string {\n if (system === 'latn') return s;\n if (!SUPPORTED_NUMBERING_SYSTEMS.has(system)) {\n throw new InvalidLocaleError({ actual: system, reason: `numbering system \"${system}\" is not supported. Supported: ${[...SUPPORTED_NUMBERING_SYSTEMS].join(', ')}.` });\n }\n const map = getDigitMap(system);\n let result = '';\n for (const ch of s) {\n if (ch >= '0' && ch <= '9') {\n // map[ch] is always populated for 0-9 — getDigitMap builds all ten\n // keys for every system we support, and ch is already range-checked\n // above. the ?? ch is really just there to satisfy TS about Record's\n // implicit undefined, not because this path is actually reachable\n /* c8 ignore next */\n result += map[ch] ?? ch;\n } else {\n result += ch;\n }\n }\n return result;\n}\n\n// inverse of convertDigits — takes non-ASCII digits back to ASCII. used\n// by parse() when someone passes an explicit numberingSystem option.\n// throws on anything unsupported\nexport function convertDigitsToAscii(s: string, system: string): string {\n // same dead-by-construction thing as the 'latn' guard above —\n // applyParseNumbering (the only caller) already returns early on\n // 'latn' before this ever gets called, so system's never actually\n // 'latn' here in practice. leaving the guard anyway in case someone\n // calls this directly someday without going through that guard\n /* c8 ignore next */\n if (system === 'latn') return s;\n if (!SUPPORTED_NUMBERING_SYSTEMS.has(system)) {\n throw new InvalidLocaleError({ actual: system, reason: `numbering system \"${system}\" is not supported.` });\n }\n const map = getDigitMap(system);\n // just flip the map around\n const reverse: Record<string, string> = {};\n for (const k of Object.keys(map)) reverse[map[k]!] = k;\n let result = '';\n for (const ch of s) {\n result += reverse[ch] ?? ch;\n }\n return result;\n}\n\n// FormatOptions plus a numberingSystem field. pass { numberingSystem: 'arab' }\n// to format() to get Arabic-Indic digits out, or { numberingSystem: 'auto' }\n// to use whichever numeral system the call's locale itself defaults to\n// (ar-EG -> arab, bn-BD -> beng, en-US -> latn), resolved via\n// Intl.NumberFormat(locale).resolvedOptions().numberingSystem with a\n// 'latn' fallback for locales whose native system isn't supported here.\n// Unset still means 'latn' — 'auto' is an opt-in, not a default change.\nexport interface NumberingFormatOptions extends FormatOptions {\n numberingSystem?: NumberingSystemOption;\n}\n\n// same idea but for the parse side. called parseNumberingSystem instead of\n// just numberingSystem so someone mixing format() and parse() options in\n// one config object can set both independently — the two directions\n// aren't always symmetric (you might want native digits out without\n// wanting to accept them back in, or vice versa). 'auto' works here too,\n// resolving from the parse call's own locale the same way the format\n// side does.\nexport interface NumberingParseOptions extends FormatOptions {\n parseNumberingSystem?: NumberingSystemOption;\n}\n\n// format-path helper: takes format()'s ASCII output and converts digits\n// if numberingSystem was asked for. lives here so format.ts doesn't need\n// to know anything about numbering systems\nexport function applyNumbering(s: string, options: NumberingFormatOptions): string {\n const system = resolveRequestedSystem(options.numberingSystem, options.locale);\n if (system === 'latn') return s;\n return convertDigits(s, system);\n}\n\n// parse-path helper: converts input digits to ASCII before matching, if\n// parseNumberingSystem got set. kept as a separate option name from the\n// format side so callers can be explicit about which direction they\n// actually want transliterated\nexport function applyParseNumbering(s: string, options: { parseNumberingSystem?: string; locale?: string }): string {\n // both call sites for this (both in parse.ts) already guard with\n // `if (options.parseNumberingSystem)` before calling, so an undefined\n // parseNumberingSystem never actually reaches here from parse() itself —\n // the undefined handling inside resolveRequestedSystem is dead by\n // construction from that direction. leaving it in as a safety net\n // rather than betting every future caller replicates the same guard\n // (this is a public export; direct calls don't come pre-guarded).\n const system = resolveRequestedSystem(options.parseNumberingSystem, options.locale);\n if (system === 'latn') return s;\n return convertDigitsToAscii(s, system);\n}"],"mappings":"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","names":[]}