temporal-fmt 0.8.2 → 0.8.3

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.
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/temporalProvider.ts","../src/localeVocab.ts","../src/tokens.ts","../src/tokenize.ts","../src/format.ts","../src/pattern.ts","../src/parsePattern.ts","../src/parse.ts"],"sourcesContent":["export { format } from './format.js';\nexport { parse } from './parse.js';\nexport { setTemporal } from './temporalProvider.js';\nexport type { TemporalLike, FormatOptions } from './tokens.js';\nexport type { TemporalNamespace } from './temporalProvider.js';","// This package's tsconfig assumes lib: [\"ESNext\"] only — no ambient\n// `Temporal` namespace type. Everywhere else in this codebase only ever\n// *reads* fields off a Temporal-like object the caller already built\n// (TemporalLike in tokens.ts). This module is the single choke point for\n// touching a *namespace*-shaped Temporal (PlainDate.from, etc.): parse()\n// uses it to construct a result, tokens.ts uses it to probe native\n// Intl<->Temporal support. Consumers can hand us an implementation via\n// setTemporal(), or we fall back to globalThis.Temporal\ninterface TemporalFactory {\n from(fields: Record<string, number | string | undefined>, options?: { overflow?: 'constrain' | 'reject' }): unknown;\n}\n\nexport interface TemporalNamespace {\n PlainDate: TemporalFactory;\n PlainTime: TemporalFactory;\n PlainDateTime: TemporalFactory;\n ZonedDateTime: TemporalFactory;\n}\n\nlet injectedTemporal: TemporalNamespace | undefined;\n\n// Anything that caches a result derived from *which* Temporal\n// implementation is active (right now: tokens.ts's native-Intl-support\n// probe) registers here so it gets invalidated whenever setTemporal()\n// swaps the implementation — see the comment on setTemporal() below for\n// why that matters. A plain array instead of an event-emitter-style API\n// since this only ever needs \"call every listener, in registration order,\n// with no payload\" — nothing here needs unsubscribe or payload data.\nconst onTemporalChanged: Array<() => void> = [];\n\nexport function subscribeToTemporalChanges(listener: () => void): void {\n onTemporalChanged.push(listener);\n}\n\n/**\n * Explicitly hand temporal-fmt the Temporal implementation to use, instead\n * of relying on a global `Temporal`. Call this once, before your first\n * `format()`/`parse()`\n *\n * Call with no argument (or `undefined`) to clear the override and fall\n * back to `globalThis.Temporal` again.\n *\n * @example\n * import { Temporal } from 'temporal-polyfill';\n * import { setTemporal } from 'temporal-fmt';\n * setTemporal(Temporal);\n */\nexport function setTemporal(temporal?: TemporalNamespace): void {\n injectedTemporal = temporal;\n // tokens.ts's native-Intl-support probe is keyed on whichever Temporal\n // implementation was active the first time a locale-aware format() ran —\n // if that implementation changes later (native -> polyfill or back), the\n // memoized probe result can go stale and disagree with what's now\n // actually active. Resetting it here means the next locale-aware format()\n // after any setTemporal() call re-probes against the implementation\n // that's active *now*, at the (small, one-time-per-switch) cost of\n // re-running the probe.\n for (const listener of onTemporalChanged) listener();\n}\n\nfunction resolveTemporal(): TemporalNamespace | undefined {\n return injectedTemporal ?? (globalThis as unknown as { Temporal?: TemporalNamespace }).Temporal;\n}\n\nexport function getTemporal(): TemporalNamespace {\n const temporal = resolveTemporal();\n if (!temporal) {\n throw new Error(\n 'temporal-fmt: parse() needs a Temporal implementation to construct its result. ' +\n 'Call setTemporal(Temporal) once at startup, or assign one to globalThis.Temporal ' +\n '(native on Node 26+, or a polyfill like temporal-polyfill).'\n );\n }\n return temporal;\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\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// Every locale-keyed cache in this library (this one, formatterCache in\n// tokens.ts, patternCache/calendarCache in parse.ts) used to key on the\n// exact locale string a caller passed in. Intl treats spelling variants of\n// the same locale as equivalent ('en-US' / 'en-us' / 'en_US' all resolve\n// the same way), but a plain string-keyed Map doesn't — so callers mixing\n// spellings for what's really one locale would silently fragment across\n// separate cache entries instead of sharing one, making the bounded\n// eviction limits (MAX_*_CACHE_SIZE) less effective than they look. This\n// doesn't change any cache's *correctness* (each entry is still built from\n// -- and valid for -- whatever locale string produced it), only how many\n// distinct entries equivalent spellings end up costing. Falls back to the\n// original string on a malformed/unrecognized tag rather than throwing —\n// cache-key normalization shouldn't be where a bad locale first surfaces\n// as an error; whatever actually calls `new Intl.DateTimeFormat(locale)`\n// downstream is the right place for that.\nexport function canonicalCacheKey(locale: string): string {\n try {\n // Intl.Locale requires BCP-47 hyphens and rejects underscore-separated\n // tags like 'en_US' outright (RangeError), rather than normalizing\n // them — so without this replace, that spelling would just fall\n // through to the catch below and never fold with 'en-US'.\n return new Intl.Locale(locale.replace(/_/g, '-')).toString().toLowerCase();\n } catch {\n return locale;\n }\n}\n\nconst vocabCache = new Map<string, LocaleVocab>();\nconst MAX_VOCAB_CACHE_SIZE = 500;\n\n// Some locales (ja-JP) split a field across two parts — month \"8\" plus a\n// counter suffix \"月\" as a separate sibling \"literal\" — while format()'s\n// tokens go through toLocaleString(), which concatenates everything into\n// \"8月\". Reading only the type-tagged part used to drop that suffix, so\n// this locale's vocab never matched what format() actually produced.\n// Only merges *adjacent* literals, not the whole string, since the\n// dayPeriod/weekday formatters below carry an extra hour part that a\n// join-everything approach would wrongly absorb.\nfunction partValue(formatter: Intl.DateTimeFormat, date: Date, type: Intl.DateTimeFormatPartTypes): string {\n const parts = formatter.formatToParts(date);\n const index = parts.findIndex((p) => p.type === type);\n if (index === -1) {\n throw new Error(`temporal-fmt: locale produced no \"${type}\" part while building match vocabulary.`);\n }\n let value = parts[index]!.value;\n const prev = parts[index - 1];\n const next = parts[index + 1];\n // skip whitespace literals (the separator before \"AM\") — only a\n // no-space suffix like ja-JP's \"月\" should get folded in\n if (prev?.type === 'literal' && !/\\s/.test(prev.value)) value = prev.value + value;\n if (next?.type === 'literal' && !/\\s/.test(next.value)) value = value + next.value;\n return value;\n}\n\n// Two entries rendering identically means parse()'s reverse lookup\n// (indexOf) can never tell them apart. Weekday collisions already surface\n// via parse()'s dayOfWeek cross-check, but with a confusing same-string\n// error; months have no equivalent cross-check, so a collision there would\n// otherwise resolve silently to the wrong month. Catching both here, once\n// at build time, gives one clear error instead.\nfunction assertNoCollision(names: string[], label: string, locale: string): void {\n const seen = new Map<string, number>();\n for (let i = 0; i < names.length; i++) {\n const prior = seen.get(names[i]!);\n if (prior !== undefined) {\n throw new Error(\n `temporal-fmt: locale \"${locale}\" renders ${label} index ${prior} and ${i} identically ` +\n `(\"${names[i]}\"). parse() can't reliably tell these apart for this locale/token, so this ` +\n `combination isn't supported.`\n );\n }\n seen.set(names[i]!, i);\n }\n}\n\nexport function getLocaleVocab(locale: string): LocaleVocab {\n const cacheKey = canonicalCacheKey(locale);\n const cached = vocabCache.get(cacheKey);\n if (cached) {\n return cached;\n }\n\n const monthLongFmt = new Intl.DateTimeFormat(locale, { month: 'long', timeZone: 'UTC' });\n const monthShortFmt = new Intl.DateTimeFormat(locale, { month: 'short', timeZone: 'UTC' });\n const monthLong: string[] = [];\n const monthShort: string[] = [];\n for (let m = 0; m < 12; m++) {\n const date = new Date(Date.UTC(2020, m, 1));\n monthLong.push(partValue(monthLongFmt, date, 'month'));\n monthShort.push(partValue(monthShortFmt, date, 'month'));\n }\n assertNoCollision(monthLong, 'MMMM month', locale);\n assertNoCollision(monthShort, 'MMM month', locale);\n\n const weekdayLongFmt = new Intl.DateTimeFormat(locale, { weekday: 'long', timeZone: 'UTC' });\n const weekdayShortFmt = new Intl.DateTimeFormat(locale, { weekday: 'short', timeZone: 'UTC' });\n const weekdayLong: string[] = [];\n const weekdayShort: string[] = [];\n // 2024-01-01 is a Monday (UTC) — walk 7 days from there for weekday names\n for (let d = 0; d < 7; d++) {\n const date = new Date(Date.UTC(2024, 0, 1 + d));\n weekdayLong.push(partValue(weekdayLongFmt, date, 'weekday'));\n weekdayShort.push(partValue(weekdayShortFmt, date, 'weekday'));\n }\n // redundant with parse()'s dayOfWeek cross-check, but gives a clearer error\n assertNoCollision(weekdayLong, 'EEEE weekday', locale);\n assertNoCollision(weekdayShort, 'EEE weekday', locale);\n\n const dayPeriodFmt = new Intl.DateTimeFormat(locale, { hour: 'numeric', hour12: true, timeZone: 'UTC' });\n const am = partValue(dayPeriodFmt, new Date(Date.UTC(2020, 0, 1, 1)), 'dayPeriod');\n const pm = partValue(dayPeriodFmt, new Date(Date.UTC(2020, 0, 1, 13)), 'dayPeriod');\n const dayPeriod = [...new Set([am, pm])];\n\n const vocab: LocaleVocab = { monthLong, monthShort, weekdayLong, weekdayShort, dayPeriod };\n if (vocabCache.size >= MAX_VOCAB_CACHE_SIZE) {\n const oldestKey = vocabCache.keys().next().value;\n if (oldestKey !== undefined) vocabCache.delete(oldestKey);\n }\n vocabCache.set(cacheKey, vocab);\n return vocab;\n}","import { getTemporal, subscribeToTemporalChanges } from './temporalProvider.js';\nimport { canonicalCacheKey } from './localeVocab.js';\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// 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 timeZoneId?: 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\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 formatter = new Intl.DateTimeFormat(locale, options);\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 nativeSupport = true;\n } catch {\n // native Temporal absent, or present but not recognized by Intl — fall back\n }\n }\n return nativeSupport;\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 // 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 if (!intlSupportsNativeTemporal()) {\n return temporal.toLocaleString!(locale, formatterOptions);\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\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 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 if (!part) {\n throw new Error(`temporal-fmt: locale \"${locale}\" produced no \"dayPeriod\" part for token \"a\".`);\n }\n return part.value;\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 ['MMMM', (t, locale) => intlPart(t, locale, { month: 'long' }, 'month'), 'month'],\n ['MMM', (t, locale) => intlPart(t, locale, { month: 'short' }, 'month'), '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) => intlPart(t, locale, { weekday: 'long' }, 'weekday'), 'dayOfWeek'],\n ['EEE', (t, locale) => intlPart(t, locale, { weekday: 'short' }, 'weekday'), '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 ['SSS', (t) => pad(t.millisecond!, 3), '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];","import { TOKENS } from './tokens.js';\n\nexport type Piece =\n | { kind: 'token'; value: string }\n | { kind: 'literal'; value: string };\n\n// longest-first so the greedy scan never matches \"M\" when \"MMMM\" was there\nconst SORTED_TOKEN_STRINGS = TOKENS.map(([tok]) => tok).sort((a, b) => b.length - a.length);\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 const pieces: Piece[] = [];\n let i = 0;\n\n while (i < format.length) {\n const ch = format[i];\n\n if (ch === \"'\") {\n // check doubled-quote first or \"''best''\" parses wrong\n if (format[i + 1] === \"'\") {\n appendLiteral(pieces, \"'\");\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 Error(`temporal-fmt: unterminated quote in format string \"${format}\"`);\n }\n\n appendLiteral(pieces, literal);\n i = j;\n continue;\n }\n\n const match = SORTED_TOKEN_STRINGS.find((tok) => format.startsWith(tok, i));\n if (match) {\n pieces.push({ kind: 'token', value: match });\n i += match.length;\n continue;\n }\n\n // not a token or quote — pass through as-is\n appendLiteral(pieces, ch);\n i += 1;\n }\n\n return pieces;\n}\n\n// merges into the previous piece if it's also a literal, so \"---\" is one\n// piece instead of three\nfunction appendLiteral(pieces: Piece[], value: string): void {\n const last = pieces[pieces.length - 1];\n if (last && last.kind === 'literal') {\n last.value += value;\n } else {\n pieces.push({ kind: 'literal', value });\n }\n}\n","import { TOKENS, DEFAULT_LOCALE, type TemporalLike, type FormatOptions } from './tokens.js';\nimport { tokenize } from './tokenize.js';\nimport { MAX_FORMAT_LENGTH } from './constants.js';\n\nconst HANDLER_BY_TOKEN = new Map(TOKENS.map(([tok, fn, field]) => [tok, { fn, field }]));\n\n/**\n * Format a Temporal.PlainDate, PlainTime, PlainDateTime, or ZonedDateTime\n * using a date-fns-style token string.\n *\n * @example\n * format(Temporal.Now.plainDateISO(), 'yyyy-MM-dd') // \"2026-08-04\"\n * format(zdt, \"MMM d, yyyy 'at' h:mm a\") // \"Aug 4, 2026 at 3:45 PM\"\n * format(zdt, 'MMMM d, yyyy', { locale: 'fr-FR' }) // \"août 4, 2026\"\n *\n * Throws on a token the input type doesn't support (e.g. 'HH' on a PlainDate).\n */\nexport function format(temporal: TemporalLike, formatStr: string, options: FormatOptions = {}): string {\n if (formatStr.length > MAX_FORMAT_LENGTH) {\n throw new Error(\n `temporal-fmt: format string exceeds maximum length of ${MAX_FORMAT_LENGTH} characters ` +\n `(got ${formatStr.length}).`\n );\n }\n\n const locale = options.locale ?? DEFAULT_LOCALE;\n const pieces = tokenize(formatStr);\n let result = '';\n\n for (const piece of pieces) {\n if (piece.kind === 'literal') {\n result += piece.value;\n continue;\n }\n\n const handler = HANDLER_BY_TOKEN.get(piece.value);\n if (!handler) {\n // shouldn't happen — tokenize() only emits tokens from TOKENS\n throw new Error(`temporal-fmt: unknown token \"${piece.value}\"`);\n }\n\n if (temporal[handler.field] === undefined) {\n throw new Error(\n `temporal-fmt: token \"${piece.value}\" requires \"${handler.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 result += handler.fn(temporal, locale);\n }\n\n return result;\n}\n","import { getLocaleVocab } from './localeVocab.js';\n\nfunction escapeRegExp(literal: string): string {\n return literal.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n\nfunction alternation(values: string[]): string {\n return `(?:${values.map(escapeRegExp).join('|')})`;\n}\n\n// Every real IANA zone id is letters/digits/'_'/'+'/'-' segments joined by\n// '/' (e.g. \"America/Argentina/Buenos_Aires\", \"Etc/GMT+12\"); UTC and\n// fixed-offset strings are the only other shapes zzz accepts. Matching that\n// *shape* here — instead of alternating all ~400 zone names inline — keeps\n// the compiled regex small regardless of how many zzz tokens appear in a\n// format string. The captured text still gets checked against the real\n// zone set in isValidTimeZone() after the overall pattern matches, so this\n// is strictly a matching-cost change, not a validation-strictness change:\n// a bogus zone id fails \"no valid pattern matches\" exactly like it did when\n// the zone list was inlined (see isValidTimeZone's caller in parse.ts).\nconst TIME_ZONE_SHAPE = '(?:UTC|[+-]\\\\d{2}:\\\\d{2}(?::\\\\d{2}(?:\\\\.\\\\d{1,9})?)?|[A-Za-z_]+(?:[+-]\\\\d{1,2})?(?:\\\\/[A-Za-z0-9_+-]+)*)';\n\nfunction getTimeZoneFragment(): string {\n return TIME_ZONE_SHAPE;\n}\n\nlet validZoneSet: Set<string> | undefined;\n\n// Real zone ids plus the couple of aliases zzz has always accepted even\n// though Intl.supportedValuesOf('timeZone') doesn't list them (UTC isn't\n// itself an IANA zone name, it's the identity offset).\nfunction getValidZoneSet(): Set<string> {\n if (!validZoneSet) {\n validZoneSet = new Set(Intl.supportedValuesOf('timeZone'));\n validZoneSet.add('UTC');\n }\n return validZoneSet;\n}\n\nconst FIXED_OFFSET_RE = /^[+-]\\d{2}:\\d{2}(?::\\d{2}(?:\\.\\d{1,9})?)?$/;\n\n// Called post-match on whatever the bounded TIME_ZONE_SHAPE captured, since\n// that shape is deliberately looser than \"a real zone id\" (it has to be, to\n// stay a fixed-size regex fragment — see the comment above). A fixed offset\n// is valid by construction; anything else has to be a real IANA name.\nexport function isValidTimeZone(raw: string): boolean {\n return FIXED_OFFSET_RE.test(raw) || getValidZoneSet().has(raw);\n}\n\n// mirrors the ranges pad() in tokens.ts actually produces — keep in sync\n// if those ever change\n//\n// Unpadded alternatives (M, H, h, m, s) list the longer branch first\n// (e.g. '1[0-2]|[1-9]', not '[1-9]|1[0-2]'). This matters only when two\n// unpadded tokens are glued with no separator: with short-first ordering,\n// a regex engine takes the first successful overall match, and won't\n// backtrack into a token's second alternative unless its first choice\n// makes the *rest* of the pattern fail outright. If the short reading also\n// happens to leave a valid match for the next token, the engine stops\n// there — silently, deterministically, and with no relation to which\n// reading a human intended. E.g. \"Md\" against \"121\": short-first order\n// resolves it as month=1/day=21 (M grabs '1', d gets '21', which is a\n// valid day) instead of month=12/day=1. Longer-first ordering fixes this\n// by making the greedy match try to consume as many digits as possible\n// before ever handing digits to the next token, which is the reading\n// that matches how format() itself produces glued output in the first\n// place (format() always emits the token's natural width, so decoding\n// should prefer the same). Found via the token×token combinatorial glue\n// matrix in combinatorial.test.js — see that file for the full case list.\nconst NUMERIC_FRAGMENTS: Record<string, string> = {\n yy: '\\\\d{2}',\n MM: '(?:0[1-9]|1[0-2])',\n M: '(?:1[0-2]|[1-9])',\n dd: '(?:0[1-9]|[12]\\\\d|3[01])',\n d: '(?:[12]\\\\d|3[01]|[1-9])',\n HH: '(?:[01]\\\\d|2[0-3])',\n H: '(?:1\\\\d|2[0-3]|[0-9])',\n hh: '(?:0[1-9]|1[0-2])',\n h: '(?:1[0-2]|[1-9])',\n mm: '(?:[0-5]\\\\d)',\n m: '(?:[1-5]\\\\d|[0-9])',\n ss: '(?:[0-5]\\\\d)',\n s: '(?:[1-5]\\\\d|[0-9])',\n SSS: '\\\\d{3}',\n};\n\n// pad()'s year formatter (tokens.ts) never truncates: it preserves the sign\n// for BCE years and doesn't cap width past 9999, so a formatted \"yyyy\" can\n// be longer than 4 digits or start with '-'. YYYY_EXTENDED accepts that;\n// YYYY_EXACT is the plain 4-unsigned-digit case. Which one a given \"yyyy\"\n// occurrence gets depends on what follows it — see buildCapturingPattern in\n// parsePattern.ts. Two separate fragments instead of one `-?\\d{4,}` because\n// an open-ended-width year directly followed by another digit token (e.g.\n// \"yyyyMM\") lets the year's own greediness silently eat digits meant for\n// the next token — same class of bug as UNPADDED_NUMERIC_TOKENS below, but\n// unbounded-width, so it can't reuse enumerateValidSplits' fixed-range\n// splitting. Restricting to exactly 4 digits whenever something could\n// follow closes that off entirely, at the cost of \"yyyyMM\" not being able\n// to represent a 5-digit year — an already-rare case doubly rare in\n// combination with a glued adjacent token.\nconst YYYY_EXACT = '-?\\\\d{4}';\nconst YYYY_EXTENDED = '-?\\\\d{4,}';\n\n// True for any token whose matched text can start with a digit — i.e.\n// every token here except the locale-named ones (MMMM/MMM/EEEE/EEE/a) and\n// zzz (which can start with a digit only via a fixed offset like \"+09:00\",\n// already handled by requiring a leading sign there). Used to decide\n// whether a \"yyyy\" immediately before this token needs the exact-4-digit\n// fragment instead of the open-ended one.\nconst DIGIT_LEADING_TOKENS = new Set([\n 'yyyy', 'yy', 'MM', 'M', 'dd', 'd', 'HH', 'H', 'hh', 'h', 'mm', 'm', 'ss', 's', 'SSS',\n]);\n\nexport function tokenFragment(token: string, locale: string, nextToken?: string): string {\n if (token === 'yyyy') {\n return nextToken !== undefined && DIGIT_LEADING_TOKENS.has(nextToken) ? YYYY_EXACT : YYYY_EXTENDED;\n }\n\n const numeric = NUMERIC_FRAGMENTS[token];\n if (numeric) {\n return numeric;\n }\n\n const vocab = getLocaleVocab(locale);\n switch (token) {\n case 'MMMM': return alternation(vocab.monthLong);\n case 'MMM': return alternation(vocab.monthShort);\n case 'EEEE': return alternation(vocab.weekdayLong);\n case 'EEE': return alternation(vocab.weekdayShort);\n case 'a': return alternation(vocab.dayPeriod);\n case 'zzz': return getTimeZoneFragment();\n default:\n throw new Error(`temporal-fmt: unknown token \"${token}\"`);\n }\n}\n\n// Tokens whose fragment is variable-width (1-2 digits, no leading zero).\n// Two or more of these glued with no literal separator between them can\n// have more than one digit-split that's independently valid against every\n// fragment in the run — see the big comment on NUMERIC_FRAGMENTS above.\n// Reordering alternation branches picks a winner for *some* of these\n// cases, but can't make both directions of a pair (e.g. \"Md\" and \"dM\")\n// agree, because the ambiguity is in the input string itself, not in how\n// any one fragment is written. exported so parsePattern.ts can find runs\n// of these that need split-counting at match time instead of a single\n// fixed regex.\nexport const UNPADDED_NUMERIC_TOKENS = new Set(['M', 'd', 'H', 'h', 'm', 's']);\n\n// Every accept width for a given unpadded numeric token, as plain min/max\n// value + digit-length pairs — used to enumerate candidate splits of a\n// digit run at match time. Mirrors NUMERIC_FRAGMENTS's semantics exactly\n// (same accepted values), just as data instead of regex source, since\n// enumerating splits against a compiled regex per-candidate would be\n// slower and harder to reason about than checking numeric ranges directly.\nexport const UNPADDED_NUMERIC_RANGES: Record<string, Array<{ digits: 1 | 2; min: number; max: number }>> = {\n M: [{ digits: 1, min: 1, max: 9 }, { digits: 2, min: 10, max: 12 }],\n d: [{ digits: 1, min: 1, max: 9 }, { digits: 2, min: 10, max: 31 }],\n H: [{ digits: 1, min: 0, max: 9 }, { digits: 2, min: 10, max: 23 }],\n h: [{ digits: 1, min: 1, max: 9 }, { digits: 2, min: 10, max: 12 }],\n m: [{ digits: 1, min: 0, max: 9 }, { digits: 2, min: 10, max: 59 }],\n s: [{ digits: 1, min: 0, max: 9 }, { digits: 2, min: 10, max: 59 }],\n};\n\n/**\n * Given the literal digit string a run of N adjacent unpadded-numeric\n * tokens matched as a whole (e.g. \"112\" for a 2-token run), enumerates\n * every way to split it into N pieces (one per token, each piece 1-2\n * digits per that token's own width rule) and returns every split where\n * every piece is independently valid for its token. Length 0 means the\n * run's regex match shouldn't have been possible in the first place\n * (shouldn't happen — the caller only invokes this after the whole\n * pattern already matched, meaning at least one split exists: the one the\n * regex actually took). Length 1 means the reading is unambiguous.\n * Length 2+ means true ambiguity — the caller should throw rather than\n * pick one.\n *\n * Recursive over token count rather than hardcoded to 2, so a 3+ token\n * unseparated run (e.g. \"Hms\") is covered by the same logic without a\n * special case — those are rarer in practice but not impossible, and a\n * partial fix that only covered pairs would leave the identical bug for\n * anyone writing a 3-token glued run.\n */\nexport function enumerateValidSplits(digits: string, tokens: string[]): number[][] {\n const memo = new Map<string, number[][]>();\n\n function solve(tokenIndex: number, offset: number): number[][] {\n const key = `${tokenIndex}:${offset}`;\n const cached = memo.get(key);\n if (cached) {\n return cached;\n }\n\n if (tokenIndex === tokens.length) {\n const result = offset === digits.length ? [[]] : [];\n memo.set(key, result);\n return result;\n }\n\n const token = tokens[tokenIndex];\n const ranges = UNPADDED_NUMERIC_RANGES[token!];\n if (!ranges) {\n throw new Error(`temporal-fmt: internal error — \"${token}\" is not an unpadded numeric token`);\n }\n\n const results: number[][] = [];\n for (const { digits: width, min, max } of ranges) {\n if (offset + width > digits.length) continue;\n const piece = digits.slice(offset, offset + width);\n if (width === 2 && piece[0] === '0') continue;\n const value = Number(piece);\n if (value < min || value > max) continue;\n\n for (const restSplit of solve(tokenIndex + 1, offset + width)) {\n results.push([value, ...restSplit]);\n if (results.length === 2) break;\n }\n if (results.length === 2) break;\n }\n\n memo.set(key, results);\n return results;\n }\n\n return solve(0, 0);\n}","import type { Piece } from './tokenize.js';\nimport { tokenFragment, UNPADDED_NUMERIC_TOKENS } from './pattern.js';\n\nfunction escapeRegExp(literal: string): string {\n return literal.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n\nexport interface CapturingPattern {\n regex: RegExp;\n groups: Array<{ name: string; token: string }>; // token pieces, in order\n // Runs of 2+ adjacent unpadded-numeric tokens with no literal separator\n // between them (e.g. \"Md\", \"dM\", \"Hm\") — these need a split-ambiguity\n // check at match time, since a single fixed regex can't distinguish\n // \"the only valid split\" from \"one of several valid splits taken\n // arbitrarily\". Empty for the overwhelmingly common case (no such runs).\n ambiguousRuns: Array<{ groupNames: string[]; tokens: string[] }>;\n}\n\n/**\n * Same walk as buildPatternSource() in pattern.ts, but each token piece\n * gets its own named capture group (positionally named so the same token,\n * e.g. \"yyyy\", could in theory appear twice) so a caller can pull the\n * matched substring for each token back out after a successful match.\n */\nexport function buildCapturingPattern(pieces: Piece[], locale: string): CapturingPattern {\n const groups: Array<{ name: string; token: string }> = [];\n const ambiguousRuns: Array<{ groupNames: string[]; tokens: string[] }> = [];\n let source = '';\n let i = 0;\n\n // tracks the group names/tokens of the current run of adjacent unpadded\n // numeric token pieces (no literal piece has broken it yet)\n let currentRun: { groupNames: string[]; tokens: string[] } = { groupNames: [], tokens: [] };\n const flushRun = () => {\n if (currentRun.tokens.length >= 2) {\n ambiguousRuns.push(currentRun);\n }\n currentRun = { groupNames: [], tokens: [] };\n };\n\n for (const [idx, piece] of pieces.entries()) {\n if (piece.kind === 'literal') {\n source += escapeRegExp(piece.value);\n flushRun(); // a literal (even a single character) breaks adjacency\n continue;\n }\n const name = `g${i++}`;\n groups.push({ name, token: piece.value });\n const nextPiece = pieces[idx + 1];\n const nextToken = nextPiece?.kind === 'token' ? nextPiece.value : undefined;\n source += `(?<${name}>${tokenFragment(piece.value, locale, nextToken)})`;\n\n if (UNPADDED_NUMERIC_TOKENS.has(piece.value)) {\n currentRun.groupNames.push(name);\n currentRun.tokens.push(piece.value);\n } else {\n flushRun(); // a non-unpadded-numeric token (padded, locale-aware, zzz) also breaks adjacency\n }\n }\n flushRun();\n\n return { regex: new RegExp(`^(?:${source})$`, 'u'), groups, ambiguousRuns };\n}","import { DEFAULT_LOCALE, type FormatOptions } from './tokens.js';\nimport { tokenize } from './tokenize.js';\nimport { buildCapturingPattern, type CapturingPattern } from './parsePattern.js';\nimport { enumerateValidSplits, isValidTimeZone } from './pattern.js';\nimport { getLocaleVocab, canonicalCacheKey } from './localeVocab.js';\nimport { getTemporal } from './temporalProvider.js';\nimport { MAX_FORMAT_LENGTH, MAX_INPUT_LENGTH } from './constants.js';\n\n// format strings are short hand-written literals reused across many calls —\n// cache the compiled capturing pattern per (formatStr, locale) pair instead\n// of rebuilding it every call.\nconst patternCache = new Map<string, CapturingPattern>();\nconst MAX_CACHE_SIZE = 500;\n\nfunction getPattern(formatStr: string, locale: string): CapturingPattern {\n const key = JSON.stringify([canonicalCacheKey(locale), formatStr]);\n let pattern = patternCache.get(key);\n if (pattern) {\n return pattern;\n }\n if (patternCache.size >= MAX_CACHE_SIZE) {\n const oldestKey = patternCache.keys().next().value;\n if (oldestKey !== undefined) patternCache.delete(oldestKey);\n }\n pattern = buildCapturingPattern(tokenize(formatStr), locale);\n patternCache.set(key, pattern);\n return pattern;\n}\n\n// Requires an explicit `-u-ca-` extension (e.g. 'en-u-ca-hebrew') to apply\n// a non-Gregorian calendar, per parse()'s own docstring. 'gregory' counts\n// as \"no calendar\" so the default locale keeps constructing plain ISO 8601.\n//\n// Used to key off resolvedOptions().calendar instead — a locale's\n// *default* calendar, whether the caller asked for one or not. That broke\n// th-TH silently: its default is 'buddhist', so plain Gregorian-looking\n// digits parsed 543 years off, while format() has no matching calendar\n// step and just prints the object's own ISO fields either way.\nconst calendarCache = new Map<string, string | undefined>();\nconst MAX_CALENDAR_CACHE_SIZE = 500;\n\nfunction resolveCalendar(locale: string): string | undefined {\n // computed up front (not just at cache-miss time) so the cache key is\n // the canonical form too — otherwise 'en-US' and 'en-us' would each get\n // their own entry for what's really the same locale (see\n // canonicalCacheKey's comment in localeVocab.ts for why that matters).\n // Left un-lowercased/un-canonicalized calls into new Intl.Locale() below\n // would still throw on a genuinely malformed tag either way; doing it\n // here just means that throw happens before touching the cache instead\n // of after, which is the more natural place for it.\n const canonicalLocale = new Intl.Locale(locale).toString().toLowerCase();\n if (calendarCache.has(canonicalLocale)) {\n return calendarCache.get(canonicalLocale);\n }\n if (calendarCache.size >= MAX_CALENDAR_CACHE_SIZE) {\n const oldestKey = calendarCache.keys().next().value;\n if (oldestKey !== undefined) calendarCache.delete(oldestKey);\n }\n let calendar: string | undefined;\n const parts = canonicalLocale.split('-');\n const extensionIndex = parts.indexOf('u');\n const calendarKeyIndex = extensionIndex === -1 ? -1 : parts.indexOf('ca', extensionIndex + 1);\n if (calendarKeyIndex !== -1 && calendarKeyIndex + 1 < parts.length) {\n const resolved = new Intl.DateTimeFormat(canonicalLocale).resolvedOptions().calendar;\n calendar = resolved === 'gregory' ? undefined : resolved;\n }\n calendarCache.set(canonicalLocale, calendar);\n return calendar;\n}\n\ninterface Fields {\n year?: number;\n twoDigitYear?: number;\n month?: number;\n day?: number;\n hour?: number;\n hour12?: number;\n dayPeriodRaw?: string;\n isPM?: boolean;\n minute?: number;\n second?: number;\n millisecond?: number;\n timeZoneId?: string;\n weekdayExpected?: number;\n weekdayRaw?: string;\n}\n\nfunction assignField<T>(fields: Fields, key: keyof Fields, value: T): void {\n (fields as Record<string, T | undefined>)[key] = value;\n}\n\nfunction applyGroup(fields: Fields, token: string, raw: string, locale: string, formatStr: string): void {\n const vocab = getLocaleVocab(locale);\n switch (token) {\n case 'yyyy':\n assignField(fields, 'year', Number(raw));\n break;\n case 'yy':\n assignField(fields, 'twoDigitYear', Number(raw));\n break;\n case 'MM': case 'M':\n assignField(fields, 'month', Number(raw));\n break;\n case 'MMMM':\n assignField(fields, 'month', vocab.monthLong.indexOf(raw) + 1);\n break;\n case 'MMM':\n assignField(fields, 'month', vocab.monthShort.indexOf(raw) + 1);\n break;\n case 'dd': case 'd':\n assignField(fields, 'day', Number(raw));\n break;\n case 'EEEE':\n assignField(fields, 'weekdayRaw', raw);\n assignField(fields, 'weekdayExpected', vocab.weekdayLong.indexOf(raw) + 1);\n break;\n case 'EEE':\n assignField(fields, 'weekdayRaw', raw);\n assignField(fields, 'weekdayExpected', vocab.weekdayShort.indexOf(raw) + 1);\n break;\n case 'HH': case 'H':\n assignField(fields, 'hour', Number(raw));\n break;\n case 'hh': case 'h':\n assignField(fields, 'hour12', Number(raw));\n break;\n case 'mm': case 'm':\n assignField(fields, 'minute', Number(raw));\n break;\n case 'ss': case 's':\n assignField(fields, 'second', Number(raw));\n break;\n case 'SSS':\n assignField(fields, 'millisecond', Number(raw));\n break;\n case 'a': {\n const periodIndex = vocab.dayPeriod.indexOf(raw);\n if (periodIndex < 0) throw new Error(`temporal-fmt: unknown day period \"${raw}\" for locale \"${locale}\".`);\n assignField(fields, 'dayPeriodRaw', raw);\n assignField(fields, 'isPM', periodIndex === 1);\n break;\n }\n case 'zzz':\n assignField(fields, 'timeZoneId', raw);\n break;\n }\n}\n\n// emulates strptime (POSIX) for 2-digit years so the result doesn't depend\n// on the current clock: 00-68 -> 2000-2068, 69-99 -> 1900-1999\n// https://www.man7.org/linux//man-pages/man3/strptime.3p.html\nfunction resolveYear(fields: Fields): number | undefined {\n if (fields.year !== undefined && fields.twoDigitYear !== undefined) {\n throw new Error(\n 'temporal-fmt: format string mixes \"yyyy\" and \"yy\" year representations.'\n );\n }\n if (fields.year !== undefined) return fields.year;\n if (fields.twoDigitYear !== undefined) {\n return fields.twoDigitYear <= 68 ? 2000 + fields.twoDigitYear : 1900 + fields.twoDigitYear;\n }\n return undefined;\n}\n\nfunction resolveHour(fields: Fields, formatStr: string, locale: string): number | undefined {\n if (fields.hour !== undefined && fields.hour12 !== undefined) {\n throw new Error(\n `temporal-fmt: format string \"${formatStr}\" mixes a 24-hour token (\"HH\"/\"H\") with a ` +\n `12-hour token (\"hh\"/\"h\").`\n );\n }\n if (fields.hour !== undefined) {\n if (fields.dayPeriodRaw !== undefined) {\n const vocab = getLocaleVocab(locale);\n const expected = fields.hour < 12 ? vocab.dayPeriod[0] : vocab.dayPeriod[1];\n if (fields.dayPeriodRaw !== expected) {\n throw new Error(\n `temporal-fmt: format string \"${formatStr}\" contains a day period that contradicts the 24-hour value.`\n );\n }\n }\n return fields.hour;\n }\n if (fields.hour12 !== undefined) {\n if (fields.isPM === undefined) {\n throw new Error(\n `temporal-fmt: format string \"${formatStr}\" uses a 12-hour token (\"hh\"/\"h\") without an \"a\" token, ` +\n `so parse() can't tell AM from PM.`\n );\n }\n return (fields.hour12 % 12) + (fields.isPM ? 12 : 0);\n }\n return undefined;\n}\n\n/**\n * Parses `input` against `formatStr` and builds the real Temporal value it\n * describes: a `Temporal.PlainDate`, `PlainTime`, `PlainDateTime`, or\n * `ZonedDateTime` depending on which tokens are present.\n *\n * Returns `unknown` — this package has no ambient `Temporal` types to return\n * a real one against.\n *\n * `options.locale` picks the calendar the result is built in. Pass a locale\n * tag with a `-u-ca-` extension (e.g. `'en-u-ca-hebrew'`) to parse into a\n * non-Gregorian calendar.\n *\n * @throws if `input` doesn't match `formatStr`'s shape at all\n * @throws if it matches the shape but describes an impossible date (e.g. Feb\n * 30) or self-contradictory data (e.g. a weekday name that doesn't match the\n * actual date)\n *\n * @example\n * parse('yyyy-MM-dd HH:mm', '2026-08-04 15:45') // Temporal.PlainDateTime\n * parse('yyyy-MM', '2026-08-04T15:45:30') // throws — shape doesn't match\n * parse('yyyy-MM-dd', '2026-02-30') // throws — not a real date\n */\nexport function parse(formatStr: string, input: string, options: FormatOptions = {}): unknown | undefined {\n if (formatStr.length > MAX_FORMAT_LENGTH) {\n throw new Error(\n `temporal-fmt: format string exceeds maximum length of ${MAX_FORMAT_LENGTH} characters ` +\n `(got ${formatStr.length}).`\n );\n }\n\n if (input.length > MAX_INPUT_LENGTH) {\n throw new Error(\n `temporal-fmt: input exceeds maximum length of ${MAX_INPUT_LENGTH} characters (got ${input.length}).`\n );\n }\n\n const locale = options.locale ?? DEFAULT_LOCALE;\n const calendar = resolveCalendar(locale);\n const pattern = getPattern(formatStr, locale);\n const match = pattern.regex.exec(input);\n if (!match) {\n throw new Error(`temporal-fmt: no valid pattern matches the format string and input shape`);\n }\n\n if (pattern.groups.length === 0) {\n throw new Error(`temporal-fmt: format string \"${formatStr}\" has no tokens — nothing to parse into a value.`);\n }\n\n // The regex's zzz fragment only matches a bounded zone-id *shape* (see\n // TIME_ZONE_SHAPE in pattern.ts) rather than alternating every real IANA\n // name inline, so a shape match isn't proof of a real zone yet — check\n // each captured zzz group against the actual zone list here. Kept as the\n // same \"no valid pattern matches\" error the inline-alternation version\n // used to throw, since from the caller's perspective this is still the\n // regex rejecting the input, just checked in two steps instead of one.\n for (const { name, token } of pattern.groups) {\n if (token === 'zzz' && !isValidTimeZone(match.groups![name]!)) {\n throw new Error(`temporal-fmt: no valid pattern matches the format string and input shape`);\n }\n }\n\n // A run of 2+ adjacent unpadded-numeric tokens with no literal separator\n // (e.g. \"Md\", \"dM\", \"Hms\") can have more than one way to split the\n // digits it matched that's independently valid for every token in the\n // run — see the comment on NUMERIC_FRAGMENTS in pattern.ts for the\n // mechanism. The regex above only ever finds one such split (whichever\n // its alternation ordering happens to prefer); silently trusting that\n // one would mean parse() sometimes returns a value indistinguishable\n // from a different, equally valid value the same input could describe.\n // Rather than guess, check every ambiguous run explicitly and throw if\n // more than one split is actually valid for the substring this call\n // matched — the input itself is what's ambiguous, not a fixable\n // property of the pattern.\n for (const run of pattern.ambiguousRuns) {\n const runDigits = run.groupNames.map((name) => match.groups![name]!).join('');\n const splits = enumerateValidSplits(runDigits, run.tokens);\n if (splits.length > 1) {\n throw new Error(\n `temporal-fmt: \"${runDigits}\" in format string \"${formatStr}\" is ambiguous — ` +\n `${splits.length} different ways to read tokens \"${run.tokens.join('')}\" (with no separator ` +\n `between them) are all individually valid (e.g. ${JSON.stringify(splits[0])} vs ${JSON.stringify(splits[1])}). ` +\n `parse() won't guess; add a separator between these tokens, or use their padded form ` +\n `(e.g. \"MM\" instead of \"M\") so each one has a fixed width.`\n );\n }\n }\n\n const fields: Fields = {};\n for (const { name, token } of pattern.groups) {\n applyGroup(fields, token, match.groups![name]!, locale, formatStr);\n }\n\n const year = resolveYear(fields);\n const hour = resolveHour(fields, formatStr, locale);\n const { month, day, minute, second, millisecond, timeZoneId, weekdayExpected, weekdayRaw } = fields;\n\n const hasAnyDatePart = year !== undefined || month !== undefined || day !== undefined;\n const hasFullDate = year !== undefined && month !== undefined && day !== undefined;\n if (hasAnyDatePart && !hasFullDate) {\n throw new Error(\n `temporal-fmt: format string \"${formatStr}\" has an incomplete date — ` +\n `year, month, and day tokens must all be present together.`\n );\n }\n\n const hasTime = hour !== undefined || minute !== undefined || second !== undefined || millisecond !== undefined;\n\n if (timeZoneId !== undefined && !(hasFullDate && hasTime)) {\n throw new Error(\n `temporal-fmt: format string \"${formatStr}\" has a \"zzz\" token but needs a full date and time ` +\n `to build a ZonedDateTime.`\n );\n }\n\n if (weekdayExpected !== undefined && !hasFullDate) {\n throw new Error(\n `temporal-fmt: format string \"${formatStr}\" has a weekday token (\"EEEE\"/\"EEE\") but needs ` +\n `a full date to validate it against.`\n );\n }\n\n if (!hasFullDate && !hasTime) {\n // shouldn't happen — every token maps to a date, time, zone, or\n // weekday field, and weekday-without-date already threw above\n throw new Error(`temporal-fmt: format string \"${formatStr}\" has no date or time tokens to parse.`);\n }\n\n const temporal = getTemporal();\n const timeFields = { hour: hour ?? 0, minute: minute ?? 0, second: second ?? 0, millisecond: millisecond ?? 0 };\n // omitted entirely for the default calendar (see resolveCalendar) so\n // construction stays plain ISO 8601 unless a caller's locale asks for\n // something else — Temporal calendars don't apply to time-only values.\n const calendarField = calendar ? { calendar } : {};\n\n // overflow: 'reject' — without it Temporal *clamps* out-of-range fields\n // (Feb 30 silently becomes Feb 28) instead of throwing, which would\n // contradict the \"throws on genuinely invalid data\" behavior parse() promises.\n const reject = { overflow: 'reject' as const };\n\n let result: unknown;\n try {\n if (timeZoneId !== undefined) {\n result = temporal.ZonedDateTime.from({ year: year!, month: month!, day: day!, ...timeFields, ...calendarField, timeZone: timeZoneId }, reject);\n } else if (hasFullDate && hasTime) {\n result = temporal.PlainDateTime.from({ year: year!, month: month!, day: day!, ...timeFields, ...calendarField }, reject);\n } else if (hasFullDate) {\n result = temporal.PlainDate.from({ year: year!, month: month!, day: day!, ...calendarField }, reject);\n } else {\n result = temporal.PlainTime.from(timeFields, reject);\n }\n } catch (err) {\n throw new Error(\n `temporal-fmt: \"${input}\" doesn't describe a valid date/time for format \"${formatStr}\": ` +\n `${(err as Error).message}`\n );\n }\n\n if (weekdayExpected !== undefined) {\n const actual = (result as { dayOfWeek: number }).dayOfWeek;\n if (actual !== weekdayExpected) {\n const vocab = getLocaleVocab(locale);\n throw new Error(\n `temporal-fmt: \"${weekdayRaw}\" doesn't match the actual weekday (${vocab.weekdayLong[actual - 1]}) ` +\n `for the parsed date.`\n );\n }\n }\n\n return result;\n}"],"mappings":"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","names":["index_exports","__export","format","parse","setTemporal","__toCommonJS","injectedTemporal","onTemporalChanged","subscribeToTemporalChanges","listener","setTemporal","temporal","resolveTemporal","getTemporal","canonicalCacheKey","locale","vocabCache","MAX_VOCAB_CACHE_SIZE","partValue","formatter","date","type","parts","index","value","prev","next","assertNoCollision","names","label","seen","i","prior","getLocaleVocab","cacheKey","cached","monthLongFmt","monthShortFmt","monthLong","monthShort","m","weekdayLongFmt","weekdayShortFmt","weekdayLong","weekdayShort","d","dayPeriodFmt","am","pm","dayPeriod","vocab","oldestKey","pad","n","len","negative","digits","DEFAULT_LOCALE","formatterCache","MAX_CACHE_SIZE","getFormatter","locale","options","key","canonicalCacheKey","formatter","oldestKey","nativeSupport","subscribeToTemporalChanges","intlSupportsNativeTemporal","temporal","getTemporal","intlPart","partType","calendar","formatterOptions","toInstant","timeZoneId","isZoned","intlSafeTemporal","nativeOptions","parts","index","p","value","prev","next","dayPeriodPart","hour","date","part","TOKENS","t","SORTED_TOKEN_STRINGS","TOKENS","tok","a","b","tokenize","format","pieces","i","ch","appendLiteral","j","literal","closed","match","value","last","HANDLER_BY_TOKEN","TOKENS","tok","fn","field","format","temporal","formatStr","options","locale","DEFAULT_LOCALE","pieces","tokenize","result","piece","handler","escapeRegExp","literal","alternation","values","TIME_ZONE_SHAPE","getTimeZoneFragment","validZoneSet","getValidZoneSet","FIXED_OFFSET_RE","isValidTimeZone","raw","NUMERIC_FRAGMENTS","YYYY_EXACT","YYYY_EXTENDED","DIGIT_LEADING_TOKENS","tokenFragment","token","locale","nextToken","numeric","vocab","getLocaleVocab","UNPADDED_NUMERIC_TOKENS","UNPADDED_NUMERIC_RANGES","enumerateValidSplits","digits","tokens","memo","solve","tokenIndex","offset","key","cached","result","ranges","results","width","min","max","piece","value","restSplit","escapeRegExp","literal","buildCapturingPattern","pieces","locale","groups","ambiguousRuns","source","i","currentRun","flushRun","idx","piece","name","nextPiece","nextToken","tokenFragment","UNPADDED_NUMERIC_TOKENS","patternCache","MAX_CACHE_SIZE","getPattern","formatStr","locale","key","canonicalCacheKey","pattern","oldestKey","buildCapturingPattern","tokenize","calendarCache","MAX_CALENDAR_CACHE_SIZE","resolveCalendar","canonicalLocale","calendar","parts","extensionIndex","calendarKeyIndex","resolved","assignField","fields","value","applyGroup","token","raw","vocab","getLocaleVocab","periodIndex","resolveYear","resolveHour","expected","parse","input","options","DEFAULT_LOCALE","match","name","isValidTimeZone","run","runDigits","splits","enumerateValidSplits","year","hour","month","day","minute","second","millisecond","timeZoneId","weekdayExpected","weekdayRaw","hasAnyDatePart","hasFullDate","hasTime","temporal","getTemporal","timeFields","calendarField","reject","result","err","actual"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/temporalProvider.ts","../src/localeVocab.ts","../src/isoWeek.ts","../src/tokens.ts","../src/tokenize.ts","../src/format.ts","../src/pattern.ts","../src/parsePattern.ts","../src/parse.ts","../src/formatDuration.ts","../src/formatDistance.ts","../src/parseRelative.ts"],"sourcesContent":["export { format } from './format.js';\nexport { parse } from './parse.js';\nexport { setTemporal } from './temporalProvider.js';\nexport { formatDuration } from './formatDuration.js';\nexport { formatDistance } from './formatDistance.js';\nexport { parseRelative } from './parseRelative.js';\nexport { registerLocaleVocab } from './localeVocab.js';\nexport type { LocaleVocab } from './localeVocab.js';\nexport type { TemporalLike, FormatOptions } from './tokens.js';\nexport type { DurationFormatOptions } from './formatDuration.js';\nexport type { FormatDistanceOptions } from './formatDistance.js';\nexport type { ParseRelativeOptions } from './parseRelative.js';\nexport type { TemporalNamespace } from './temporalProvider.js';\n","// This package's tsconfig assumes lib: [\"ESNext\"] only — no ambient\n// `Temporal` namespace type. Everywhere else in this codebase only ever\n// *reads* fields off a Temporal-like object the caller already built\n// (TemporalLike in tokens.ts). This module is the single choke point for\n// touching a *namespace*-shaped Temporal (PlainDate.from, etc.): parse()\n// uses it to construct a result, tokens.ts uses it to probe native\n// Intl<->Temporal support. Consumers can hand us an implementation via\n// setTemporal(), or we fall back to globalThis.Temporal\ninterface TemporalFactory {\n from(fields: Record<string, number | string | undefined>, options?: { overflow?: 'constrain' | 'reject' }): unknown;\n // compare() lives on the namespace (Temporal.PlainDate.compare), not on\n // instances. Optional here since a consumer might be passing a\n // stripped-down Temporal shim that doesn't expose it.\n compare?(one: unknown, two: unknown): number;\n}\n\nexport interface TemporalNamespace {\n PlainDate: TemporalFactory;\n PlainTime: TemporalFactory;\n PlainDateTime: TemporalFactory;\n ZonedDateTime: TemporalFactory;\n}\n\nlet injectedTemporal: TemporalNamespace | undefined;\n\n// Anything that caches a result derived from *which* Temporal\n// implementation is active (right now: tokens.ts's native-Intl-support\n// probe) registers here so it gets invalidated whenever setTemporal()\n// swaps the implementation — see the comment on setTemporal() below for\n// why that matters. A plain array instead of an event-emitter-style API\n// since this only ever needs \"call every listener, in registration order,\n// with no payload\" — nothing here needs unsubscribe or payload data.\nconst onTemporalChanged: Array<() => void> = [];\n\nexport function subscribeToTemporalChanges(listener: () => void): void {\n onTemporalChanged.push(listener);\n}\n\n/**\n * Explicitly hand temporal-fmt the Temporal implementation to use, instead\n * of relying on a global `Temporal`. Call this once, before your first\n * `format()`/`parse()`\n *\n * Call with no argument (or `undefined`) to clear the override and fall\n * back to `globalThis.Temporal` again.\n *\n * @example\n * import { Temporal } from 'temporal-polyfill';\n * import { setTemporal } from 'temporal-fmt';\n * setTemporal(Temporal);\n */\nexport function setTemporal(temporal?: TemporalNamespace): void {\n injectedTemporal = temporal;\n // tokens.ts's native-Intl-support probe is keyed on whichever Temporal\n // implementation was active the first time a locale-aware format() ran —\n // if that implementation changes later (native -> polyfill or back), the\n // memoized probe result can go stale and disagree with what's now\n // actually active. Resetting it here means the next locale-aware format()\n // after any setTemporal() call re-probes against the implementation\n // that's active *now*, at the (small, one-time-per-switch) cost of\n // re-running the probe.\n for (const listener of onTemporalChanged) listener();\n}\n\nfunction resolveTemporal(): TemporalNamespace | undefined {\n return injectedTemporal ?? (globalThis as unknown as { Temporal?: TemporalNamespace }).Temporal;\n}\n\nexport function getTemporal(): TemporalNamespace {\n const temporal = resolveTemporal();\n if (!temporal) {\n throw new Error(\n 'temporal-fmt: parse() needs a Temporal implementation to construct its result. ' +\n 'Call setTemporal(Temporal) once at startup, or assign one to globalThis.Temporal ' +\n '(native on Node 26+, or a polyfill like temporal-polyfill).'\n );\n }\n return temporal;\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\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>();\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 });\n }\n\n // Reuse the same collision check the Intl-derived path uses — a\n // duplicate month name is just as ambiguous when supplied by a caller\n // as when produced by Intl.\n assertNoCollision(vocab.monthLong!, 'MMMM month', locale);\n assertNoCollision(vocab.monthShort!, 'MMM month', locale);\n assertNoCollision(vocab.weekdayLong!, 'EEEE weekday', locale);\n assertNoCollision(vocab.weekdayShort!, 'EEE weekday', locale);\n\n // dayPeriod entries must differ from each other, or parse()'s\n // isPM check (raw === vocab.dayPeriod[1]) can never return true and\n // every 12-hour parse silently resolves to AM. The Intl-derived path\n // dedupes a same-AM/PM collision to length 1, but a caller passing\n // both entries identical is a real bug to surface — not something to\n // dedupe around.\n if (vocab.dayPeriod![0] === vocab.dayPeriod![1]) {\n throw new Error(\n `temporal-fmt: registerLocaleVocab for locale \"${locale}\": dayPeriod entries must differ ` +\n `(both are \"${vocab.dayPeriod![0]}\"); otherwise parse() can't tell AM from PM.`\n );\n }\n}\n\n/**\n * Supply a custom month/weekday/day-period vocabulary for a locale key,\n * overriding the Intl-derived vocab this library would otherwise build\n * for that key. Useful for locales Intl doesn't cover well — the README's\n * known-limitations section calls out the Hebrew leap-month gap as a\n * specific case this addresses.\n *\n * Throws descriptively on malformed input (wrong array lengths, empty\n * strings, duplicate entries, missing fields) rather than failing later\n * during format/parse.\n *\n * Registered vocab takes precedence over the Intl-derived vocab for that\n * locale key, including for already-cached entries — registering\n * invalidates the prior cache entry for that locale so the next call\n * picks up the new vocab.\n *\n * @example\n * registerLocaleVocab('en-u-ca-hebrew-leap', {\n * monthLong: ['Nisan','Iyar','Sivan','Tammuz','Av','Elul','Tishrei','Marcheshvan','Kislev','Tevet','Shevat','Adar I','Adar II'],\n * monthShort: ['Nis','Iyy','Siv','Tam','Av','Elu','Tish','Chesh','Kis','Tev','Shv','Ad1','Ad2'],\n * weekdayLong: ['Monday','Tuesday','Wednesday','Thursday','Friday','Saturday','Sunday'],\n * weekdayShort: ['Mon','Tue','Wed','Thu','Fri','Sat','Sun'],\n * dayPeriod: ['AM','PM'],\n * });\n */\nexport function registerLocaleVocab(locale: string, vocab: Partial<LocaleVocab>): void {\n if (typeof locale !== 'string' || locale.length === 0) {\n throw new Error(`temporal-fmt: registerLocaleVocab requires a non-empty locale string, got ${String(locale)}.`);\n }\n assertValidVocab(vocab, locale);\n\n const cacheKey = canonicalCacheKey(locale);\n customVocabs.set(cacheKey, {\n monthLong: [...vocab.monthLong!],\n monthShort: [...vocab.monthShort!],\n weekdayLong: [...vocab.weekdayLong!],\n weekdayShort: [...vocab.weekdayShort!],\n dayPeriod: [...vocab.dayPeriod!],\n });\n // Invalidate the Intl-derived cache entry so any prior format/parse\n // result cached for this locale is rebuilt against the new vocab. Not\n // strictly necessary (getLocaleVocab checks customVocabs first), but\n // cheap and keeps the two caches from drifting out of sync.\n vocabCache.delete(cacheKey);\n}\n\n// Every locale-keyed cache in this library (this one, formatterCache in\n// tokens.ts, patternCache/calendarCache in parse.ts) used to key on the\n// exact locale string a caller passed in. Intl treats spelling variants of\n// the same locale as equivalent ('en-US' / 'en-us' / 'en_US' all resolve\n// the same way), but a plain string-keyed Map doesn't — so callers mixing\n// spellings for what's really one locale would silently fragment across\n// separate cache entries instead of sharing one, making the bounded\n// eviction limits (MAX_*_CACHE_SIZE) less effective than they look. This\n// doesn't change any cache's *correctness* (each entry is still built from\n// -- and valid for -- whatever locale string produced it), only how many\n// distinct entries equivalent spellings end up costing. Falls back to the\n// original string on a malformed/unrecognized tag rather than throwing —\n// cache-key normalization shouldn't be where a bad locale first surfaces\n// as an error; whatever actually calls `new Intl.DateTimeFormat(locale)`\n// downstream is the right place for that.\nexport function canonicalCacheKey(locale: string): string {\n try {\n // Intl.Locale requires BCP-47 hyphens and rejects underscore-separated\n // tags like 'en_US' outright (RangeError), rather than normalizing\n // them — so without this replace, that spelling would just fall\n // through to the catch below and never fold with 'en-US'.\n return new Intl.Locale(locale.replace(/_/g, '-')).toString().toLowerCase();\n } catch {\n return locale;\n }\n}\n\nconst vocabCache = new Map<string, LocaleVocab>();\nconst MAX_VOCAB_CACHE_SIZE = 500;\n\n// Some locales (ja-JP) split a field across two parts — month \"8\" plus a\n// counter suffix \"月\" as a separate sibling \"literal\" — while format()'s\n// tokens go through toLocaleString(), which concatenates everything into\n// \"8月\". Reading only the type-tagged part used to drop that suffix, so\n// this locale's vocab never matched what format() actually produced.\n// Only merges *adjacent* literals, not the whole string, since the\n// dayPeriod/weekday formatters below carry an extra hour part that a\n// join-everything approach would wrongly absorb.\nfunction partValue(formatter: Intl.DateTimeFormat, date: Date, type: Intl.DateTimeFormatPartTypes): string {\n const parts = formatter.formatToParts(date);\n const index = parts.findIndex((p) => p.type === type);\n if (index === -1) {\n throw new Error(`temporal-fmt: locale produced no \"${type}\" part while building match vocabulary.`);\n }\n let value = parts[index]!.value;\n const prev = parts[index - 1];\n const next = parts[index + 1];\n // skip whitespace literals (the separator before \"AM\") — only a\n // no-space suffix like ja-JP's \"月\" should get folded in\n if (prev?.type === 'literal' && !/\\s/.test(prev.value)) value = prev.value + value;\n if (next?.type === 'literal' && !/\\s/.test(next.value)) value = value + next.value;\n return value;\n}\n\n// Two entries rendering identically means parse()'s reverse lookup\n// (indexOf) can never tell them apart. Weekday collisions already surface\n// via parse()'s dayOfWeek cross-check, but with a confusing same-string\n// error; months have no equivalent cross-check, so a collision there would\n// otherwise resolve silently to the wrong month. Catching both here, once\n// at build time, gives one clear error instead.\nfunction assertNoCollision(names: string[], label: string, locale: string): void {\n const seen = new Map<string, number>();\n for (let i = 0; i < names.length; i++) {\n const prior = seen.get(names[i]!);\n if (prior !== undefined) {\n throw new Error(\n `temporal-fmt: locale \"${locale}\" renders ${label} index ${prior} and ${i} identically ` +\n `(\"${names[i]}\"). parse() can't reliably tell these apart for this locale/token, so this ` +\n `combination isn't supported.`\n );\n }\n seen.set(names[i]!, i);\n }\n}\n\n// Exposed for tokens.ts: when a custom vocab is registered for this\n// locale, format()'s locale-aware tokens (MMMM/MMM/EEEE/EEE/a) read\n// straight from the registered array instead of going through\n// Intl.DateTimeFormat. Without this override, format() would silently\n// keep producing Intl's strings while parse() matched against the\n// registered vocab — the two would round-trip-fail.\nexport function getCustomVocab(locale: string): LocaleVocab | undefined {\n const cacheKey = canonicalCacheKey(locale);\n return customVocabs.get(cacheKey);\n}\n\nexport function getLocaleVocab(locale: string): LocaleVocab {\n const cacheKey = canonicalCacheKey(locale);\n // Registered vocabs take precedence over the Intl-derived one — this\n // is the override mechanism registerLocaleVocab() promises. Checking\n // here, before the Intl cache lookup, means a registration that\n // happens *after* the first Intl-derived vocab was built still takes\n // effect on the next call.\n const custom = customVocabs.get(cacheKey);\n if (custom) {\n return custom;\n }\n const cached = vocabCache.get(cacheKey);\n if (cached) {\n return cached;\n }\n\n const monthLongFmt = new Intl.DateTimeFormat(locale, { month: 'long', timeZone: 'UTC' });\n const monthShortFmt = new Intl.DateTimeFormat(locale, { month: 'short', timeZone: 'UTC' });\n const monthLong: string[] = [];\n const monthShort: string[] = [];\n for (let m = 0; m < 12; m++) {\n const date = new Date(Date.UTC(2020, m, 1));\n monthLong.push(partValue(monthLongFmt, date, 'month'));\n monthShort.push(partValue(monthShortFmt, date, 'month'));\n }\n assertNoCollision(monthLong, 'MMMM month', locale);\n assertNoCollision(monthShort, 'MMM month', locale);\n\n const weekdayLongFmt = new Intl.DateTimeFormat(locale, { weekday: 'long', timeZone: 'UTC' });\n const weekdayShortFmt = new Intl.DateTimeFormat(locale, { weekday: 'short', timeZone: 'UTC' });\n const weekdayLong: string[] = [];\n const weekdayShort: string[] = [];\n // 2024-01-01 is a Monday (UTC) — walk 7 days from there for weekday names\n for (let d = 0; d < 7; d++) {\n const date = new Date(Date.UTC(2024, 0, 1 + d));\n weekdayLong.push(partValue(weekdayLongFmt, date, 'weekday'));\n weekdayShort.push(partValue(weekdayShortFmt, date, 'weekday'));\n }\n // redundant with parse()'s dayOfWeek cross-check, but gives a clearer error\n assertNoCollision(weekdayLong, 'EEEE weekday', locale);\n assertNoCollision(weekdayShort, 'EEE weekday', locale);\n\n const dayPeriodFmt = new Intl.DateTimeFormat(locale, { hour: 'numeric', hour12: true, timeZone: 'UTC' });\n const am = partValue(dayPeriodFmt, new Date(Date.UTC(2020, 0, 1, 1)), 'dayPeriod');\n const pm = partValue(dayPeriodFmt, new Date(Date.UTC(2020, 0, 1, 13)), 'dayPeriod');\n const dayPeriod = [...new Set([am, pm])];\n\n const vocab: LocaleVocab = { monthLong, monthShort, weekdayLong, weekdayShort, dayPeriod };\n if (vocabCache.size >= MAX_VOCAB_CACHE_SIZE) {\n const oldestKey = vocabCache.keys().next().value;\n if (oldestKey !== undefined) vocabCache.delete(oldestKey);\n }\n vocabCache.set(cacheKey, vocab);\n return vocab;\n}","// ISO 8601 week numbering: a week runs Monday–Sunday, and week 1 of a year\n// is the week containing the year's first Thursday (equivalently, the week\n// containing January 4). This means late-December dates can fall in week 1\n// of the *next* year, and early-January dates can fall in week 52 or 53 of\n// the *previous* year. The \"ISO week-numbering year\" (what `RRRR` formats)\n// is that adjacent year, not the calendar year.\n//\n// Computed here from the date's own year/month/day plus its ISO dayOfWeek\n// (1=Mon..7=Sun, matching Temporal's numbering) using plain Gregorian\n// arithmetic — no Temporal factory needed. format() only has the fields\n// the caller already put on the object, and requiring a Temporal\n// implementation just for ISO week would be a regression for callers who\n// use format() without setTemporal() on a non-26 Node.\n\nconst DAYS_IN_MONTH = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];\nconst CUMULATIVE_DAYS_BY_MONTH = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334];\n\nexport function isGregorianLeapYear(year: number): boolean {\n // Gregorian rule: divisible by 4, except centuries which must also be\n // divisible by 400. Temporal's iso8601 calendar is proleptic Gregorian\n // (no Julian cutover), so this applies for every year, including BCE.\n return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;\n}\n\nfunction daysInYear(year: number): 365 | 366 {\n return isGregorianLeapYear(year) ? 366 : 365;\n}\n\nexport function dayOfYear(year: number, month: number, day: number): number {\n let doy = CUMULATIVE_DAYS_BY_MONTH[month - 1]! + day;\n if (month > 2 && isGregorianLeapYear(year)) doy += 1;\n return doy;\n}\n\n// Jan 1, 2000 was a Saturday — ISO dayOfWeek 6. Anchoring day-of-week\n// computations to a known reference date is simpler and cheaper than\n// pulling in Zeller's congruence, and the reference never changes.\nconst REFERENCE_YEAR = 2000;\nconst REFERENCE_JAN1_DAY_OF_WEEK = 6;\n\nfunction dayOfWeekOfJan1(year: number): number {\n // Sum full-year deltas from the 2000 anchor rather than recomputing from\n // scratch each call — the per-call work is a single mod this way, and\n // the loop rarely runs far (a typical caller passes a current-era year).\n let offset = 0;\n if (year >= REFERENCE_YEAR) {\n for (let y = REFERENCE_YEAR; y < year; y++) offset += daysInYear(y);\n } else {\n for (let y = year; y < REFERENCE_YEAR; y++) offset -= daysInYear(y);\n }\n // Convert \"days since Jan 1, 2000\" into an ISO day-of-week (1=Mon..7=Sun).\n // Jan 1, 2000 was ISO 6 (Sat), so zero-indexed dow = (6-1 + offset) mod 7.\n const zeroIndexed = (((6 - 1 + offset) % 7) + 7) % 7;\n return zeroIndexed + 1;\n}\n\nexport interface IsoWeekDate {\n isoYear: number;\n week: number; // 1..53\n}\n\nexport function isoWeekYearAndWeek(year: number, month: number, day: number, dayOfWeek: number): IsoWeekDate {\n // Step 1: find the Thursday of the current ISO week. The ISO week-numbering\n // year is whichever calendar year that Thursday falls in. Computing it via\n // day-of-year offsets (rather than constructing a Temporal.PlainDate and\n // adding days) keeps this function pure-numeric.\n const doy = dayOfYear(year, month, day);\n const thursdayDoyRelative = doy + (4 - dayOfWeek); // may be <1 or >daysInYear\n\n let isoYear: number;\n let thursdayDoy: number;\n if (thursdayDoyRelative < 1) {\n isoYear = year - 1;\n thursdayDoy = thursdayDoyRelative + daysInYear(isoYear);\n } else if (thursdayDoyRelative > daysInYear(year)) {\n isoYear = year + 1;\n thursdayDoy = thursdayDoyRelative - daysInYear(year);\n } else {\n isoYear = year;\n thursdayDoy = thursdayDoyRelative;\n }\n\n // Step 2: locate the first Thursday of isoYear — its week is week 1. The\n // first Thursday's day-of-year depends on what weekday Jan 1 of isoYear is.\n const jan1Dow = dayOfWeekOfJan1(isoYear);\n const firstThursdayDoy = 1 + ((4 - jan1Dow + 7) % 7); // 1..7\n\n // Step 3: count full weeks between the two Thursdays.\n const week = 1 + Math.floor((thursdayDoy - firstThursdayDoy) / 7);\n return { isoYear, week };\n}\n","import { getTemporal, subscribeToTemporalChanges } from './temporalProvider.js';\nimport { canonicalCacheKey, getCustomVocab } from './localeVocab.js';\nimport { isoWeekYearAndWeek } from './isoWeek.js';\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// 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 timeZoneId?: 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 formatter = new Intl.DateTimeFormat(locale, options);\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 nativeSupport = true;\n } catch {\n // native Temporal absent, or present but not recognized by Intl — fall back\n }\n }\n return nativeSupport;\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 // 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 if (!intlSupportsNativeTemporal()) {\n return temporal.toLocaleString!(locale, formatterOptions);\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\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 if (!part) {\n throw new Error(`temporal-fmt: locale \"${locale}\" produced no \"dayPeriod\" part for token \"a\".`);\n }\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\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 ['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 ['SSS', (t) => pad(t.millisecond!, 3), '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\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 const { week } = isoWeekYearAndWeek(t.year!, t.month!, t.day!, t.dayOfWeek!);\n return pad(week, 2);\n }, 'dayOfWeek'],\n ['RRRR', (t) => {\n const { isoYear } = isoWeekYearAndWeek(t.year!, t.month!, t.day!, t.dayOfWeek!);\n return pad(isoYear, 4);\n }, 'dayOfWeek'],\n];","import { TOKENS } from './tokens.js';\n\nexport type Piece =\n | { kind: 'token'; value: string }\n | { kind: 'literal'; value: string };\n\n// longest-first so the greedy scan never matches \"M\" when \"MMMM\" was there\nconst SORTED_TOKEN_STRINGS = TOKENS.map(([tok]) => tok).sort((a, b) => b.length - a.length);\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 const pieces: Piece[] = [];\n let i = 0;\n\n while (i < format.length) {\n const ch = format[i];\n\n if (ch === \"'\") {\n // check doubled-quote first or \"''best''\" parses wrong\n if (format[i + 1] === \"'\") {\n appendLiteral(pieces, \"'\");\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 Error(`temporal-fmt: unterminated quote in format string \"${format}\"`);\n }\n\n appendLiteral(pieces, literal);\n i = j;\n continue;\n }\n\n const match = SORTED_TOKEN_STRINGS.find((tok) => format.startsWith(tok, i));\n if (match) {\n pieces.push({ kind: 'token', value: match });\n i += match.length;\n continue;\n }\n\n // not a token or quote — pass through as-is\n appendLiteral(pieces, ch);\n i += 1;\n }\n\n return pieces;\n}\n\n// merges into the previous piece if it's also a literal, so \"---\" is one\n// piece instead of three\nfunction appendLiteral(pieces: Piece[], value: string): void {\n const last = pieces[pieces.length - 1];\n if (last && last.kind === 'literal') {\n last.value += value;\n } else {\n pieces.push({ kind: 'literal', value });\n }\n}\n","import { TOKENS, DEFAULT_LOCALE, type TemporalLike, type FormatOptions } from './tokens.js';\nimport { tokenize } from './tokenize.js';\nimport { MAX_FORMAT_LENGTH } from './constants.js';\n\nconst HANDLER_BY_TOKEN = new Map(TOKENS.map(([tok, fn, field]) => [tok, { fn, field }]));\n\n/**\n * Format a Temporal.PlainDate, PlainTime, PlainDateTime, or ZonedDateTime\n * using a date-fns-style token string.\n *\n * @example\n * format(Temporal.Now.plainDateISO(), 'yyyy-MM-dd') // \"2026-08-04\"\n * format(zdt, \"MMM d, yyyy 'at' h:mm a\") // \"Aug 4, 2026 at 3:45 PM\"\n * format(zdt, 'MMMM d, yyyy', { locale: 'fr-FR' }) // \"août 4, 2026\"\n *\n * Throws on a token the input type doesn't support (e.g. 'HH' on a PlainDate).\n */\nexport function format(temporal: TemporalLike, formatStr: string, options: FormatOptions = {}): string {\n if (formatStr.length > MAX_FORMAT_LENGTH) {\n throw new Error(\n `temporal-fmt: format string exceeds maximum length of ${MAX_FORMAT_LENGTH} characters ` +\n `(got ${formatStr.length}).`\n );\n }\n\n const locale = options.locale ?? DEFAULT_LOCALE;\n const pieces = tokenize(formatStr);\n let result = '';\n\n for (const piece of pieces) {\n if (piece.kind === 'literal') {\n result += piece.value;\n continue;\n }\n\n const handler = HANDLER_BY_TOKEN.get(piece.value);\n if (!handler) {\n // shouldn't happen — tokenize() only emits tokens from TOKENS\n throw new Error(`temporal-fmt: unknown token \"${piece.value}\"`);\n }\n\n if (temporal[handler.field] === undefined) {\n throw new Error(\n `temporal-fmt: token \"${piece.value}\" requires \"${handler.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 result += handler.fn(temporal, locale);\n }\n\n return result;\n}\n","import { getLocaleVocab } from './localeVocab.js';\n\nfunction escapeRegExp(literal: string): string {\n return literal.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n\nfunction alternation(values: string[]): string {\n return `(?:${values.map(escapeRegExp).join('|')})`;\n}\n\n// Every real IANA zone id is letters/digits/'_'/'+'/'-' segments joined by\n// '/' (e.g. \"America/Argentina/Buenos_Aires\", \"Etc/GMT+12\"); UTC and\n// fixed-offset strings are the only other shapes zzz accepts. Matching that\n// *shape* here — instead of alternating all ~400 zone names inline — keeps\n// the compiled regex small regardless of how many zzz tokens appear in a\n// format string. The captured text still gets checked against the real\n// zone set in isValidTimeZone() after the overall pattern matches, so this\n// is strictly a matching-cost change, not a validation-strictness change:\n// a bogus zone id fails \"no valid pattern matches\" exactly like it did when\n// the zone list was inlined (see isValidTimeZone's caller in parse.ts).\nconst TIME_ZONE_SHAPE = '(?:UTC|[+-]\\\\d{2}:\\\\d{2}(?::\\\\d{2}(?:\\\\.\\\\d{1,9})?)?|[A-Za-z_]+(?:[+-]\\\\d{1,2})?(?:\\\\/[A-Za-z0-9_+-]+)*)';\n\nfunction getTimeZoneFragment(): string {\n return TIME_ZONE_SHAPE;\n}\n\nlet validZoneSet: Set<string> | undefined;\n\n// Real zone ids plus the couple of aliases zzz has always accepted even\n// though Intl.supportedValuesOf('timeZone') doesn't list them (UTC isn't\n// itself an IANA zone name, it's the identity offset).\nfunction getValidZoneSet(): Set<string> {\n if (!validZoneSet) {\n validZoneSet = new Set(Intl.supportedValuesOf('timeZone'));\n validZoneSet.add('UTC');\n }\n return validZoneSet;\n}\n\nconst FIXED_OFFSET_RE = /^[+-]\\d{2}:\\d{2}(?::\\d{2}(?:\\.\\d{1,9})?)?$/;\n\n// Called post-match on whatever the bounded TIME_ZONE_SHAPE captured, since\n// that shape is deliberately looser than \"a real zone id\" (it has to be, to\n// stay a fixed-size regex fragment — see the comment above). A fixed offset\n// is valid by construction; anything else has to be a real IANA name.\nexport function isValidTimeZone(raw: string): boolean {\n return FIXED_OFFSET_RE.test(raw) || getValidZoneSet().has(raw);\n}\n\n// mirrors the ranges pad() in tokens.ts actually produces — keep in sync\n// if those ever change\n//\n// Unpadded alternatives (M, H, h, m, s) list the longer branch first\n// (e.g. '1[0-2]|[1-9]', not '[1-9]|1[0-2]'). This matters only when two\n// unpadded tokens are glued with no separator: with short-first ordering,\n// a regex engine takes the first successful overall match, and won't\n// backtrack into a token's second alternative unless its first choice\n// makes the *rest* of the pattern fail outright. If the short reading also\n// happens to leave a valid match for the next token, the engine stops\n// there — silently, deterministically, and with no relation to which\n// reading a human intended. E.g. \"Md\" against \"121\": short-first order\n// resolves it as month=1/day=21 (M grabs '1', d gets '21', which is a\n// valid day) instead of month=12/day=1. Longer-first ordering fixes this\n// by making the greedy match try to consume as many digits as possible\n// before ever handing digits to the next token, which is the reading\n// that matches how format() itself produces glued output in the first\n// place (format() always emits the token's natural width, so decoding\n// should prefer the same). Found via the token×token combinatorial glue\n// matrix in combinatorial.test.js — see that file for the full case list.\nconst NUMERIC_FRAGMENTS: Record<string, string> = {\n yy: '\\\\d{2}',\n MM: '(?:0[1-9]|1[0-2])',\n M: '(?:1[0-2]|[1-9])',\n dd: '(?:0[1-9]|[12]\\\\d|3[01])',\n d: '(?:[12]\\\\d|3[01]|[1-9])',\n HH: '(?:[01]\\\\d|2[0-3])',\n H: '(?:1\\\\d|2[0-3]|[0-9])',\n hh: '(?:0[1-9]|1[0-2])',\n h: '(?:1[0-2]|[1-9])',\n mm: '(?:[0-5]\\\\d)',\n m: '(?:[1-5]\\\\d|[0-9])',\n ss: '(?:[0-5]\\\\d)',\n s: '(?:[1-5]\\\\d|[0-9])',\n SSS: '\\\\d{3}',\n // Q is always a single digit 1-4 (no padding variant, so no width ambiguity\n // with adjacent digit tokens the way M/d/H/m/s have).\n Q: '[1-4]',\n};\n\n// QQQ matches its own formatted output: the literal \"Q\" prefix plus a single\n// digit 1-4. Kept as a static fragment rather than routed through vocab,\n// since the \"Q\" prefix is part of the token's own contract, not locale-vocab\n// data that could ever differ.\nconst QQQ_FRAGMENT = 'Q[1-4]';\n\n// Format-only tokens — they have no parse counterpart. The tokenizer still\n// recognizes them (so format() can use them), but parse()'s regex builder\n// rejects them with a clear error rather than silently dropping the token\n// or falling through to the generic \"unknown token\" message.\nconst FORMAT_ONLY_TOKENS = new Set(['do', 'ww', 'RRRR']);\n\n// pad()'s year formatter (tokens.ts) never truncates: it preserves the sign\n// for BCE years and doesn't cap width past 9999, so a formatted \"yyyy\" can\n// be longer than 4 digits or start with '-'. YYYY_EXTENDED accepts that;\n// YYYY_EXACT is the plain 4-unsigned-digit case. Which one a given \"yyyy\"\n// occurrence gets depends on what follows it — see buildCapturingPattern in\n// parsePattern.ts. Two separate fragments instead of one `-?\\d{4,}` because\n// an open-ended-width year directly followed by another digit token (e.g.\n// \"yyyyMM\") lets the year's own greediness silently eat digits meant for\n// the next token — same class of bug as UNPADDED_NUMERIC_TOKENS below, but\n// unbounded-width, so it can't reuse enumerateValidSplits' fixed-range\n// splitting. Restricting to exactly 4 digits whenever something could\n// follow closes that off entirely, at the cost of \"yyyyMM\" not being able\n// to represent a 5-digit year — an already-rare case doubly rare in\n// combination with a glued adjacent token.\nconst YYYY_EXACT = '-?\\\\d{4}';\nconst YYYY_EXTENDED = '-?\\\\d{4,}';\n\n// True for any token whose matched text can start with a digit — i.e.\n// every token here except the locale-named ones (MMMM/MMM/EEEE/EEE/a) and\n// zzz (which can start with a digit only via a fixed offset like \"+09:00\",\n// already handled by requiring a leading sign there). Used to decide\n// whether a \"yyyy\" immediately before this token needs the exact-4-digit\n// fragment instead of the open-ended one.\nconst DIGIT_LEADING_TOKENS = new Set([\n 'yyyy', 'yy', 'MM', 'M', 'dd', 'd', 'HH', 'H', 'hh', 'h', 'mm', 'm', 'ss', 's', 'SSS',\n]);\n\nexport function tokenFragment(token: string, locale: string, nextToken?: string): string {\n if (token === 'yyyy') {\n return nextToken !== undefined && DIGIT_LEADING_TOKENS.has(nextToken) ? YYYY_EXACT : YYYY_EXTENDED;\n }\n\n const numeric = NUMERIC_FRAGMENTS[token];\n if (numeric) {\n return numeric;\n }\n\n if (token === 'QQQ') {\n return QQQ_FRAGMENT;\n }\n\n if (FORMAT_ONLY_TOKENS.has(token)) {\n throw new Error(\n `temporal-fmt: token \"${token}\" is format-only — it can't be parsed back into a value. ` +\n `Use a different token in the parse format string (e.g. \"d\" for \"do\", \"MM\" for \"ww\").`\n );\n }\n\n const vocab = getLocaleVocab(locale);\n switch (token) {\n case 'MMMM': return alternation(vocab.monthLong);\n case 'MMM': return alternation(vocab.monthShort);\n case 'EEEE': return alternation(vocab.weekdayLong);\n case 'EEE': return alternation(vocab.weekdayShort);\n case 'a': return alternation(vocab.dayPeriod);\n case 'zzz': return getTimeZoneFragment();\n default:\n throw new Error(`temporal-fmt: unknown token \"${token}\"`);\n }\n}\n\n// Tokens whose fragment is variable-width (1-2 digits, no leading zero).\n// Two or more of these glued with no literal separator between them can\n// have more than one digit-split that's independently valid against every\n// fragment in the run — see the big comment on NUMERIC_FRAGMENTS above.\n// Reordering alternation branches picks a winner for *some* of these\n// cases, but can't make both directions of a pair (e.g. \"Md\" and \"dM\")\n// agree, because the ambiguity is in the input string itself, not in how\n// any one fragment is written. exported so parsePattern.ts can find runs\n// of these that need split-counting at match time instead of a single\n// fixed regex.\nexport const UNPADDED_NUMERIC_TOKENS = new Set(['M', 'd', 'H', 'h', 'm', 's']);\n\n// Every accept width for a given unpadded numeric token, as plain min/max\n// value + digit-length pairs — used to enumerate candidate splits of a\n// digit run at match time. Mirrors NUMERIC_FRAGMENTS's semantics exactly\n// (same accepted values), just as data instead of regex source, since\n// enumerating splits against a compiled regex per-candidate would be\n// slower and harder to reason about than checking numeric ranges directly.\nexport const UNPADDED_NUMERIC_RANGES: Record<string, Array<{ digits: 1 | 2; min: number; max: number }>> = {\n M: [{ digits: 1, min: 1, max: 9 }, { digits: 2, min: 10, max: 12 }],\n d: [{ digits: 1, min: 1, max: 9 }, { digits: 2, min: 10, max: 31 }],\n H: [{ digits: 1, min: 0, max: 9 }, { digits: 2, min: 10, max: 23 }],\n h: [{ digits: 1, min: 1, max: 9 }, { digits: 2, min: 10, max: 12 }],\n m: [{ digits: 1, min: 0, max: 9 }, { digits: 2, min: 10, max: 59 }],\n s: [{ digits: 1, min: 0, max: 9 }, { digits: 2, min: 10, max: 59 }],\n};\n\n/**\n * Given the literal digit string a run of N adjacent unpadded-numeric\n * tokens matched as a whole (e.g. \"112\" for a 2-token run), enumerates\n * every way to split it into N pieces (one per token, each piece 1-2\n * digits per that token's own width rule) and returns every split where\n * every piece is independently valid for its token. Length 0 means the\n * run's regex match shouldn't have been possible in the first place\n * (shouldn't happen — the caller only invokes this after the whole\n * pattern already matched, meaning at least one split exists: the one the\n * regex actually took). Length 1 means the reading is unambiguous.\n * Length 2+ means true ambiguity — the caller should throw rather than\n * pick one.\n *\n * Recursive over token count rather than hardcoded to 2, so a 3+ token\n * unseparated run (e.g. \"Hms\") is covered by the same logic without a\n * special case — those are rarer in practice but not impossible, and a\n * partial fix that only covered pairs would leave the identical bug for\n * anyone writing a 3-token glued run.\n */\nexport function enumerateValidSplits(digits: string, tokens: string[]): number[][] {\n const memo = new Map<string, number[][]>();\n\n function solve(tokenIndex: number, offset: number): number[][] {\n const key = `${tokenIndex}:${offset}`;\n const cached = memo.get(key);\n if (cached) {\n return cached;\n }\n\n if (tokenIndex === tokens.length) {\n const result = offset === digits.length ? [[]] : [];\n memo.set(key, result);\n return result;\n }\n\n const token = tokens[tokenIndex];\n const ranges = UNPADDED_NUMERIC_RANGES[token!];\n if (!ranges) {\n throw new Error(`temporal-fmt: internal error — \"${token}\" is not an unpadded numeric token`);\n }\n\n const results: number[][] = [];\n for (const { digits: width, min, max } of ranges) {\n if (offset + width > digits.length) continue;\n const piece = digits.slice(offset, offset + width);\n if (width === 2 && piece[0] === '0') continue;\n const value = Number(piece);\n if (value < min || value > max) continue;\n\n for (const restSplit of solve(tokenIndex + 1, offset + width)) {\n results.push([value, ...restSplit]);\n if (results.length === 2) break;\n }\n if (results.length === 2) break;\n }\n\n memo.set(key, results);\n return results;\n }\n\n return solve(0, 0);\n}","import type { Piece } from './tokenize.js';\nimport { tokenFragment, UNPADDED_NUMERIC_TOKENS } from './pattern.js';\n\nfunction escapeRegExp(literal: string): string {\n return literal.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n\nexport interface CapturingPattern {\n regex: RegExp;\n groups: Array<{ name: string; token: string }>; // token pieces, in order\n // Runs of 2+ adjacent unpadded-numeric tokens with no literal separator\n // between them (e.g. \"Md\", \"dM\", \"Hm\") — these need a split-ambiguity\n // check at match time, since a single fixed regex can't distinguish\n // \"the only valid split\" from \"one of several valid splits taken\n // arbitrarily\". Empty for the overwhelmingly common case (no such runs).\n ambiguousRuns: Array<{ groupNames: string[]; tokens: string[] }>;\n}\n\n/**\n * Same walk as buildPatternSource() in pattern.ts, but each token piece\n * gets its own named capture group (positionally named so the same token,\n * e.g. \"yyyy\", could in theory appear twice) so a caller can pull the\n * matched substring for each token back out after a successful match.\n */\nexport function buildCapturingPattern(pieces: Piece[], locale: string): CapturingPattern {\n const groups: Array<{ name: string; token: string }> = [];\n const ambiguousRuns: Array<{ groupNames: string[]; tokens: string[] }> = [];\n let source = '';\n let i = 0;\n\n // tracks the group names/tokens of the current run of adjacent unpadded\n // numeric token pieces (no literal piece has broken it yet)\n let currentRun: { groupNames: string[]; tokens: string[] } = { groupNames: [], tokens: [] };\n const flushRun = () => {\n if (currentRun.tokens.length >= 2) {\n ambiguousRuns.push(currentRun);\n }\n currentRun = { groupNames: [], tokens: [] };\n };\n\n for (const [idx, piece] of pieces.entries()) {\n if (piece.kind === 'literal') {\n source += escapeRegExp(piece.value);\n flushRun(); // a literal (even a single character) breaks adjacency\n continue;\n }\n const name = `g${i++}`;\n groups.push({ name, token: piece.value });\n const nextPiece = pieces[idx + 1];\n const nextToken = nextPiece?.kind === 'token' ? nextPiece.value : undefined;\n source += `(?<${name}>${tokenFragment(piece.value, locale, nextToken)})`;\n\n if (UNPADDED_NUMERIC_TOKENS.has(piece.value)) {\n currentRun.groupNames.push(name);\n currentRun.tokens.push(piece.value);\n } else {\n flushRun(); // a non-unpadded-numeric token (padded, locale-aware, zzz) also breaks adjacency\n }\n }\n flushRun();\n\n return { regex: new RegExp(`^(?:${source})$`, 'u'), groups, ambiguousRuns };\n}","import { DEFAULT_LOCALE, type FormatOptions } from './tokens.js';\nimport { tokenize } from './tokenize.js';\nimport { buildCapturingPattern, type CapturingPattern } from './parsePattern.js';\nimport { enumerateValidSplits, isValidTimeZone } from './pattern.js';\nimport { getLocaleVocab, canonicalCacheKey } from './localeVocab.js';\nimport { getTemporal } from './temporalProvider.js';\nimport { MAX_FORMAT_LENGTH, MAX_INPUT_LENGTH } from './constants.js';\n\n// format strings are short hand-written literals reused across many calls —\n// cache the compiled capturing pattern per (formatStr, locale) pair instead\n// of rebuilding it every call.\nconst patternCache = new Map<string, CapturingPattern>();\nconst MAX_CACHE_SIZE = 500;\n\nfunction getPattern(formatStr: string, locale: string): CapturingPattern {\n const key = JSON.stringify([canonicalCacheKey(locale), formatStr]);\n let pattern = patternCache.get(key);\n if (pattern) {\n return pattern;\n }\n if (patternCache.size >= MAX_CACHE_SIZE) {\n const oldestKey = patternCache.keys().next().value;\n if (oldestKey !== undefined) patternCache.delete(oldestKey);\n }\n pattern = buildCapturingPattern(tokenize(formatStr), locale);\n patternCache.set(key, pattern);\n return pattern;\n}\n\n// Requires an explicit `-u-ca-` extension (e.g. 'en-u-ca-hebrew') to apply\n// a non-Gregorian calendar, per parse()'s own docstring. 'gregory' counts\n// as \"no calendar\" so the default locale keeps constructing plain ISO 8601.\n//\n// Used to key off resolvedOptions().calendar instead — a locale's\n// *default* calendar, whether the caller asked for one or not. That broke\n// th-TH silently: its default is 'buddhist', so plain Gregorian-looking\n// digits parsed 543 years off, while format() has no matching calendar\n// step and just prints the object's own ISO fields either way.\nconst calendarCache = new Map<string, string | undefined>();\nconst MAX_CALENDAR_CACHE_SIZE = 500;\n\nfunction resolveCalendar(locale: string): string | undefined {\n // computed up front (not just at cache-miss time) so the cache key is\n // the canonical form too — otherwise 'en-US' and 'en-us' would each get\n // their own entry for what's really the same locale (see\n // canonicalCacheKey's comment in localeVocab.ts for why that matters).\n // Left un-lowercased/un-canonicalized calls into new Intl.Locale() below\n // would still throw on a genuinely malformed tag either way; doing it\n // here just means that throw happens before touching the cache instead\n // of after, which is the more natural place for it.\n const canonicalLocale = new Intl.Locale(locale).toString().toLowerCase();\n if (calendarCache.has(canonicalLocale)) {\n return calendarCache.get(canonicalLocale);\n }\n if (calendarCache.size >= MAX_CALENDAR_CACHE_SIZE) {\n const oldestKey = calendarCache.keys().next().value;\n if (oldestKey !== undefined) calendarCache.delete(oldestKey);\n }\n let calendar: string | undefined;\n const parts = canonicalLocale.split('-');\n const extensionIndex = parts.indexOf('u');\n const calendarKeyIndex = extensionIndex === -1 ? -1 : parts.indexOf('ca', extensionIndex + 1);\n if (calendarKeyIndex !== -1 && calendarKeyIndex + 1 < parts.length) {\n const resolved = new Intl.DateTimeFormat(canonicalLocale).resolvedOptions().calendar;\n calendar = resolved === 'gregory' ? undefined : resolved;\n }\n calendarCache.set(canonicalLocale, calendar);\n return calendar;\n}\n\ninterface Fields {\n year?: number;\n twoDigitYear?: number;\n month?: number;\n day?: number;\n hour?: number;\n hour12?: number;\n dayPeriodRaw?: string;\n isPM?: boolean;\n minute?: number;\n second?: number;\n millisecond?: number;\n timeZoneId?: string;\n weekdayExpected?: number;\n weekdayRaw?: string;\n quarter?: number;\n}\n\nfunction assignField<T>(fields: Fields, key: keyof Fields, value: T): void {\n (fields as Record<string, T | undefined>)[key] = value;\n}\n\nfunction applyGroup(fields: Fields, token: string, raw: string, locale: string, formatStr: string): void {\n const vocab = getLocaleVocab(locale);\n switch (token) {\n case 'yyyy':\n assignField(fields, 'year', Number(raw));\n break;\n case 'yy':\n assignField(fields, 'twoDigitYear', Number(raw));\n break;\n case 'MM': case 'M':\n assignField(fields, 'month', Number(raw));\n break;\n case 'MMMM':\n assignField(fields, 'month', vocab.monthLong.indexOf(raw) + 1);\n break;\n case 'MMM':\n assignField(fields, 'month', vocab.monthShort.indexOf(raw) + 1);\n break;\n case 'dd': case 'd':\n assignField(fields, 'day', Number(raw));\n break;\n case 'EEEE':\n assignField(fields, 'weekdayRaw', raw);\n assignField(fields, 'weekdayExpected', vocab.weekdayLong.indexOf(raw) + 1);\n break;\n case 'EEE':\n assignField(fields, 'weekdayRaw', raw);\n assignField(fields, 'weekdayExpected', vocab.weekdayShort.indexOf(raw) + 1);\n break;\n case 'HH': case 'H':\n assignField(fields, 'hour', Number(raw));\n break;\n case 'hh': case 'h':\n assignField(fields, 'hour12', Number(raw));\n break;\n case 'mm': case 'm':\n assignField(fields, 'minute', Number(raw));\n break;\n case 'ss': case 's':\n assignField(fields, 'second', Number(raw));\n break;\n case 'SSS':\n assignField(fields, 'millisecond', Number(raw));\n break;\n case 'a': {\n const periodIndex = vocab.dayPeriod.indexOf(raw);\n if (periodIndex < 0) throw new Error(`temporal-fmt: unknown day period \"${raw}\" for locale \"${locale}\".`);\n assignField(fields, 'dayPeriodRaw', raw);\n assignField(fields, 'isPM', periodIndex === 1);\n break;\n }\n case 'zzz':\n assignField(fields, 'timeZoneId', raw);\n break;\n case 'Q':\n assignField(fields, 'quarter', Number(raw));\n break;\n case 'QQQ':\n // strips the literal \"Q\" prefix the token itself formats; the suffix\n // digit is the quarter value 1-4\n assignField(fields, 'quarter', Number(raw.slice(1)));\n break;\n }\n}\n\n// The lenient split-selection heuristic for ambiguous glued numeric runs.\n// See README \"Lenient parse mode\" — the strict default throws on these,\n// lenient mode opts into picking one split instead.\nfunction pickLenientSplit(splits: number[][], tokens: string[]): number[] {\n // Prefer the split where a \"d\" (day) token, if any, has a value of 12 or\n // less. Rationale: when a person writes a glued run like \"121\" for an\n // Md format string, the reading \"Dec 1\" (M=12, d=1) is what they\n // typically meant — if they meant \"Jan 21\" they would more often have\n // written it as \"1/21\" or \"01/21\" with a separator or padding, since the\n // 2-digit day is the more naturally-cohesive unit to keep glued. This\n // isn't a guarantee, which is exactly why lenient mode is opt-in — but\n // it's a reasonable default when the caller has asked us to guess.\n const dayIndex = tokens.indexOf('d');\n if (dayIndex !== -1) {\n const smallDaySplits = splits.filter((s) => s[dayIndex]! <= 12);\n if (smallDaySplits.length > 0) {\n return smallDaySplits[0]!;\n }\n }\n // Fallback to the first valid split when the day heuristic doesn't\n // narrow it down — deterministic, and \"first\" here means \"whichever\n // enumerateValidSplits returned first\", which is a depth-first\n // leftmost-shortest walk over the candidate splits.\n return splits[0]!;\n}\n\n// emulates strptime (POSIX) for 2-digit years so the result doesn't depend\n// on the current clock: 00-68 -> 2000-2068, 69-99 -> 1900-1999\n// https://www.man7.org/linux//man-pages/man3/strptime.3p.html\nfunction resolveYear(fields: Fields): number | undefined {\n if (fields.year !== undefined && fields.twoDigitYear !== undefined) {\n throw new Error(\n 'temporal-fmt: format string mixes \"yyyy\" and \"yy\" year representations.'\n );\n }\n if (fields.year !== undefined) return fields.year;\n if (fields.twoDigitYear !== undefined) {\n return fields.twoDigitYear <= 68 ? 2000 + fields.twoDigitYear : 1900 + fields.twoDigitYear;\n }\n return undefined;\n}\n\nfunction resolveHour(fields: Fields, formatStr: string, locale: string): number | undefined {\n if (fields.hour !== undefined && fields.hour12 !== undefined) {\n throw new Error(\n `temporal-fmt: format string \"${formatStr}\" mixes a 24-hour token (\"HH\"/\"H\") with a ` +\n `12-hour token (\"hh\"/\"h\").`\n );\n }\n if (fields.hour !== undefined) {\n if (fields.dayPeriodRaw !== undefined) {\n const vocab = getLocaleVocab(locale);\n const expected = fields.hour < 12 ? vocab.dayPeriod[0] : vocab.dayPeriod[1];\n if (fields.dayPeriodRaw !== expected) {\n throw new Error(\n `temporal-fmt: format string \"${formatStr}\" contains a day period that contradicts the 24-hour value.`\n );\n }\n }\n return fields.hour;\n }\n if (fields.hour12 !== undefined) {\n if (fields.isPM === undefined) {\n throw new Error(\n `temporal-fmt: format string \"${formatStr}\" uses a 12-hour token (\"hh\"/\"h\") without an \"a\" token, ` +\n `so parse() can't tell AM from PM.`\n );\n }\n return (fields.hour12 % 12) + (fields.isPM ? 12 : 0);\n }\n return undefined;\n}\n\n/**\n * Parses `input` against `formatStr` and builds the real Temporal value it\n * describes: a `Temporal.PlainDate`, `PlainTime`, `PlainDateTime`, or\n * `ZonedDateTime` depending on which tokens are present.\n *\n * Returns `unknown` — this package has no ambient `Temporal` types to return\n * a real one against.\n *\n * `options.locale` picks the calendar the result is built in. Pass a locale\n * tag with a `-u-ca-` extension (e.g. `'en-u-ca-hebrew'`) to parse into a\n * non-Gregorian calendar.\n *\n * @throws if `input` doesn't match `formatStr`'s shape at all\n * @throws if it matches the shape but describes an impossible date (e.g. Feb\n * 30) or self-contradictory data (e.g. a weekday name that doesn't match the\n * actual date)\n *\n * @example\n * parse('yyyy-MM-dd HH:mm', '2026-08-04 15:45') // Temporal.PlainDateTime\n * parse('yyyy-MM', '2026-08-04T15:45:30') // throws — shape doesn't match\n * parse('yyyy-MM-dd', '2026-02-30') // throws — not a real date\n */\nexport function parse(formatStr: string, input: string, options: FormatOptions = {}): unknown | undefined {\n if (formatStr.length > MAX_FORMAT_LENGTH) {\n throw new Error(\n `temporal-fmt: format string exceeds maximum length of ${MAX_FORMAT_LENGTH} characters ` +\n `(got ${formatStr.length}).`\n );\n }\n\n if (input.length > MAX_INPUT_LENGTH) {\n throw new Error(\n `temporal-fmt: input exceeds maximum length of ${MAX_INPUT_LENGTH} characters (got ${input.length}).`\n );\n }\n\n const locale = options.locale ?? DEFAULT_LOCALE;\n const calendar = resolveCalendar(locale);\n const pattern = getPattern(formatStr, locale);\n const match = pattern.regex.exec(input);\n if (!match) {\n throw new Error(`temporal-fmt: no valid pattern matches the format string and input shape`);\n }\n\n if (pattern.groups.length === 0) {\n throw new Error(`temporal-fmt: format string \"${formatStr}\" has no tokens — nothing to parse into a value.`);\n }\n\n // The regex's zzz fragment only matches a bounded zone-id *shape* (see\n // TIME_ZONE_SHAPE in pattern.ts) rather than alternating every real IANA\n // name inline, so a shape match isn't proof of a real zone yet — check\n // each captured zzz group against the actual zone list here. Kept as the\n // same \"no valid pattern matches\" error the inline-alternation version\n // used to throw, since from the caller's perspective this is still the\n // regex rejecting the input, just checked in two steps instead of one.\n for (const { name, token } of pattern.groups) {\n if (token === 'zzz' && !isValidTimeZone(match.groups![name]!)) {\n throw new Error(`temporal-fmt: no valid pattern matches the format string and input shape`);\n }\n }\n\n // A run of 2+ adjacent unpadded-numeric tokens with no literal separator\n // (e.g. \"Md\", \"dM\", \"Hms\") can have more than one way to split the\n // digits it matched that's independently valid for every token in the\n // run — see the comment on NUMERIC_FRAGMENTS in pattern.ts for the\n // mechanism. The regex above only ever finds one such split (whichever\n // its alternation ordering happens to prefer); silently trusting that\n // one would mean parse() sometimes returns a value indistinguishable\n // from a different, equally valid value the same input could describe.\n // Rather than guess, check every ambiguous run explicitly. Strict mode\n // (the default) throws; lenient mode opts into picking one split via a\n // documented heuristic. The input itself is what's ambiguous, not a\n // fixable property of the pattern.\n // Collect any lenient-mode split overrides before the applyGroup loop,\n // so it can substitute the heuristic-chosen values for tokens in a\n // multiply-split run instead of trusting the regex's arbitrary split.\n const runPicks: Array<{ groupNames: string[]; values: number[] }> = [];\n for (const run of pattern.ambiguousRuns) {\n const runDigits = run.groupNames.map((name) => match.groups![name]!).join('');\n const splits = enumerateValidSplits(runDigits, run.tokens);\n if (splits.length > 1) {\n // Strict default — throw on ambiguity. The whole point of the\n // library's parse() is to refuse to guess when the same input has\n // more than one valid reading. Lenient mode (opt-in via\n // options.lenient) instead picks one split via a documented\n // heuristic — see pickLenientSplit() above and the README section\n // \"Lenient parse mode\" for why this is strictly additive and never\n // the default.\n if (!options.lenient) {\n throw new Error(\n `temporal-fmt: \"${runDigits}\" in format string \"${formatStr}\" is ambiguous — ` +\n `${splits.length} different ways to read tokens \"${run.tokens.join('')}\" (with no separator ` +\n `between them) are all individually valid (e.g. ${JSON.stringify(splits[0])} vs ${JSON.stringify(splits[1])}). ` +\n `parse() won't guess; add a separator between these tokens, or use their padded form ` +\n `(e.g. \"MM\" instead of \"M\") so each one has a fixed width. ` +\n `Pass { lenient: true } to opt into a documented heuristic that picks one.`\n );\n }\n runPicks.push({ groupNames: run.groupNames, values: pickLenientSplit(splits, run.tokens) });\n }\n }\n\n const fields: Fields = {};\n // Map group-name -> heuristic-picked value (as string) for groups that\n // belong to a lenient-resolved ambiguous run. The applyGroup loop reads\n // from here first, falling back to the regex's own captured value when\n // the group isn't part of a lenient-resolved run.\n const lenientValues = new Map<string, string>();\n for (const { groupNames, values } of runPicks) {\n groupNames.forEach((name, i) => lenientValues.set(name, String(values[i])));\n }\n for (const { name, token } of pattern.groups) {\n const raw = lenientValues.get(name) ?? match.groups![name]!;\n applyGroup(fields, token, raw, locale, formatStr);\n }\n\n const year = resolveYear(fields);\n const hour = resolveHour(fields, formatStr, locale);\n const { month, day, minute, second, millisecond, timeZoneId, weekdayExpected, weekdayRaw, quarter } = fields;\n\n const hasAnyDatePart = year !== undefined || month !== undefined || day !== undefined;\n const hasFullDate = year !== undefined && month !== undefined && day !== undefined;\n if (hasAnyDatePart && !hasFullDate) {\n throw new Error(\n `temporal-fmt: format string \"${formatStr}\" has an incomplete date — ` +\n `year, month, and day tokens must all be present together.`\n );\n }\n\n const hasTime = hour !== undefined || minute !== undefined || second !== undefined || millisecond !== undefined;\n\n if (timeZoneId !== undefined && !(hasFullDate && hasTime)) {\n throw new Error(\n `temporal-fmt: format string \"${formatStr}\" has a \"zzz\" token but needs a full date and time ` +\n `to build a ZonedDateTime.`\n );\n }\n\n if (weekdayExpected !== undefined && !hasFullDate) {\n throw new Error(\n `temporal-fmt: format string \"${formatStr}\" has a weekday token (\"EEEE\"/\"EEE\") but needs ` +\n `a full date to validate it against.`\n );\n }\n\n if (!hasFullDate && !hasTime) {\n // shouldn't happen — every token maps to a date, time, zone, or\n // weekday field, and weekday-without-date already threw above\n throw new Error(`temporal-fmt: format string \"${formatStr}\" has no date or time tokens to parse.`);\n }\n\n const temporal = getTemporal();\n const timeFields = { hour: hour ?? 0, minute: minute ?? 0, second: second ?? 0, millisecond: millisecond ?? 0 };\n // omitted entirely for the default calendar (see resolveCalendar) so\n // construction stays plain ISO 8601 unless a caller's locale asks for\n // something else — Temporal calendars don't apply to time-only values.\n const calendarField = calendar ? { calendar } : {};\n\n // overflow: 'reject' — without it Temporal *clamps* out-of-range fields\n // (Feb 30 silently becomes Feb 28) instead of throwing, which would\n // contradict the \"throws on genuinely invalid data\" behavior parse() promises.\n const reject = { overflow: 'reject' as const };\n\n let result: unknown;\n try {\n if (timeZoneId !== undefined) {\n result = temporal.ZonedDateTime.from({ year: year!, month: month!, day: day!, ...timeFields, ...calendarField, timeZone: timeZoneId }, reject);\n } else if (hasFullDate && hasTime) {\n result = temporal.PlainDateTime.from({ year: year!, month: month!, day: day!, ...timeFields, ...calendarField }, reject);\n } else if (hasFullDate) {\n result = temporal.PlainDate.from({ year: year!, month: month!, day: day!, ...calendarField }, reject);\n } else {\n result = temporal.PlainTime.from(timeFields, reject);\n }\n } catch (err) {\n throw new Error(\n `temporal-fmt: \"${input}\" doesn't describe a valid date/time for format \"${formatStr}\": ` +\n `${(err as Error).message}`\n );\n }\n\n if (weekdayExpected !== undefined) {\n const actual = (result as { dayOfWeek: number }).dayOfWeek;\n if (actual !== weekdayExpected) {\n const vocab = getLocaleVocab(locale);\n throw new Error(\n `temporal-fmt: \"${weekdayRaw}\" doesn't match the actual weekday (${vocab.weekdayLong[actual - 1]}) ` +\n `for the parsed date.`\n );\n }\n }\n\n // Q/QQQ is a derived field of the month: 1-3 -> Q1, 4-6 -> Q2, 7-9 -> Q3,\n // 10-12 -> Q4. If a format string carries a quarter token alongside\n // month/date tokens, parse() cross-checks the parsed quarter against the\n // month the same way EEEE cross-checks weekday against date — silently\n // accepting a mismatch would defeat the point of having a quarter token\n // at all, since you'd be telling parse() one thing and the date another.\n if (quarter !== undefined && month !== undefined) {\n const expectedQuarter = Math.ceil(month / 3);\n if (quarter !== expectedQuarter) {\n throw new Error(\n `temporal-fmt: format string \"${formatStr}\" contains a quarter token (Q/QQQ) whose value ` +\n `(Q${quarter}) disagrees with the parsed month's actual quarter — month ${month} is in ` +\n `Q${expectedQuarter}.`\n );\n }\n }\n\n return result;\n}","import { DEFAULT_LOCALE, type FormatOptions } from './tokens.js';\nimport { tokenize } from './tokenize.js';\nimport { MAX_FORMAT_LENGTH } from './constants.js';\n\n// A Temporal.Duration doesn't sit on a calendar — it has no year/month/day\n// position the way a PlainDate does — so the date/time token set in\n// tokens.ts doesn't apply. This is a separate token table for the same\n// reason parseRelative() lives in its own module: a different surface\n// deserves its own grammar, not a bolt-on to the existing one.\n//\n// Token design: each unit has three forms, in increasing verbosity.\n// `y` — numeric value, no unit text (\"2\")\n// `yy` — short unit suffix, plural-aware (\"2y\", or \"1y\" — see below)\n// `yyy` — full unit word, plural-aware (\"2 years\", \"1 year\")\n// Same for months (`o`/`oo`/`ooo` — `o` not `M` because the duration's\n// own field name in Temporal is \"months\" and the long-form output should\n// read \"month\"/\"months\", but the *token* letter is arbitrary; `o` is\n// chosen so it doesn't visually collide with `m` (minutes) or `M`\n// (months in date format) when a reader skims both tables side by side).\n//\n// Plural-awareness: English distinguishes \"1 year\" from \"2 years\". The\n// short form (\"yr\" vs \"yrs\") follows the same rule. When the value is\n// exactly 1, the singular form is used; otherwise the plural. This\n// matches Intl.NumberFormat's behavior for unit formatting and is what\n// most callers expect.\n\ntype UnitKey = 'years' | 'months' | 'weeks' | 'days' | 'hours' | 'minutes' | 'seconds' | 'milliseconds';\n\ninterface UnitForms {\n // singular and plural suffixes for the long form (e.g. \"year\", \"years\")\n longSingular: string;\n longPlural: string;\n // singular and plural suffixes for the short form (e.g. \"yr\", \"yrs\")\n shortSingular: string;\n shortPlural: string;\n // the property on Temporal.Duration to read for this unit\n field: UnitKey;\n}\n\nconst DURATION_UNITS: Record<string, UnitForms> = {\n y: { longSingular: 'year', longPlural: 'years', shortSingular: 'yr', shortPlural: 'yrs', field: 'years' },\n o: { longSingular: 'month', longPlural: 'months', shortSingular: 'mo', shortPlural: 'mos', field: 'months' },\n w: { longSingular: 'week', longPlural: 'weeks', shortSingular: 'wk', shortPlural: 'wks', field: 'weeks' },\n d: { longSingular: 'day', longPlural: 'days', shortSingular: 'd', shortPlural: 'd', field: 'days' },\n h: { longSingular: 'hour', longPlural: 'hours', shortSingular: 'h', shortPlural: 'h', field: 'hours' },\n m: { longSingular: 'minute', longPlural: 'minutes', shortSingular: 'm', shortPlural: 'm', field: 'minutes' },\n s: { longSingular: 'second', longPlural: 'seconds', shortSingular: 's', shortPlural: 's', field: 'seconds' },\n S: { longSingular: 'millisecond', longPlural: 'milliseconds', shortSingular: 'ms', shortPlural: 'ms', field: 'milliseconds' },\n};\n\n// Token strings this module recognizes, longest-first for the greedy\n// tokenizer. tokenize() in tokenize.ts already does longest-first via\n// SORTED_TOKEN_STRINGS, but it operates on the *date/time* TOKENS table —\n// not this one. Rather than teach tokenize() about two tables, this\n// module uses its own equivalent scan over DURATION_UNITS keys.\nconst DURATION_TOKEN_STRINGS = Object.keys(DURATION_UNITS)\n .flatMap((single) => [single + single + single, single + single, single])\n .sort((a, b) => b.length - a.length);\n\ntype Piece =\n | { kind: 'token'; value: string; unit: string; form: 'numeric' | 'short' | 'long' }\n | { kind: 'literal'; value: string };\n\n// Same greedy-match shape as tokenize() in tokenize.ts, but with this\n// module's own token set. Quoted-literal handling is identical so the\n// same documented escaping rules carry over: 'at' is a literal, '' is a\n// literal quote.\nfunction tokenizeDuration(format: string): Piece[] {\n const pieces: Piece[] = [];\n let i = 0;\n while (i < format.length) {\n const ch = format[i];\n if (ch === \"'\") {\n if (format[i + 1] === \"'\") {\n appendLiteral(pieces, \"'\");\n i += 2;\n continue;\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 if (!closed) {\n throw new Error(`temporal-fmt: unterminated quote in duration format string \"${format}\"`);\n }\n appendLiteral(pieces, literal);\n i = j;\n continue;\n }\n const match = DURATION_TOKEN_STRINGS.find((tok) => format.startsWith(tok, i));\n if (match) {\n // single letter = numeric, double = short, triple = long\n const unit = match[0]!;\n const form = match.length === 1 ? 'numeric' : match.length === 2 ? 'short' : 'long';\n pieces.push({ kind: 'token', value: match, unit, form });\n i += match.length;\n continue;\n }\n appendLiteral(pieces, ch);\n i += 1;\n }\n return pieces;\n}\n\nfunction appendLiteral(pieces: Piece[], value: string): void {\n const last = pieces[pieces.length - 1];\n if (last && last.kind === 'literal') {\n last.value += value;\n } else {\n pieces.push({ kind: 'literal', value });\n }\n}\n\n// Reads a unit value off the duration in a way that works for both real\n// Temporal.Duration objects and field bags ({ hours: 2, minutes: 30 }).\n// Temporal.Duration's fields are numbers, possibly negative for\n// backwards durations; a field bag the caller passes might leave fields\n// off entirely (undefined → treated as 0 for this unit).\nfunction readUnit(duration: Record<string, unknown>, field: UnitKey): number {\n const value = duration[field];\n if (value === undefined || value === null) return 0;\n if (typeof value === 'number' && Number.isFinite(value)) return value;\n // Temporal.Duration values are always numbers, but a hand-built field\n // bag could end up with a string here. Coerce; throw on garbage rather\n // than silently formatting NaN.\n const coerced = Number(value);\n if (!Number.isFinite(coerced)) {\n throw new Error(\n `temporal-fmt: duration field \"${field}\" is not a finite number (got ${String(value)}).`\n );\n }\n return coerced;\n}\n\nexport interface DurationFormatOptions extends FormatOptions {\n /**\n * When true, units whose value is zero are still emitted in the output\n * (e.g. \"0 hours, 30 minutes\" instead of \"30 minutes\"). Default is\n * false: zero-value units are omitted, matching how date-fns's\n * formatDuration works and how most callers rendering a duration to\n * a human would want it.\n */\n showZeroValues?: boolean;\n}\n\n/**\n * Format a Temporal.Duration (or a plain field bag { years, months, ...\n * }) using a duration-specific token string. Token grammar is documented\n * in the README under \"Duration formatting\" — it does NOT reuse the\n * date/time token table, since a duration has no calendar position.\n *\n * Zero-value units are omitted by default; pass { showZeroValues: true }\n * to force them to appear.\n *\n * @example\n * formatDuration(Temporal.Duration.from({ years: 2, months: 1 }), 'yyy ooo')\n * // \"2 years 1 month\"\n * formatDuration({ hours: 2, minutes: 30 }, 'hhh mmm')\n * // \"2 hours 30 minutes\"\n */\nexport function formatDuration(\n duration: Record<string, unknown>,\n formatStr: string,\n options: DurationFormatOptions = {},\n): string {\n if (formatStr.length > MAX_FORMAT_LENGTH) {\n throw new Error(\n `temporal-fmt: duration format string exceeds maximum length of ${MAX_FORMAT_LENGTH} characters ` +\n `(got ${formatStr.length}).`\n );\n }\n\n // locale unused for the actual unit names — those are hardcoded English,\n // same limitation as the `do` ordinal token. Intl.DurationFormat exists\n // in some engines but is still maturing; for now, English-only is\n // explicit. Callers wanting locale-aware duration formatting should use\n // Intl.DurationFormat directly.\n void options.locale;\n\n const showZeroes = options.showZeroValues === true;\n const pieces = tokenizeDuration(formatStr);\n let result = '';\n\n for (const piece of pieces) {\n if (piece.kind === 'literal') {\n result += piece.value;\n continue;\n }\n\n const unit = DURATION_UNITS[piece.unit];\n if (!unit) {\n // shouldn't happen — tokenizeDuration only emits known units\n throw new Error(`temporal-fmt: unknown duration token \"${piece.value}\"`);\n }\n\n const value = readUnit(duration, unit.field);\n if (value === 0 && !showZeroes) {\n // Omit zero-value units entirely. The token's contribution to the\n // output is empty — but this can leave a dangling separator literal\n // (e.g. \"2 hours, \" with nothing after). Callers who want clean\n // output should structure their format string to not put a\n // separator after a unit that might be zero, or use showZeroValues.\n continue;\n }\n\n if (piece.form === 'numeric') {\n result += String(value);\n } else if (piece.form === 'short') {\n result += value + (value === 1 || value === -1 ? unit.shortSingular : unit.shortPlural);\n } else {\n // long form\n result += value + ' ' + (value === 1 || value === -1 ? unit.longSingular : unit.longPlural);\n }\n }\n\n return result;\n}\n","import { DEFAULT_LOCALE, type FormatOptions } from './tokens.js';\nimport { dayOfYear, isGregorianLeapYear } from './isoWeek.js';\n\n// Reads date fields off a Temporal value in the same minimal-shape style\n// as the rest of the library (TemporalLike) — no Temporal factory needed.\n// Works for PlainDate, PlainDateTime, and ZonedDateTime (since they all\n// expose year/month/day). PlainTime has no date fields; formatDistance()\n// between two PlainTime values isn't really meaningful and isn't\n// supported — it'll throw below when the field read fails.\ninterface DateFieldView {\n year?: number;\n month?: number;\n day?: number;\n hour?: number;\n minute?: number;\n second?: number;\n millisecond?: number;\n}\n\nfunction readFields(value: unknown, label: string): DateFieldView {\n if (value === null || typeof value !== 'object') {\n throw new Error(\n `temporal-fmt: formatDistance expects Temporal values, got ${label} = ${String(value)}.`\n );\n }\n const obj = value as Record<string, unknown>;\n // Either year/month/day (a real date) or none of them (a PlainTime);\n // anything in between would be a malformed Temporal-like.\n const hasYear = typeof obj.year === 'number';\n const hasMonth = typeof obj.month === 'number';\n const hasDay = typeof obj.day === 'number';\n if (hasYear !== hasMonth || hasMonth !== hasDay) {\n throw new Error(\n `temporal-fmt: formatDistance got a ${label} with a partial date (some of year/month/day missing). ` +\n `Pass a full Temporal.PlainDate / PlainDateTime / ZonedDateTime.`\n );\n }\n return {\n year: hasYear ? (obj.year as number) : undefined,\n month: hasMonth ? (obj.month as number) : undefined,\n day: hasDay ? (obj.day as number) : undefined,\n hour: typeof obj.hour === 'number' ? (obj.hour as number) : undefined,\n minute: typeof obj.minute === 'number' ? (obj.minute as number) : undefined,\n second: typeof obj.second === 'number' ? (obj.second as number) : undefined,\n millisecond: typeof obj.millisecond === 'number' ? (obj.millisecond as number) : undefined,\n };\n}\n\n// Reference epoch for ms-since computation. Jan 1, 2000 UTC is the same\n// anchor isoWeek.ts uses for day-of-week arithmetic; reusing it keeps the\n// reasoning localized to one well-known reference date.\nconst REFERENCE_YEAR = 2000;\nconst MS_PER_SECOND = 1000;\nconst MS_PER_MINUTE = 60 * MS_PER_SECOND;\nconst MS_PER_HOUR = 60 * MS_PER_MINUTE;\nconst MS_PER_DAY = 24 * MS_PER_HOUR;\n\nfunction daysSinceReference(year: number, month: number, day: number): number {\n let days = 0;\n if (year >= REFERENCE_YEAR) {\n for (let y = REFERENCE_YEAR; y < year; y++) {\n days += isGregorianLeapYear(y) ? 366 : 365;\n }\n } else {\n for (let y = year; y < REFERENCE_YEAR; y++) {\n days -= isGregorianLeapYear(y) ? 366 : 365;\n }\n }\n days += dayOfYear(year, month, day) - 1;\n return days;\n}\n\nfunction toEpochMs(fields: DateFieldView): number {\n // A PlainDate has no time fields — treat as midnight. A PlainDateTime\n // has them all. Comparing two PlainDates is a day-resolution diff;\n // comparing two PlainDateTimes is millisecond-resolution. Mixing the\n // two (one PlainDate, one PlainDateTime) is allowed and just means the\n // PlainDate side is treated as midnight, which is what most callers\n // would intuitively expect (\"distance from this date to this moment\").\n if (fields.year === undefined || fields.month === undefined || fields.day === undefined) {\n throw new Error(\n `temporal-fmt: formatDistance needs a Temporal value with year/month/day fields ` +\n `(PlainDate, PlainDateTime, or ZonedDateTime). A PlainTime or other shape ` +\n `has no anchor date to diff against.`\n );\n }\n const days = daysSinceReference(fields.year, fields.month, fields.day);\n const hour = fields.hour ?? 0;\n const minute = fields.minute ?? 0;\n const second = fields.second ?? 0;\n const millisecond = fields.millisecond ?? 0;\n return days * MS_PER_DAY + hour * MS_PER_HOUR + minute * MS_PER_MINUTE + second * MS_PER_SECOND + millisecond;\n}\n\n// Unit selection cutoffs. Mirrors the rough thresholds date-fns uses,\n// trimmed to the units Intl.RelativeTimeFormat supports in every engine\n// (it accepts 'second' through 'year'; 'quarter' and 'week' support\n// varies, so we skip 'week' here — going days → months directly at the\n// 30-day boundary is simpler and avoids the engine-support cliff).\n//\n// |diff| < 60 s → seconds (\"30 seconds ago\")\n// |diff| < 60 min → minutes (\"12 minutes ago\")\n// |diff| < 24 h → hours (\"3 hours ago\")\n// |diff| < 30 d → days (\"5 days ago\")\n// |diff| < 365 d → months (\"2 months ago\")\n// otherwise → years (\"1 year ago\")\n//\n// 30 days is an approximation of a month — a calendar month is 28-31\n// days, so the boundary is inherently fuzzy. Same with 365 days for a\n// year. Documented as approximations, not exact calendar arithmetic.\nconst UNIT_CUTOFFS: Array<{ maxMs: number; unit: Intl.RelativeTimeFormatUnit }> = [\n { maxMs: MS_PER_MINUTE, unit: 'second' },\n { maxMs: MS_PER_HOUR, unit: 'minute' },\n { maxMs: MS_PER_DAY, unit: 'hour' },\n { maxMs: 30 * MS_PER_DAY, unit: 'day' },\n { maxMs: 365 * MS_PER_DAY, unit: 'month' },\n];\n\nconst rtfCache = new Map<string, Intl.RelativeTimeFormat>();\nconst MAX_RTF_CACHE_SIZE = 100;\n\nfunction getRtf(locale: string, numeric: 'always' | 'auto'): Intl.RelativeTimeFormat {\n const key = `${canonicalLocaleKey(locale)}|${numeric}`;\n let rtf = rtfCache.get(key);\n if (rtf) return rtf;\n if (rtfCache.size >= MAX_RTF_CACHE_SIZE) {\n const oldestKey = rtfCache.keys().next().value;\n if (oldestKey !== undefined) rtfCache.delete(oldestKey);\n }\n rtf = new Intl.RelativeTimeFormat(locale, { numeric });\n rtfCache.set(key, rtf);\n return rtf;\n}\n\nfunction canonicalLocaleKey(locale: string): string {\n try {\n return new Intl.Locale(locale.replace(/_/g, '-')).toString().toLowerCase();\n } catch {\n return locale;\n }\n}\n\nexport interface FormatDistanceOptions extends FormatOptions {\n /**\n * 'auto' (default) lets Intl.RelativeTimeFormat use natural forms like\n * \"yesterday\"/\"tomorrow\"/\"now\" when the rounded value lands on ±1 or 0.\n * 'always' forces the strict \"1 day ago\"/\"in 1 day\"/\"in 0 seconds\" form.\n */\n numeric?: 'always' | 'auto';\n}\n\n/**\n * Returns a human-readable relative-time string describing `date1`\n * relative to `date2`, e.g. \"3 days ago\", \"in 2 hours\", \"now\". Delegates\n * unit names and pluralization to `Intl.RelativeTimeFormat` so the\n * output localizes the same way the rest of the library's locale-aware\n * tokens do.\n *\n * Convention: the result describes `date1`'s position relative to\n * `date2`. `formatDistance(now, threeDaysAgo)` → `\"3 days ago\"` (the\n * past date is described relative to now). `formatDistance(now, twoHoursFromNow)`\n * → `\"in 2 hours\"`. This matches the natural-language reading \"describe\n * the first date as if standing at the second one.\"\n *\n * Unit-selection cutoffs (seconds → minutes → hours → days → months →\n * years) and the rationale for each are documented in the README under\n * \"formatDistance\".\n *\n * @example\n * formatDistance(threeDaysAgo, today) // \"3 days ago\"\n * formatDistance(twoHoursFromNow, today) // \"in 2 hours\"\n * formatDistance(today, today) // \"now\"\n * formatDistance(futureDate, today, {locale:'fr-FR'}) // \"dans 2 jours\"\n */\nexport function formatDistance(\n date1: unknown,\n date2: unknown,\n options: FormatDistanceOptions = {},\n): string {\n const fields1 = readFields(date1, 'date1');\n const fields2 = readFields(date2, 'date2');\n\n // diff > 0: date1 is later than date2 → \"in X units\" (date1 is in\n // the future relative to date2). diff < 0: date1 is earlier than\n // date2 → \"X units ago\". This convention puts date1 in the\n // \"described\" role and date2 in the \"reference\" role, matching\n // natural-language reading: `formatDistance(threeDaysAgo, today)`\n // = \"describe threeDaysAgo as if standing at today\" = \"3 days ago\".\n const diffMs = toEpochMs(fields1) - toEpochMs(fields2);\n const absMs = Math.abs(diffMs);\n\n let unit: Intl.RelativeTimeFormatUnit = 'year';\n for (const { maxMs, unit: candidateUnit } of UNIT_CUTOFFS) {\n if (absMs < maxMs) {\n unit = candidateUnit;\n break;\n }\n }\n\n const divisor = unitToMs(unit);\n const rounded = Math.round(diffMs / divisor);\n\n const numeric = options.numeric ?? 'auto';\n const locale = options.locale ?? DEFAULT_LOCALE;\n const rtf = getRtf(locale, numeric);\n return rtf.format(rounded, unit);\n}\n\nfunction unitToMs(unit: Intl.RelativeTimeFormatUnit): number {\n switch (unit) {\n case 'second': return MS_PER_SECOND;\n case 'minute': return MS_PER_MINUTE;\n case 'hour': return MS_PER_HOUR;\n case 'day': return MS_PER_DAY;\n case 'week': return 7 * MS_PER_DAY;\n case 'month': return 30 * MS_PER_DAY;\n case 'quarter': return 91 * MS_PER_DAY;\n case 'year': return 365 * MS_PER_DAY;\n default:\n // exhaustive switch with a defensive fallback — Intl's\n // RelativeTimeFormatUnit is a fixed set, but TS's narrowing can't\n // see through the unit array iteration above, so this branch\n // exists for the compiler rather than for runtime.\n throw new Error(`temporal-fmt: formatDistance hit unhandled unit \"${String(unit)}\".`);\n }\n}\n","import { getTemporal } from './temporalProvider.js';\nimport { DEFAULT_LOCALE, type FormatOptions } from './tokens.js';\n\n// parseRelative is a different subsystem from the token-based parse(): the\n// input isn't a structurally-typed format string with greedy token matching,\n// it's free English text. Treat it as its own module with its own grammar\n// rather than bolting a natural-language layer onto the existing tokenizer.\n//\n// English only for this pass — the matching patterns are hand-written\n// regular expressions keyed on English month names and weekday names.\n// Documenting as English-only matches how the `do` ordinal token handles\n// its scope (English-only suffix rules), and the rest of the library\n// leaves locale-awareness to Intl. A future pass could add a localized\n// month/weekday table here the same way localeVocab.ts does, but that's\n// not what this round is for.\n\nconst MONTH_NAMES = [\n 'January', 'February', 'March', 'April', 'May', 'June',\n 'July', 'August', 'September', 'October', 'November', 'December',\n];\n// Both full and short forms (\"January\" / \"Jan\") — match the same way the\n// date format MMMM/MMM tokens cover both forms. The longer name is\n// tried first in the alternation so \"January\" doesn't match as \"Jan\" +\n// \"uary\" literal prefix.\nconst MONTH_ALIASES: Array<[string, number]> = MONTH_NAMES.flatMap((name, i) => [[name, i + 1], [name.slice(0, 3), i + 1]]);\nconst MONTH_PATTERN = MONTH_ALIASES.map(([name]) => name).join('|');\nconst WEEKDAY_NAMES = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'];\nconst WEEKDAY_PATTERN = WEEKDAY_NAMES.join('|');\n\n// Reference: ISO dayOfWeek. 1=Mon..7=Sun, matching Temporal's numbering.\nfunction weekdayIndex(name: string): number {\n // case-insensitive lookup — the regex matches case-insensitively, so\n // a lowercase \"tuesday\" or uppercase \"TUESDAY\" can both reach here\n // while still resolving to the right ISO day-of-week index.\n const lower = name.toLowerCase();\n const matched = WEEKDAY_NAMES.find((w) => w.toLowerCase() === lower);\n return WEEKDAY_NAMES.indexOf(matched ?? name) + 1;\n}\nfunction monthIndex(name: string): number {\n // Match either the full name or the 3-letter abbreviation. The regex\n // above ordered longer-first so \"January\" wins over \"Jan\"; here we\n // just check both lists since the regex already returned a single\n // concrete string. Case-insensitive on top of that.\n const lower = name.toLowerCase();\n const fullIdx = MONTH_NAMES.findIndex((m) => m.toLowerCase() === lower);\n if (fullIdx >= 0) return fullIdx + 1;\n const shortIdx = MONTH_NAMES.findIndex((m) => m.slice(0, 3).toLowerCase() === lower);\n return shortIdx + 1;\n}\n\nfunction extractDayOfWeek(referenceDate: { dayOfWeek?: number }): number {\n const dow = referenceDate.dayOfWeek;\n if (typeof dow !== 'number') {\n throw new Error(\n 'temporal-fmt: parseRelative needs a reference date exposing dayOfWeek (a Temporal.PlainDate / PlainDateTime / ZonedDateTime).'\n );\n }\n return dow;\n}\n\ninterface TemporalFieldBag {\n year?: number;\n month?: number;\n day?: number;\n dayOfWeek?: number;\n hour?: number;\n minute?: number;\n second?: number;\n millisecond?: number;\n}\n\nfunction readYear(reference: TemporalFieldBag): number {\n if (typeof reference.year !== 'number') {\n throw new Error('temporal-fmt: parseRelative reference date is missing year.');\n }\n return reference.year;\n}\nfunction readMonth(reference: TemporalFieldBag): number {\n if (typeof reference.month !== 'number') {\n throw new Error('temporal-fmt: parseRelative reference date is missing month.');\n }\n return reference.month;\n}\nfunction readDay(reference: TemporalFieldBag): number {\n if (typeof reference.day !== 'number') {\n throw new Error('temporal-fmt: parseRelative reference date is missing day.');\n }\n return reference.day;\n}\n\nexport interface ParseRelativeOptions extends FormatOptions {}\n\n/**\n * Resolve common English relative-date phrases against a reference date,\n * returning a Temporal.PlainDate. Supported phrases (English only):\n *\n * - weekday refs: \"next Tuesday\", \"last Friday\", \"this Monday\"\n * - day offsets: \"today\", \"tomorrow\", \"yesterday\"\n * - unit offsets: \"in 3 days\", \"2 weeks ago\", \"in 1 month\", \"1 year ago\"\n * - month-day without year: \"March 5th\" (resolved to next occurrence)\n *\n * For ambiguous \"next Tuesday\"-on-a-Tuesday — i.e. today is the named\n * weekday — \"next Tuesday\" means 7 days from now (strictly future), not\n * today. See README \"parseRelative\" for the documented behavior of each\n * ambiguous case.\n *\n * Returns a Temporal.PlainDate for every supported phrase. (Time-of-day\n * extensions like \"next Tuesday at 3pm\" aren't supported in this pass —\n * the return type is always PlainDate.)\n *\n * Throws a descriptive error for any phrase it doesn't recognize, rather\n * than guessing.\n */\nexport function parseRelative(\n input: string,\n referenceDate: unknown,\n options: ParseRelativeOptions = {},\n): unknown {\n // locale option is accepted for API consistency with FormatOptions,\n // but parseRelative is English-only this pass — the value is unused.\n void options.locale;\n const reference = referenceDate as TemporalFieldBag;\n const temporal = getTemporal();\n\n const trimmed = (input ?? '').trim().replace(/\\s+/g, ' ');\n if (trimmed.length === 0) {\n throw new Error('temporal-fmt: parseRelative got an empty input string.');\n }\n const lower = trimmed.toLowerCase();\n\n // \"today\" / \"tomorrow\" / \"yesterday\" — checked first since they're\n // the most common and the most rigidly-shaped.\n if (lower === 'today') {\n return buildPlainDate(temporal, readYear(reference), readMonth(reference), readDay(reference));\n }\n if (lower === 'tomorrow') {\n return addDays(temporal, reference, 1);\n }\n if (lower === 'yesterday') {\n return addDays(temporal, reference, -1);\n }\n\n // weekday references: \"next/last/this Tuesday\"\n // Capture the relative word and the weekday name separately, then\n // resolve via the +7 / -7 logic below.\n const weekdayMatch = trimmed.match(\n new RegExp(`^(next|last|this)\\\\s+(${WEEKDAY_PATTERN})$`, 'i'),\n );\n if (weekdayMatch) {\n const rel = weekdayMatch[1]!.toLowerCase() as 'next' | 'last' | 'this';\n const weekdayName = weekdayMatch[2]!;\n const refDow = extractDayOfWeek(reference);\n const targetDow = weekdayIndex(weekdayName);\n const offset = weekdayOffset(rel, refDow, targetDow);\n return addDays(temporal, reference, offset);\n }\n\n // unit offsets: \"in 3 days\", \"2 weeks ago\", \"in 1 month\", \"1 year ago\"\n // Two shapes share this branch — leading \"in\" (future) or trailing\n // \"ago\" (past). Both lead with <number> <unit>.\n const unitMatch = trimmed.match(\n /^(in\\s+)?(\\d+)\\s+(day|week|month|year)s?(?:\\s+ago)?$/i,\n );\n if (unitMatch) {\n const inPrefix = unitMatch[1];\n const count = unitMatch[2]!;\n const unitName = unitMatch[3]!.toLowerCase() as 'day' | 'week' | 'month' | 'year';\n // Disambiguate future vs past: \"in N units\" is always future; \"N\n // units ago\" is always past. The regex also accepts a bare \"N\n // units\" without either — treat that as a sign error and throw,\n // since neither direction is implied and guessing is exactly what\n // this library refuses to do.\n const hasIn = !!inPrefix;\n const hasAgo = /\\bago\\b/i.test(trimmed);\n if (!hasIn && !hasAgo) {\n throw new Error(\n `temporal-fmt: parseRelative can't tell whether \"${trimmed}\" is past or future — ` +\n `use \"in ${count} ${unitName}s\" or \"${count} ${unitName}s ago\".`\n );\n }\n const sign = hasIn ? 1 : -1;\n return addUnits(temporal, reference, sign * Number(count), unitName);\n }\n\n // month-day without year, e.g. \"March 5th\", \"March 5\", \"March 5, 2026\" (year is ignored if present)\n // The ordinal suffix is decorative — strip it. The day-of-month is a\n // 1- or 2-digit number.\n const monthDayMatch = trimmed.match(\n new RegExp(`^(${MONTH_PATTERN})\\\\s+(\\\\d{1,2})(?:st|nd|rd|th)?(?:,?\\\\s*(\\\\d{4}))?$`, 'i'),\n );\n if (monthDayMatch) {\n const monthName = monthDayMatch[1]!;\n const dayStr = monthDayMatch[2]!;\n const month = monthIndex(monthName);\n const day = Number(dayStr);\n return resolveToNextOccurrence(temporal, reference, month, day);\n }\n\n throw new Error(\n `temporal-fmt: parseRelative doesn't recognize \"${trimmed}\". ` +\n `Supported: weekday refs (\"next Tuesday\"), day offsets (\"today\"/\"tomorrow\"/\"yesterday\"), ` +\n `unit offsets (\"in 3 days\", \"2 weeks ago\"), and month-day (\"March 5th\").`\n );\n}\n\n// \"next X\" — strictly future next occurrence. Said on a Tuesday:\n// \"next Tuesday\" = 7 days from now, not today. Rationale: \"this\n// Tuesday\" handles the same-week case, so \"next Tuesday\" staying\n// strictly-future gives the two phrases distinct, non-overlapping\n// meanings. Documented in README \"parseRelative\".\n//\n// \"last X\" — strictly past most recent occurrence. Symmetric to \"next\".\n//\n// \"this X\" — the X of the current ISO week (Mon..Sun). Can land on\n// today, past, or future depending on where in the week today falls.\nfunction weekdayOffset(rel: 'next' | 'last' | 'this', refDow: number, targetDow: number): number {\n // refDow and targetDow are both 1..7 (ISO Mon=1..Sun=7).\n if (rel === 'next') {\n let diff = targetDow - refDow;\n if (diff <= 0) diff += 7; // 0 (today) → 7, negative → wrap forward\n return diff; // 1..7\n }\n if (rel === 'last') {\n let diff = targetDow - refDow;\n if (diff >= 0) diff -= 7; // 0 (today) → -7, positive → wrap backward\n return diff; // -7..-1\n }\n // rel === 'this' — X of the current week. Can be today, past, or future.\n return targetDow - refDow; // -6..+6\n}\n\nfunction buildPlainDate(temporal: { PlainDate: { from: (fields: Record<string, number>, options?: { overflow?: 'constrain' | 'reject' }) => unknown } }, year: number, month: number, day: number): unknown {\n return temporal.PlainDate.from({ year, month, day }, { overflow: 'reject' });\n}\n\nfunction addDays(\n temporal: { PlainDate: { from: (fields: Record<string, number>) => unknown } },\n reference: TemporalFieldBag,\n days: number,\n): unknown {\n // Build the reference as a real PlainDate so we can use Temporal's\n // own add() for date arithmetic — calendar-correct (handles month\n // boundaries, leap years, etc.) so we don't have to reimplement it.\n const refDate = temporal.PlainDate.from({\n year: readYear(reference),\n month: readMonth(reference),\n day: readDay(reference),\n }) as { add: (duration: Record<string, number>) => unknown };\n return refDate.add({ days });\n}\n\nfunction addUnits(\n temporal: { PlainDate: { from: (fields: Record<string, number>) => unknown } },\n reference: TemporalFieldBag,\n count: number,\n unit: 'day' | 'week' | 'month' | 'year',\n): unknown {\n const refDate = temporal.PlainDate.from({\n year: readYear(reference),\n month: readMonth(reference),\n day: readDay(reference),\n }) as { add: (duration: Record<string, number>) => unknown };\n const duration: Record<string, number> = {};\n if (unit === 'day') duration.days = count;\n if (unit === 'week') duration.weeks = count;\n if (unit === 'month') duration.months = count;\n if (unit === 'year') duration.years = count;\n return refDate.add(duration);\n}\n\n// \"March 5th\" without a year → resolve to the next occurrence of that\n// month/day. If today is that date, return today. Otherwise, if it's\n// already past this year, return next year's occurrence.\n//\n// \"Next occurrence\" semantics: future-leaning. Documented in README\n// as the chosen behavior. The alternative (\"nearest in time, past or\n// future\") would mean \"March 5th\" said on March 6 returns yesterday —\n// counterintuitive for the typical \"next birthday\"/\"next deadline\"\n// use case this kind of phrase tends to drive.\nfunction resolveToNextOccurrence(\n temporal: {\n PlainDate: {\n from: (fields: Record<string, number>, options?: { overflow?: 'constrain' | 'reject' }) => unknown;\n compare?: (one: unknown, two: unknown) => number;\n };\n },\n reference: TemporalFieldBag,\n month: number,\n day: number,\n): unknown {\n const refYear = readYear(reference);\n const refDate = temporal.PlainDate.from({\n year: refYear,\n month: readMonth(reference),\n day: readDay(reference),\n });\n // try this year first; if Feb 29 in a non-leap year, Temporal will\n // throw via overflow: 'reject' — fall through to next year.\n try {\n const thisYear = temporal.PlainDate.from({ year: refYear, month, day }, { overflow: 'reject' });\n // PlainDate.compare is a static method on the namespace, not an\n // instance method — that's why it's called off temporal.PlainDate\n // here rather than off thisYear. Returns -1 / 0 / 1.\n if (!temporal.PlainDate.compare) {\n throw new Error('temporal-fmt: parseRelative needs Temporal.PlainDate.compare to resolve month-day phrases; the active implementation does not expose it.');\n }\n if (temporal.PlainDate.compare(thisYear, refDate) >= 0) {\n // today or in the future → use this year's occurrence\n return thisYear;\n }\n // already past this year → next year's occurrence\n return temporal.PlainDate.from({ year: refYear + 1, month, day }, { overflow: 'reject' });\n } catch (err) {\n // Feb 29 in a non-leap year, or similar. Try next year — if the\n // caller asked for \"Feb 29\" said in a non-leap year, next year may\n // also not be leap, in which case the inner from() will throw and\n // surface a clear error rather than silently landing on Feb 28.\n try {\n return temporal.PlainDate.from({ year: refYear + 1, month, day }, { overflow: 'reject' });\n } catch {\n throw new Error(\n `temporal-fmt: parseRelative can't resolve month ${month} day ${day} — ` +\n `it isn't a valid date in either ${refYear} or ${refYear + 1}. ` +\n `Original error: ${(err as Error).message}`\n );\n }\n }\n}\n"],"mappings":"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","names":["index_exports","__export","format","formatDistance","formatDuration","parse","parseRelative","registerLocaleVocab","setTemporal","__toCommonJS","injectedTemporal","onTemporalChanged","subscribeToTemporalChanges","listener","setTemporal","temporal","resolveTemporal","getTemporal","customVocabs","assertValidVocab","vocab","locale","required","key","length","label","value","entry","i","assertNoCollision","registerLocaleVocab","cacheKey","canonicalCacheKey","vocabCache","MAX_VOCAB_CACHE_SIZE","partValue","formatter","date","type","parts","index","p","prev","next","names","seen","prior","getCustomVocab","getLocaleVocab","custom","cached","monthLongFmt","monthShortFmt","monthLong","monthShort","m","weekdayLongFmt","weekdayShortFmt","weekdayLong","weekdayShort","d","dayPeriodFmt","am","pm","dayPeriod","oldestKey","CUMULATIVE_DAYS_BY_MONTH","isGregorianLeapYear","year","daysInYear","dayOfYear","month","day","doy","REFERENCE_YEAR","dayOfWeekOfJan1","year","offset","REFERENCE_YEAR","y","daysInYear","isoWeekYearAndWeek","month","day","dayOfWeek","thursdayDoyRelative","dayOfYear","isoYear","thursdayDoy","firstThursdayDoy","week","pad","n","len","negative","digits","DEFAULT_LOCALE","formatterCache","MAX_CACHE_SIZE","getFormatter","locale","options","key","canonicalCacheKey","formatter","oldestKey","nativeSupport","subscribeToTemporalChanges","intlSupportsNativeTemporal","temporal","getTemporal","intlPart","partType","calendar","formatterOptions","toInstant","timeZoneId","isZoned","intlSafeTemporal","nativeOptions","parts","index","p","value","prev","next","dayPeriodPart","hour","custom","getCustomVocab","date","part","localeAwareName","customArray","customIndex","TOKENS","t","day","lastDigit","lastTwoDigits","week","isoWeekYearAndWeek","isoYear","SORTED_TOKEN_STRINGS","TOKENS","tok","a","b","tokenize","format","pieces","i","ch","appendLiteral","j","literal","closed","match","value","last","HANDLER_BY_TOKEN","TOKENS","tok","fn","field","format","temporal","formatStr","options","locale","DEFAULT_LOCALE","pieces","tokenize","result","piece","handler","escapeRegExp","literal","alternation","values","TIME_ZONE_SHAPE","getTimeZoneFragment","validZoneSet","getValidZoneSet","FIXED_OFFSET_RE","isValidTimeZone","raw","NUMERIC_FRAGMENTS","QQQ_FRAGMENT","FORMAT_ONLY_TOKENS","YYYY_EXACT","YYYY_EXTENDED","DIGIT_LEADING_TOKENS","tokenFragment","token","locale","nextToken","numeric","vocab","getLocaleVocab","UNPADDED_NUMERIC_TOKENS","UNPADDED_NUMERIC_RANGES","enumerateValidSplits","digits","tokens","memo","solve","tokenIndex","offset","key","cached","result","ranges","results","width","min","max","piece","value","restSplit","escapeRegExp","literal","buildCapturingPattern","pieces","locale","groups","ambiguousRuns","source","i","currentRun","flushRun","idx","piece","name","nextPiece","nextToken","tokenFragment","UNPADDED_NUMERIC_TOKENS","patternCache","MAX_CACHE_SIZE","getPattern","formatStr","locale","key","canonicalCacheKey","pattern","oldestKey","buildCapturingPattern","tokenize","calendarCache","MAX_CALENDAR_CACHE_SIZE","resolveCalendar","canonicalLocale","calendar","parts","extensionIndex","calendarKeyIndex","resolved","assignField","fields","value","applyGroup","token","raw","vocab","getLocaleVocab","periodIndex","pickLenientSplit","splits","tokens","dayIndex","smallDaySplits","s","resolveYear","resolveHour","expected","parse","input","options","DEFAULT_LOCALE","match","name","isValidTimeZone","runPicks","run","runDigits","enumerateValidSplits","lenientValues","groupNames","values","i","year","hour","month","day","minute","second","millisecond","timeZoneId","weekdayExpected","weekdayRaw","quarter","hasAnyDatePart","hasFullDate","hasTime","temporal","getTemporal","timeFields","calendarField","reject","result","err","actual","expectedQuarter","DURATION_UNITS","DURATION_TOKEN_STRINGS","single","a","b","tokenizeDuration","format","pieces","i","ch","appendLiteral","j","literal","closed","match","tok","unit","form","value","last","readUnit","duration","field","coerced","formatDuration","formatStr","options","showZeroes","result","piece","readFields","value","label","obj","hasYear","hasMonth","hasDay","REFERENCE_YEAR","MS_PER_SECOND","MS_PER_MINUTE","MS_PER_HOUR","MS_PER_DAY","daysSinceReference","year","month","day","days","y","isGregorianLeapYear","dayOfYear","toEpochMs","fields","hour","minute","second","millisecond","UNIT_CUTOFFS","rtfCache","MAX_RTF_CACHE_SIZE","getRtf","locale","numeric","key","canonicalLocaleKey","rtf","oldestKey","formatDistance","date1","date2","options","fields1","fields2","diffMs","absMs","unit","maxMs","candidateUnit","divisor","unitToMs","rounded","DEFAULT_LOCALE","MONTH_NAMES","MONTH_ALIASES","name","i","MONTH_PATTERN","WEEKDAY_NAMES","WEEKDAY_PATTERN","weekdayIndex","lower","matched","w","monthIndex","fullIdx","m","extractDayOfWeek","referenceDate","dow","readYear","reference","readMonth","readDay","parseRelative","input","options","temporal","getTemporal","trimmed","buildPlainDate","addDays","weekdayMatch","rel","weekdayName","refDow","targetDow","offset","weekdayOffset","unitMatch","inPrefix","count","unitName","hasIn","hasAgo","addUnits","monthDayMatch","monthName","dayStr","month","day","resolveToNextOccurrence","diff","year","days","unit","refDate","duration","refYear","thisYear","err"]}