temporal-fmt 0.9.0 → 0.9.2
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.
- package/dist/calendarUtils.cjs +4 -259
- package/dist/calendarUtils.cjs.map +1 -1
- package/dist/calendarUtils.js +1 -33
- package/dist/chunk-2JHEV7ZF.js +7 -0
- package/dist/chunk-2JHEV7ZF.js.map +1 -0
- package/dist/chunk-3MZLTVP3.js +2 -0
- package/dist/{chunk-MXXYIMFJ.js.map → chunk-3MZLTVP3.js.map} +1 -1
- package/dist/chunk-4JWGUR4O.js +11 -0
- package/dist/{chunk-AXQLUIDY.js.map → chunk-4JWGUR4O.js.map} +1 -1
- package/dist/chunk-5U5WJ465.js +36 -0
- package/dist/chunk-5U5WJ465.js.map +1 -0
- package/dist/chunk-C5YESWFT.js +2 -0
- package/dist/chunk-C5YESWFT.js.map +1 -0
- package/dist/chunk-C6ZZ62ET.js +2 -0
- package/dist/{chunk-YS52LOMJ.js.map → chunk-C6ZZ62ET.js.map} +1 -1
- package/dist/chunk-EHLRZULM.js +5 -0
- package/dist/{chunk-4ZBGVYLR.js.map → chunk-EHLRZULM.js.map} +1 -1
- package/dist/chunk-F4RGUDA3.js +2 -0
- package/dist/chunk-F4RGUDA3.js.map +1 -0
- package/dist/chunk-J2AQNXF2.js +2 -0
- package/dist/{chunk-GRJQHQLW.js.map → chunk-J2AQNXF2.js.map} +1 -1
- package/dist/chunk-NBXF7V5B.js +2 -0
- package/dist/chunk-NBXF7V5B.js.map +1 -0
- package/dist/chunk-R52YOKI3.js +7 -0
- package/dist/{chunk-M33NSC4E.js.map → chunk-R52YOKI3.js.map} +1 -1
- package/dist/chunk-UJR6DHXY.js +2 -0
- package/dist/chunk-UJR6DHXY.js.map +1 -0
- package/dist/chunk-VQXUMFB2.js +21 -0
- package/dist/{chunk-VHSZL3P3.js.map → chunk-VQXUMFB2.js.map} +1 -1
- package/dist/chunk-XH46JNPK.js +2 -0
- package/dist/chunk-XH46JNPK.js.map +1 -0
- package/dist/chunk-YB6YAG7E.js +35 -0
- package/dist/chunk-YB6YAG7E.js.map +1 -0
- package/dist/chunk-ZGMTY52O.js +2 -0
- package/dist/chunk-ZGMTY52O.js.map +1 -0
- package/dist/duration.cjs +9 -624
- package/dist/duration.cjs.map +1 -1
- package/dist/duration.js +1 -29
- package/dist/format.cjs +18 -769
- package/dist/format.cjs.map +1 -1
- package/dist/format.js +1 -20
- package/dist/formatDuration.d.cts +6 -0
- package/dist/formatDuration.d.ts +6 -0
- package/dist/index.cjs +146 -6672
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +58 -3004
- package/dist/index.js.map +1 -1
- package/dist/interval.cjs +18 -952
- package/dist/interval.cjs.map +1 -1
- package/dist/interval.js +1 -41
- package/dist/localeRegistry.cjs +15 -328
- package/dist/localeRegistry.cjs.map +1 -1
- package/dist/localeRegistry.js +1 -11
- package/dist/localeVocab.d.cts +3 -0
- package/dist/localeVocab.d.ts +3 -0
- package/dist/parse.cjs +53 -1637
- package/dist/parse.cjs.map +1 -1
- package/dist/parse.js +1 -20
- package/dist/parsePattern.d.cts +1 -0
- package/dist/parsePattern.d.ts +1 -0
- package/dist/pattern.d.cts +1 -0
- package/dist/pattern.d.ts +1 -0
- package/dist/recurrence.cjs +20 -450
- package/dist/recurrence.cjs.map +1 -1
- package/dist/recurrence.js +1 -20
- package/dist/relativeTime.cjs +24 -177
- package/dist/relativeTime.cjs.map +1 -1
- package/dist/relativeTime.js +1 -14
- package/dist/rounding.d.cts +1 -0
- package/dist/rounding.d.ts +1 -0
- package/dist/serialization.d.cts +1 -1
- package/dist/serialization.d.ts +1 -1
- package/dist/timezone.cjs +20 -387
- package/dist/timezone.cjs.map +1 -1
- package/dist/timezone.js +1 -26
- package/dist/typeGuards.d.cts +9 -9
- package/dist/typeGuards.d.ts +9 -9
- package/package.json +4 -2
- package/dist/chunk-4ZBGVYLR.js +0 -182
- package/dist/chunk-AXQLUIDY.js +0 -183
- package/dist/chunk-CKVHXFX7.js +0 -902
- package/dist/chunk-CKVHXFX7.js.map +0 -1
- package/dist/chunk-CSUIKL73.js +0 -215
- package/dist/chunk-CSUIKL73.js.map +0 -1
- package/dist/chunk-EDUFSRWJ.js +0 -9
- package/dist/chunk-EDUFSRWJ.js.map +0 -1
- package/dist/chunk-FQA4XJME.js +0 -57
- package/dist/chunk-FQA4XJME.js.map +0 -1
- package/dist/chunk-FTMLZ4EN.js +0 -196
- package/dist/chunk-FTMLZ4EN.js.map +0 -1
- package/dist/chunk-GRJQHQLW.js +0 -170
- package/dist/chunk-JCIMP43G.js +0 -602
- package/dist/chunk-JCIMP43G.js.map +0 -1
- package/dist/chunk-KDYK72Z2.js +0 -75
- package/dist/chunk-KDYK72Z2.js.map +0 -1
- package/dist/chunk-M33NSC4E.js +0 -169
- package/dist/chunk-MXXYIMFJ.js +0 -29
- package/dist/chunk-NFKQ3EXC.js +0 -188
- package/dist/chunk-NFKQ3EXC.js.map +0 -1
- package/dist/chunk-O6GVMBLW.js +0 -403
- package/dist/chunk-O6GVMBLW.js.map +0 -1
- package/dist/chunk-VHSZL3P3.js +0 -242
- package/dist/chunk-VSUCBUTC.js +0 -295
- package/dist/chunk-VSUCBUTC.js.map +0 -1
- package/dist/chunk-YS52LOMJ.js +0 -51
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/rounding.ts","../src/formatDuration.ts","../src/duration.ts"],"sourcesContent":["// Rounding helpers. Pure functions over field bags,\n// same convention as arithmetic.ts/calendarUtils.ts.\n\nimport { asDateFieldView, type DateFieldView } from './calendarUtils.js';\n\ntype RoundingUnit = 'day' | 'hour' | 'minute' | 'second' | 'millisecond';\ntype RoundingMode = 'nearest' | 'floor' | 'ceil' | 'trunc';\n\ninterface DateTimeFieldView extends DateFieldView {\n hour?: number;\n minute?: number;\n second?: number;\n millisecond?: number;\n}\n\n// Ms-per-unit table. Used by round() to convert a field bag to ms,\n// apply the rounding mode, and convert back. Mirrors how add() in\n// arithmetic.ts handles sub-day arithmetic.\nconst MS_PER_UNIT: Record<RoundingUnit, number> = {\n day: 86_400_000,\n hour: 3_600_000,\n minute: 60_000,\n second: 1_000,\n millisecond: 1,\n};\n\nfunction applyMode(ms: number, mode: RoundingMode, increment: number): number {\n // All four modes operate on absolute value then re-apply the sign,\n // so negative values round symmetrically (floor of -1.5 → -2, not -1).\n // Matches Math.round semantics for 'nearest' and Temporal's rounding\n // for the other three.\n const sign = ms < 0 ? -1 : 1;\n const abs = Math.abs(ms);\n const stepped = abs / increment;\n let rounded: number;\n switch (mode) {\n case 'nearest': rounded = Math.round(stepped); break;\n case 'floor': rounded = Math.floor(stepped); break;\n case 'ceil': rounded = Math.ceil(stepped); break;\n case 'trunc': rounded = Math.trunc(stepped); break;\n }\n return sign * rounded * increment;\n}\n\nfunction toMs(v: DateTimeFieldView): number {\n // Returns ms-since-(Howard Hinnant epoch). Used internally — the\n // absolute value is meaningful only relative to the same epoch used\n // by fromMs.\n const y = v.year!, m = v.month!, d = v.day!;\n const y2 = m <= 2 ? y - 1 : y;\n const era = Math.floor((y2 >= 0 ? y2 : y2 - 399) / 400);\n const yoe = y2 - era * 400;\n const m2 = m > 2 ? m - 3 : m + 9;\n const doy = Math.floor((153 * m2 + 2) / 5) + d - 1;\n const doe = yoe * 365 + Math.floor(yoe / 4) - Math.floor(yoe / 100) + doy;\n const days = era * 146097 + doe - 719468;\n return days * MS_PER_UNIT.day\n + (v.hour ?? 0) * MS_PER_UNIT.hour\n + (v.minute ?? 0) * MS_PER_UNIT.minute\n + (v.second ?? 0) * MS_PER_UNIT.second\n + (v.millisecond ?? 0);\n}\n\nfunction fromMs(ms: number, base: DateTimeFieldView): DateTimeFieldView {\n // Convert absolute ms (on the same epoch as toMs) back to a field bag.\n // Walks the Howard Hinnant days_from_civil inverse inline.\n const MS_PER_DAY = MS_PER_UNIT.day;\n const totalDays = Math.floor(ms / MS_PER_DAY);\n let withinDay = ms - totalDays * MS_PER_DAY; // ms since midnight\n // Defensive floating-point guard: given totalDays = Math.floor(ms /\n // MS_PER_DAY), withinDay = ms - totalDays * MS_PER_DAY is\n // mathematically guaranteed non-negative (that's what Math.floor\n // division gives you), verified against extreme values including\n // Number.MIN_SAFE_INTEGER and sub-ms fractional noise near day\n // boundaries. Kept in case a future change to how ms is computed\n // upstream breaks that guarantee.\n /* c8 ignore next */\n if (withinDay < 0) withinDay += MS_PER_DAY;\n const hour = Math.floor(withinDay / MS_PER_UNIT.hour);\n const minute = Math.floor((withinDay % MS_PER_UNIT.hour) / MS_PER_UNIT.minute);\n const second = Math.floor((withinDay % MS_PER_UNIT.minute) / MS_PER_UNIT.second);\n const millisecond = withinDay % MS_PER_UNIT.second;\n // Convert totalDays back to year/month/day via Howard Hinnant's\n // civil_from_days. Same algorithm as arithmetic.ts's shiftDays.\n const z = totalDays + 719468;\n const era = Math.floor((z >= 0 ? z : z - 146096) / 146097);\n const doe = z - era * 146097;\n const yoe = Math.floor((doe - Math.floor(doe / 1460) + Math.floor(doe / 36524) - Math.floor(doe / 146096)) / 365);\n const y2 = yoe + era * 400;\n const doy = doe - (365 * yoe + Math.floor(yoe / 4) - Math.floor(yoe / 100));\n const mp = Math.floor((5 * doy + 2) / 153);\n const d = doy - Math.floor((153 * mp + 2) / 5) + 1;\n const m = mp < 10 ? mp + 3 : mp - 9;\n const year = m <= 2 ? y2 + 1 : y2;\n return { ...base, year, month: m, day: d, hour, minute, second, millisecond };\n}\n\nfunction shiftDays(v: DateTimeFieldView, days: number): DateTimeFieldView {\n const y = v.year!, m = v.month!, d = v.day!;\n const y2 = m <= 2 ? y - 1 : y;\n const era = Math.floor((y2 >= 0 ? y2 : y2 - 399) / 400);\n const yoe = y2 - era * 400;\n const m2 = m > 2 ? m - 3 : m + 9;\n const doy = Math.floor((153 * m2 + 2) / 5) + d - 1;\n const doe = yoe * 365 + Math.floor(yoe / 4) - Math.floor(yoe / 100) + doy;\n const totalDays = era * 146097 + doe - 719468 + days;\n const z = totalDays + 719468;\n const era2 = Math.floor((z >= 0 ? z : z - 146096) / 146097);\n const doe2 = z - era2 * 146097;\n const yoe2 = Math.floor((doe2 - Math.floor(doe2 / 1460) + Math.floor(doe2 / 36524) - Math.floor(doe2 / 146096)) / 365);\n const y2out = yoe2 + era2 * 400;\n const doy2 = doe2 - (365 * yoe2 + Math.floor(yoe2 / 4) - Math.floor(yoe2 / 100));\n const mp = Math.floor((5 * doy2 + 2) / 153);\n const d2 = doy2 - Math.floor((153 * mp + 2) / 5) + 1;\n const m2out = mp < 10 ? mp + 3 : mp - 9;\n const yOut = m2out <= 2 ? y2out + 1 : y2out;\n return { ...v, year: yOut, month: m2out, day: d2 };\n}\n\nexport interface RoundOptions {\n unit: RoundingUnit;\n mode?: RoundingMode;\n roundingIncrement?: number;\n}\n\nexport function round(value: unknown, options: RoundOptions): DateTimeFieldView {\n const base = asDateFieldView(value) as DateTimeFieldView;\n const mode = options.mode ?? 'nearest';\n const increment = (options.roundingIncrement ?? 1) * MS_PER_UNIT[options.unit];\n if (increment <= 0) {\n throw new Error(`temporal-fmt: round() requires a positive roundingIncrement (got ${options.roundingIncrement}).`);\n }\n const ms = toMs(base);\n const rounded = applyMode(ms, mode, increment);\n return fromMs(rounded, base);\n}\n\nexport function floor(value: unknown, unit: RoundingUnit, roundingIncrement: number = 1): DateTimeFieldView {\n return round(value, { unit, mode: 'floor', roundingIncrement });\n}\n\nexport function ceil(value: unknown, unit: RoundingUnit, roundingIncrement: number = 1): DateTimeFieldView {\n return round(value, { unit, mode: 'ceil', roundingIncrement });\n}\n\nexport function truncate(value: unknown, unit: RoundingUnit, roundingIncrement: number = 1): DateTimeFieldView {\n return round(value, { unit, mode: 'trunc', roundingIncrement });\n}\n\n// Duration rounding. Takes a duration field bag and rounds it to the\n// requested unit. Operates on the duration's own fields (years/months\n// for calendar-bound units, days/hours/minutes/seconds/ms/µs/ns for\n// absolute units). Mirrors Temporal.Duration.prototype.round's surface\n// — but operates on plain field bags rather than real Temporal.Duration\n// instances, same convention as the rest of this module set.\nexport interface DurationFields {\n years?: number;\n months?: number;\n weeks?: number;\n days?: number;\n hours?: number;\n minutes?: number;\n seconds?: number;\n milliseconds?: number;\n microseconds?: number;\n nanoseconds?: number;\n}\n\ntype DurationUnit = 'years' | 'months' | 'weeks' | 'days' | 'hours' | 'minutes' | 'seconds' | 'milliseconds' | 'microseconds' | 'nanoseconds';\n\nconst DURATION_UNIT_TO_NS: Record<DurationUnit, bigint> = {\n years: 0n, // calendar-bound, can't convert without relativeTo\n months: 0n, // same\n weeks: 0n, // same\n days: 86_400n * 1_000_000_000n,\n hours: 3_600n * 1_000_000_000n,\n minutes: 60n * 1_000_000_000n,\n seconds: 1_000_000_000n,\n milliseconds: 1_000_000n,\n microseconds: 1_000n,\n nanoseconds: 1n,\n};\n\n// Absolute units (days and below) can be rounded without a relativeTo\n// because their length is fixed in nanoseconds. Calendar-bound units\n// (years/months/weeks) can't — would need a Temporal.Duration round()\n// call with a relativeTo. This helper throws if the caller tries to\n// round to/from a calendar-bound unit, rather than silently producing\n// a wrong result.\nfunction isCalendarBound(unit: DurationUnit): boolean {\n return unit === 'years' || unit === 'months' || unit === 'weeks';\n}\n\nexport function roundDuration(duration: DurationFields, options: {\n unit: DurationUnit;\n mode?: RoundingMode;\n roundingIncrement?: number;\n}): DurationFields {\n const mode = options.mode ?? 'nearest';\n const increment = BigInt(options.roundingIncrement ?? 1);\n if (increment <= 0n) {\n throw new Error(`temporal-fmt: roundDuration() requires a positive roundingIncrement (got ${options.roundingIncrement}).`);\n }\n if (isCalendarBound(options.unit)) {\n throw new Error(\n `temporal-fmt: roundDuration() to \"${options.unit}\" requires a Temporal.Duration with a relativeTo — ` +\n `this helper operates on plain field bags without calendar context. Use Temporal.Duration.prototype.round() directly.`\n );\n }\n\n // Sum all absolute-unit contributions into total nanoseconds.\n let totalNs = 0n;\n for (const u of ['days', 'hours', 'minutes', 'seconds', 'milliseconds', 'microseconds', 'nanoseconds'] as DurationUnit[]) {\n const v = duration[u] ?? 0;\n totalNs += BigInt(v) * DURATION_UNIT_TO_NS[u];\n }\n\n // Apply rounding on the absolute value, preserving sign (same as round()).\n const stepNs = increment * DURATION_UNIT_TO_NS[options.unit];\n const sign = totalNs < 0n ? -1n : 1n;\n const abs = totalNs < 0n ? -totalNs : totalNs;\n const stepped = abs / stepNs;\n const remainder = abs % stepNs;\n let rounded: bigint;\n switch (mode) {\n case 'nearest':\n // Banker's rounding would be the Temporal spec, but Math.round-style\n // half-up is what callers typically expect from a \"round\" function.\n // Going with half-up to match round() above and avoid surprising anyone\n // who reads the output.\n rounded = remainder * 2n >= stepNs ? stepped + 1n : stepped;\n break;\n case 'floor': rounded = stepped; break;\n case 'ceil': rounded = remainder > 0n ? stepped + 1n : stepped; break;\n case 'trunc': rounded = stepped; break;\n }\n const newTotalNs = sign * rounded * stepNs;\n\n // Distribute back into the largest unit ≤ the requested unit. This\n // produces a balanced duration (e.g. rounding 90s to minutes gives\n // 1m30s in the minute field, not 90s).\n const result: DurationFields = { ...duration };\n let remaining = newTotalNs;\n // Clear all absolute units, then re-populate from largest to smallest.\n for (const u of ['days', 'hours', 'minutes', 'seconds', 'milliseconds', 'microseconds', 'nanoseconds'] as DurationUnit[]) {\n if (u === options.unit || isLargerUnit(u, options.unit) || u === options.unit) {\n // Keep calendar-bound units as-is; clear absolute units smaller\n // than the rounding target so the balanced output is clean.\n }\n result[u] = 0;\n }\n // Re-populate from the rounding unit downward (largest absolute unit\n // ≤ the target), so the result has all its value concentrated at the\n // target unit and below.\n const unitsInOrder: DurationUnit[] = ['days', 'hours', 'minutes', 'seconds', 'milliseconds', 'microseconds', 'nanoseconds'];\n const startIdx = unitsInOrder.indexOf(options.unit);\n // Include days in the distribution if days is below or equal to the target.\n // Otherwise leave days untouched (caller's days value stays).\n for (let i = 0; i <= startIdx; i++) {\n const u = unitsInOrder[i]!;\n const unitNs = DURATION_UNIT_TO_NS[u];\n // Dead by construction: unitsInOrder only lists days/hours/minutes/\n // seconds/milliseconds/microseconds/nanoseconds, none of which are\n // 0n in DURATION_UNIT_TO_NS (only years/months/weeks are, and those\n // never appear in this array). Kept in case unitsInOrder grows to\n // include a calendar-bound unit later.\n /* c8 ignore next */\n if (unitNs === 0n) continue;\n const count = remaining / unitNs;\n remaining -= count * unitNs;\n if (count !== 0n) {\n result[u] = Number(count);\n }\n }\n return result;\n}\n\nfunction isLargerUnit(_a: DurationUnit, _b: DurationUnit): boolean {\n // Helper kept for clarity above — currently unused because the\n // distribution loop handles ordering explicitly via unitsInOrder.\n return false;\n}","import { type FormatOptions } from './tokens.js';\nimport { tokenize } from './tokenize.js';\nimport { MAX_FORMAT_LENGTH } from './constants.js';\nimport { canonicalCacheKey } from './localeVocab.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// Locale-aware rendering: when a `locale` is passed, the short/long\n// forms delegate to `Intl.NumberFormat` with `style: 'unit'`. That gives\n// us pluralization, native unit names, and the locale's preferred\n// spacing for free, matching the same \"delegate to Intl\" approach\n// `formatDistance` already uses. Without a `locale`, the original\n// English-only hardcoded table is used so existing default output stays\n// byte-identical — this is an additive locale option, not a rewrite of\n// the default rendering path.\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 // Intl.NumberFormat unit identifier (used only on the locale-aware path).\n // Confirmed against the current Intl spec — including `millisecond`, which\n // is in fact supported by every engine we target (Node 20+, modern ICU).\n // The task brief flagged this as a possible gap; empirically it isn't.\n intlUnit: string;\n}\n\nconst DURATION_UNITS: Record<string, UnitForms> = {\n y: { longSingular: 'year', longPlural: 'years', shortSingular: 'yr', shortPlural: 'yrs', field: 'years', intlUnit: 'year' },\n o: { longSingular: 'month', longPlural: 'months', shortSingular: 'mo', shortPlural: 'mos', field: 'months', intlUnit: 'month' },\n w: { longSingular: 'week', longPlural: 'weeks', shortSingular: 'wk', shortPlural: 'wks', field: 'weeks', intlUnit: 'week' },\n d: { longSingular: 'day', longPlural: 'days', shortSingular: 'd', shortPlural: 'd', field: 'days', intlUnit: 'day' },\n h: { longSingular: 'hour', longPlural: 'hours', shortSingular: 'h', shortPlural: 'h', field: 'hours', intlUnit: 'hour' },\n m: { longSingular: 'minute', longPlural: 'minutes', shortSingular: 'm', shortPlural: 'm', field: 'minutes', intlUnit: 'minute' },\n s: { longSingular: 'second', longPlural: 'seconds', shortSingular: 's', shortPlural: 's', field: 'seconds', intlUnit: 'second' },\n S: { longSingular: 'millisecond', longPlural: 'milliseconds', shortSingular: 'ms', shortPlural: 'ms', field: 'milliseconds', intlUnit: 'millisecond' },\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 values 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\n// Intl.NumberFormat is expensive to construct, and a typical duration\n// render walks 2-4 units — so a tight loop on the same (locale, unit,\n// display) triple would otherwise rebuild the formatter for every token\n// of every row. Cache by canonical-locale + unit + display, same\n// eviction shape as the other locale-keyed caches in this library\n// (formatterCache in tokens.ts, rtfCache in formatDistance.ts).\nconst unitFormatterCache = new Map<string, Intl.NumberFormat>();\nconst MAX_UNIT_FORMATTER_CACHE_SIZE = 200;\n\nfunction getUnitFormatter(locale: string, intlUnit: string, unitDisplay: 'short' | 'long'): Intl.NumberFormat {\n const key = `${canonicalCacheKey(locale)}|${intlUnit}|${unitDisplay}`;\n const cached = unitFormatterCache.get(key);\n if (cached) return cached;\n if (unitFormatterCache.size >= MAX_UNIT_FORMATTER_CACHE_SIZE) {\n const oldestKey = unitFormatterCache.keys().next().value;\n if (oldestKey !== undefined) unitFormatterCache.delete(oldestKey);\n }\n const formatter = new Intl.NumberFormat(locale, {\n style: 'unit',\n unit: intlUnit,\n unitDisplay,\n });\n unitFormatterCache.set(key, formatter);\n return formatter;\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 * Unit-name localization: without a `locale`, output is the original\n * English hardcoded singular/plural forms (byte-identical to previous\n * versions). With a `locale`, the short/long forms delegate to\n * `Intl.NumberFormat`'s `style: 'unit'` — same approach `formatDistance`\n * already uses for `Intl.RelativeTimeFormat`. Numeric-only tokens\n * (`y`, `o`, `w`, ...) are not affected by `locale`; they remain ASCII\n * digits, matching the rest of this library's \"numbers stay Western\"\n * convention.\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', { locale: 'fr-FR' })\n * // \"2 heures 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 const showZeroes = options.showZeroValues === true;\n // Undefined locale → keep the original hardcoded English path so\n // existing default output is byte-identical. Any locale string passed\n // (including 'en-US') takes the Intl.NumberFormat path — so an\n // explicit `locale: 'en-US'` will produce Intl's spacing/word choices\n // rather than the hand-rolled English table. That's intentional: the\n // locale-aware path delegates to Intl, the default path doesn't.\n const locale = options.locale;\n const useIntl = locale !== undefined;\n\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 // Dead by construction: piece.unit always comes from match[0] where\n // match is a member of DURATION_TOKEN_STRINGS, itself built\n // exhaustively from DURATION_UNITS' own keys — so this lookup can\n // never miss. Kept in case tokenizeDuration's token construction\n // ever diverges from DURATION_UNITS.\n /* c8 ignore start */\n if (!unit) {\n throw new Error(`temporal-fmt: unknown duration token \"${piece.value}\"`);\n }\n /* c8 ignore stop */\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 continue;\n }\n\n if (useIntl) {\n // Intl.NumberFormat handles pluralization, native unit names, and\n // locale-appropriate spacing all in one call — no point replicating\n // that here. We pass the value as-is so negative durations\n // (`-1 hour`) round-trip the sign the way Intl expects.\n const unitDisplay = piece.form === 'short' ? 'short' : 'long';\n const formatter = getUnitFormatter(locale!, unit.intlUnit, unitDisplay);\n result += formatter.format(value);\n continue;\n }\n\n // Default-path English. Plural rule based on absolute value matches\n // what Intl does for unit style on en-US — only |value| === 1 is\n // singular, including for negatives like \"-1 hour\" (which is what\n // Intl produces for en-US too).\n if (piece.form === 'short') {\n result += value + (value === 1 || value === -1 ? unit.shortSingular : unit.shortPlural);\n } else {\n result += value + ' ' + (value === 1 || value === -1 ? unit.longSingular : unit.longPlural);\n }\n }\n\n return result;\n}","// Duration extensions. The existing formatDuration()\n// in formatDuration.ts stays as-is; this module adds the rest of the\n// duration surface: parseDuration, parseISODuration, formatISODuration,\n// formatDurationToParts, balanceDuration, roundDuration (re-exported\n// from rounding.ts), totalDuration, compareDuration, addDuration,\n// subtractDuration.\n//\n// All operate on plain field bags ({ years, months, weeks, days, hours,\n// minutes, seconds, milliseconds, microseconds, nanoseconds }) rather\n// than Temporal.Duration instances — same convention as the rest of\n// this module set, so callers can use these without committing to a\n// Temporal implementation. addDuration/subtractDuration produce a new\n// field bag; totalDuration produces a number; compareDuration produces\n// -1/0/1.\n\nimport { roundDuration, type DurationFields } from './rounding.js';\nimport { getTemporal } from './temporalProvider.js';\nimport { formatDuration } from './formatDuration.js';\nimport { InvalidDurationError, FormatSyntaxError } from './errors.js';\n\n// Re-export roundDuration here so callers can import everything duration-\n// related from one place.\nexport { roundDuration };\nexport type { DurationFields };\n\n// Mirrors formatToParts in format.ts. Returns the same piece list\n// formatDuration() would consume, but split into parts instead of\n// joined. Useful for callers building custom UIs (one DOM node per\n// unit) or doing their own unit suppression logic.\n\nimport type { Piece } from './tokenize.js';\n\nexport interface DurationPart {\n type: 'literal' | 'token';\n value: string;\n token?: string;\n}\n\n// Delegates to formatDuration() by formatting once with each piece\n// in isolation, then splitting. Simpler than duplicating the\n// formatting logic — at the cost of running the formatter multiple\n// times. For typical durations (2-4 units) this is fine.\nexport function formatDurationToParts(\n duration: Record<string, unknown>,\n formatStr: string,\n options: Parameters<typeof formatDuration>[2] = {},\n): DurationPart[] {\n // Format the whole thing once to get the joined output, then walk\n // the formatStr's tokens and slice the output by each token's\n // rendered width. This works because formatDuration() emits each\n // token's value verbatim with literals in between — there's no\n // locale-dependent reordering.\n const full = formatDuration(duration, formatStr, options);\n // Re-tokenize the format string to know what tokens are present.\n // (formatDuration has its own tokenizer; we don't re-implement it\n // here. Instead, we scan the format string for token runs the same\n // way its tokenizer does.)\n const tokens = tokenizeDurationFormat(formatStr);\n const parts: DurationPart[] = [];\n let pos = 0;\n for (const tok of tokens) {\n if (tok.kind === 'literal') {\n // Find the literal text in `full` starting at `pos`. It should\n // be there verbatim.\n const lit = tok.value;\n if (full.slice(pos, pos + lit.length) === lit) {\n parts.push({ type: 'literal', value: lit });\n pos += lit.length;\n /* c8 ignore start @preserve -- unreachable given current formatDuration(): every\n literal is appended to `full` unconditionally regardless of adjacent\n zero-value tokens, and pos tracking never desyncs from the true\n literal position (verified via adversarial testing: duplicate\n literals, skipped tokens before/after, ambiguous text — all still\n found via indexOf as expected). Kept as a guard against a future\n formatDuration() change that conditionally drops literals. */\n } else {\n // Literal missing from output (e.g. zero-value unit was\n // skipped). Don't emit a part for it.\n }\n /* c8 ignore stop @preserve */\n } else {\n // Token: find the next non-literal chunk of `full` starting at\n // `pos`. Use the next literal piece (if any) as the bound.\n const nextLiteralStart = findNextLiteralStart(tokens, tok, full, pos);\n const value = full.slice(pos, nextLiteralStart);\n parts.push({ type: 'token', value, token: tok.value });\n pos = nextLiteralStart;\n }\n }\n return parts;\n}\n\n// Minimal tokenizer for the duration format string. Reuses the same\n// quoted-literal and greedy-match logic as tokenize.ts.\nfunction tokenizeDurationFormat(formatStr: string): Array<Piece & { unit?: string }> {\n const DURATION_TOKEN_STRINGS = ['y', 'o', 'w', 'd', 'h', 'm', 's', 'S']\n .flatMap((single) => [single + single + single, single + single, single])\n .sort((a, b) => b.length - a.length);\n const pieces: Array<Piece & { unit?: string }> = [];\n let i = 0;\n while (i < formatStr.length) {\n const ch = formatStr[i];\n if (ch === \"'\") {\n if (formatStr[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 < formatStr.length) {\n if (formatStr[j] === \"'\") {\n if (formatStr[j + 1] === \"'\") {\n literal += \"'\";\n j += 2;\n continue;\n }\n closed = true;\n j += 1;\n break;\n }\n literal += formatStr[j]!;\n j += 1;\n }\n if (!closed) throw new FormatSyntaxError({ format: formatStr, reason: 'unterminated quote' });\n appendLiteral(pieces, literal);\n i = j;\n continue;\n }\n const match = DURATION_TOKEN_STRINGS.find((tok) => formatStr.startsWith(tok, i));\n if (match) {\n pieces.push({ kind: 'token', value: match, unit: match[0] });\n i += match.length;\n continue;\n }\n appendLiteral(pieces, ch);\n i += 1;\n }\n return pieces;\n}\n\nfunction appendLiteral(pieces: Array<Piece & { unit?: string }>, 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\nfunction findNextLiteralStart(\n tokens: Array<Piece & { unit?: string }>,\n _currentTok: Piece & { unit?: string },\n full: string,\n pos: number,\n): number {\n // Find the position in `full` where the next literal after the\n // current token starts. For now, just return the end of `full` —\n // formatDurationToParts is best-effort for the common case where\n // tokens are separated by literals, and degrades gracefully when\n // they aren't.\n // A more precise implementation would walk the tokens array from\n // the current position and use the next literal piece's text to\n // find the boundary in `full`. Doing it correctly requires handling\n // zero-value unit suppression, which is why this helper exists.\n // For now, we use the next literal piece's value as a search anchor.\n const idx = tokens.indexOf(_currentTok);\n for (let j = idx + 1; j < tokens.length; j++) {\n const t = tokens[j]!;\n if (t.kind === 'literal') {\n const found = full.indexOf(t.value, pos);\n if (found >= 0) return found;\n }\n }\n return full.length;\n}\n\n// ISO 8601 duration format: P[n]Y[n]M[n]W[n]DT[n]H[n]M[n]S\n// e.g. \"P3Y6M4DT12H30M5S\", \"PT1H30M\", \"P1W\", \"P0D\".\n// Parsed into a DurationFields bag.\nexport function parseISODuration(input: string): DurationFields {\n // Loose grammar: P [years Y] [months M] [weeks W] [days D] [T [hours H] [minutes M] [seconds S]]\n // Weeks is an ISO 8601-2 extension; widely supported.\n const re = /^P(?:(?:(\\d+(?:\\.\\d+)?)Y)?(?:(\\d+(?:\\.\\d+)?)M)?(?:(\\d+(?:\\.\\d+)?)W)?(?:(\\d+(?:\\.\\d+)?)D)?)?(?:T(?:(\\d+(?:\\.\\d+)?)H)?(?:(\\d+(?:\\.\\d+)?)M)?(?:(\\d+(?:\\.\\d+)?)S)?)?$/;\n const m = re.exec(input);\n if (!m) {\n throw new InvalidDurationError({ input, reason: 'does not match ISO 8601 duration grammar (P[n]Y[n]M[n]W[n]DT[n]H[n]M[n]S)' });\n }\n // All-zero / empty: P0D is valid ISO for zero duration.\n if (m.slice(1).every((g) => g === undefined)) {\n throw new InvalidDurationError({ input, reason: 'duration has no fields (use \"P0D\" for zero duration)' });\n }\n const toNum = (s: string | undefined): number => s === undefined ? 0 : Number(s);\n return {\n years: toNum(m[1]),\n months: toNum(m[2]),\n weeks: toNum(m[3]),\n days: toNum(m[4]),\n hours: toNum(m[5]),\n minutes: toNum(m[6]),\n seconds: toNum(m[7]),\n };\n}\n\nexport function formatISODuration(duration: DurationFields): string {\n const parts: string[] = ['P'];\n const years = duration.years ?? 0;\n const months = duration.months ?? 0;\n const weeks = duration.weeks ?? 0;\n const days = duration.days ?? 0;\n const hours = duration.hours ?? 0;\n const minutes = duration.minutes ?? 0;\n const seconds = duration.seconds ?? 0;\n if (years) parts.push(`${years}Y`);\n if (months) parts.push(`${months}M`);\n if (weeks) parts.push(`${weeks}W`);\n if (days) parts.push(`${days}D`);\n if (hours || minutes || seconds) {\n parts.push('T');\n if (hours) parts.push(`${hours}H`);\n if (minutes) parts.push(`${minutes}M`);\n if (seconds) parts.push(`${seconds}S`);\n }\n // parts is just ['P'] when every field is zero -- ISO has no empty\n // duration, so fall back to P0D.\n if (parts.length === 1) parts.push('0D');\n return parts.join('');\n}\n\n// Parses a duration-format string (the tokenized format formatDuration\n// accepts) into a DurationFields bag. Inverse of formatDuration().\nexport function parseDuration(input: string, formatStr: string, options: { locale?: string } = {}): DurationFields {\n // Build a capturing regex from the format string. Each token\n // captures a number; literals are matched literally.\n const tokens = tokenizeDurationFormat(formatStr);\n const fields: DurationFields = {};\n const groupSpecs: Array<{ unit: keyof DurationFields; form: 'numeric' | 'short' | 'long' }> = [];\n let regex = '';\n for (const tok of tokens) {\n if (tok.kind === 'literal') {\n regex += tok.value.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n } else {\n const unit = tok.unit!;\n const form = tok.value.length === 1 ? 'numeric' : tok.value.length === 2 ? 'short' : 'long';\n // Map token letter to DurationFields key.\n /* c8 ignore start @preserve -- the final `: null` branch, and the\n `if (key === null)` throw it feeds, are both unreachable: tok.unit\n is always one of the 8 letters in DURATION_TOKEN_STRINGS (the same\n source set this ternary checks against), so `null` can't be\n produced by any token the tokenizer emits. Kept as a guard against\n a future letter being added to one set without the other. */\n const key: keyof DurationFields | null = (\n unit === 'y' ? 'years'\n : unit === 'o' ? 'months'\n : unit === 'w' ? 'weeks'\n : unit === 'd' ? 'days'\n : unit === 'h' ? 'hours'\n : unit === 'm' ? 'minutes'\n : unit === 's' ? 'seconds'\n : unit === 'S' ? 'milliseconds'\n : null\n );\n if (key === null) {\n throw new FormatSyntaxError({ format: formatStr, token: tok.value, reason: 'unknown duration token' });\n }\n /* c8 ignore stop @preserve */\n // Numeric form: capture digits (and optional sign).\n // Short/long form: capture digits followed by optional unit text.\n // We'll handle unit-text stripping after the match.\n if (form === 'numeric') {\n regex += '(-?\\\\d+)';\n } else {\n // Short form like \"2y\" or \"2yrs\"; long form like \"2 years\" or \"2 year\".\n // Capture the number; unit text is matched but not captured.\n regex += '(\\\\d+)\\\\s*\\\\w*';\n }\n groupSpecs.push({ unit: key, form });\n }\n }\n const re = new RegExp(`^${regex}$`, 'u');\n const m = re.exec(input);\n if (!m) {\n throw new FormatSyntaxError({ input, format: formatStr, reason: 'does not match duration format' });\n }\n groupSpecs.forEach((spec, i) => {\n const raw = m[i + 1]!;\n const value = Number(raw);\n if (!Number.isFinite(value)) {\n throw new InvalidDurationError({ input, reason: `token for ${spec.unit} matched \"${raw}\" but isn't a finite number` });\n }\n fields[spec.unit] = value;\n });\n return fields;\n}\n\n// Balances a duration's absolute fields (days/hours/minutes/seconds/ms/µs/ns)\n// so each carries its natural range and excess carries into the next\n// larger unit. Doesn't touch calendar-bound fields (years/months/weeks)\n// since those need a relativeTo to balance correctly.\nexport function balanceDuration(duration: DurationFields): DurationFields {\n const NS_PER: Array<[keyof DurationFields, bigint]> = [\n ['days', 86_400n * 1_000_000_000n],\n ['hours', 3_600n * 1_000_000_000n],\n ['minutes', 60n * 1_000_000_000n],\n ['seconds', 1_000_000_000n],\n ['milliseconds', 1_000_000n],\n ['microseconds', 1_000n],\n ['nanoseconds', 1n],\n ];\n // Sum everything from days down to ns.\n let totalNs = 0n;\n for (const [k, nsPer] of NS_PER) {\n const v = duration[k];\n if (typeof v === 'number') totalNs += BigInt(v) * nsPer;\n }\n // Re-distribute.\n const result: DurationFields = { ...duration };\n let remaining = totalNs;\n for (const [k, nsPer] of NS_PER) {\n const count = remaining / nsPer;\n remaining -= count * nsPer;\n result[k] = Number(count);\n }\n return result;\n}\n\n// Sums all absolute fields into a single number expressed in the\n// requested unit. Throws for calendar-bound target units (years/months/\n// weeks) since those need a relativeTo.\nexport function totalDuration(duration: DurationFields, unit: 'days' | 'hours' | 'minutes' | 'seconds' | 'milliseconds' | 'microseconds' | 'nanoseconds'): number {\n const NS_PER: Record<string, bigint> = {\n days: 86_400n * 1_000_000_000n,\n hours: 3_600n * 1_000_000_000n,\n minutes: 60n * 1_000_000_000n,\n seconds: 1_000_000_000n,\n milliseconds: 1_000_000n,\n microseconds: 1_000n,\n nanoseconds: 1n,\n };\n let totalNs = 0n;\n for (const k of Object.keys(NS_PER) as Array<keyof typeof NS_PER>) {\n const v = duration[k as keyof DurationFields];\n if (typeof v === 'number') totalNs += BigInt(v) * NS_PER[k]!;\n }\n const divisor = NS_PER[unit];\n if (!divisor) {\n throw new InvalidDurationError({ reason: `totalDuration() does not support unit \"${unit}\" (calendar-bound units need a relativeTo)` });\n }\n return Number(totalNs) / Number(divisor);\n}\n\n// Compares two durations by their total absolute length. Returns -1/0/1.\nexport function compareDuration(a: DurationFields, b: DurationFields): number {\n const aNs = totalDurationNs(a);\n const bNs = totalDurationNs(b);\n if (aNs < bNs) return -1;\n if (aNs > bNs) return 1;\n return 0;\n}\n\nfunction totalDurationNs(d: DurationFields): bigint {\n const NS_PER: Record<string, bigint> = {\n days: 86_400n * 1_000_000_000n,\n hours: 3_600n * 1_000_000_000n,\n minutes: 60n * 1_000_000_000n,\n seconds: 1_000_000_000n,\n milliseconds: 1_000_000n,\n microseconds: 1_000n,\n nanoseconds: 1n,\n };\n let total = 0n;\n for (const k of Object.keys(NS_PER)) {\n const v = d[k as keyof DurationFields];\n if (typeof v === 'number') total += BigInt(v) * NS_PER[k]!;\n }\n return total;\n}\n\n// Adds two durations field-by-field. Doesn't balance — call\n// balanceDuration() afterwards if you want a balanced result.\nexport function addDuration(a: DurationFields, b: DurationFields): DurationFields {\n const keys: Array<keyof DurationFields> = ['years', 'months', 'weeks', 'days', 'hours', 'minutes', 'seconds', 'milliseconds', 'microseconds', 'nanoseconds'];\n const result: DurationFields = {};\n for (const k of keys) {\n result[k] = (a[k] ?? 0) + (b[k] ?? 0);\n }\n return result;\n}\n\nexport function subtractDuration(a: DurationFields, b: DurationFields): DurationFields {\n return addDuration(a, negateDuration(b));\n}\n\nfunction negateDuration(d: DurationFields): DurationFields {\n const result: DurationFields = {};\n for (const k of Object.keys(d) as Array<keyof DurationFields>) {\n result[k] = -(d[k] ?? 0);\n }\n return result;\n}\n\n// Suppress unused-import warning. getTemporal is used by fromUnix*\n// helpers in serialization.ts, not here — but keeping the import\n// around ensures this module is consistent with the rest of the\n// library's pattern of importing temporalProvider for any code path\n// that might construct a Temporal value.\nvoid getTemporal;\n"],"mappings":";;;;;;;;;;;;;AAkBA,IAAM,cAA4C;AAAA,EAChD,KAAK;AAAA,EACL,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,aAAa;AACf;AAEA,SAAS,UAAU,IAAY,MAAoB,WAA2B;AAK5E,QAAM,OAAO,KAAK,IAAI,KAAK;AAC3B,QAAM,MAAM,KAAK,IAAI,EAAE;AACvB,QAAM,UAAU,MAAM;AACtB,MAAI;AACJ,UAAQ,MAAM;AAAA,IACZ,KAAK;AAAW,gBAAU,KAAK,MAAM,OAAO;AAAG;AAAA,IAC/C,KAAK;AAAS,gBAAU,KAAK,MAAM,OAAO;AAAG;AAAA,IAC7C,KAAK;AAAQ,gBAAU,KAAK,KAAK,OAAO;AAAG;AAAA,IAC3C,KAAK;AAAS,gBAAU,KAAK,MAAM,OAAO;AAAG;AAAA,EAC/C;AACA,SAAO,OAAO,UAAU;AAC1B;AAEA,SAAS,KAAK,GAA8B;AAI1C,QAAM,IAAI,EAAE,MAAO,IAAI,EAAE,OAAQ,IAAI,EAAE;AACvC,QAAM,KAAK,KAAK,IAAI,IAAI,IAAI;AAC5B,QAAM,MAAM,KAAK,OAAO,MAAM,IAAI,KAAK,KAAK,OAAO,GAAG;AACtD,QAAM,MAAM,KAAK,MAAM;AACvB,QAAM,KAAK,IAAI,IAAI,IAAI,IAAI,IAAI;AAC/B,QAAM,MAAM,KAAK,OAAO,MAAM,KAAK,KAAK,CAAC,IAAI,IAAI;AACjD,QAAM,MAAM,MAAM,MAAM,KAAK,MAAM,MAAM,CAAC,IAAI,KAAK,MAAM,MAAM,GAAG,IAAI;AACtE,QAAM,OAAO,MAAM,SAAS,MAAM;AAClC,SAAO,OAAO,YAAY,OACrB,EAAE,QAAQ,KAAK,YAAY,QAC3B,EAAE,UAAU,KAAK,YAAY,UAC7B,EAAE,UAAU,KAAK,YAAY,UAC7B,EAAE,eAAe;AACxB;AAEA,SAAS,OAAO,IAAY,MAA4C;AAGtE,QAAM,aAAa,YAAY;AAC/B,QAAM,YAAY,KAAK,MAAM,KAAK,UAAU;AAC5C,MAAI,YAAY,KAAK,YAAY;AASjC,MAAI,YAAY,EAAG,cAAa;AAChC,QAAM,OAAO,KAAK,MAAM,YAAY,YAAY,IAAI;AACpD,QAAM,SAAS,KAAK,MAAO,YAAY,YAAY,OAAQ,YAAY,MAAM;AAC7E,QAAM,SAAS,KAAK,MAAO,YAAY,YAAY,SAAU,YAAY,MAAM;AAC/E,QAAM,cAAc,YAAY,YAAY;AAG5C,QAAM,IAAI,YAAY;AACtB,QAAM,MAAM,KAAK,OAAO,KAAK,IAAI,IAAI,IAAI,UAAU,MAAM;AACzD,QAAM,MAAM,IAAI,MAAM;AACtB,QAAM,MAAM,KAAK,OAAO,MAAM,KAAK,MAAM,MAAM,IAAI,IAAI,KAAK,MAAM,MAAM,KAAK,IAAI,KAAK,MAAM,MAAM,MAAM,KAAK,GAAG;AAChH,QAAM,KAAK,MAAM,MAAM;AACvB,QAAM,MAAM,OAAO,MAAM,MAAM,KAAK,MAAM,MAAM,CAAC,IAAI,KAAK,MAAM,MAAM,GAAG;AACzE,QAAM,KAAK,KAAK,OAAO,IAAI,MAAM,KAAK,GAAG;AACzC,QAAM,IAAI,MAAM,KAAK,OAAO,MAAM,KAAK,KAAK,CAAC,IAAI;AACjD,QAAM,IAAI,KAAK,KAAK,KAAK,IAAI,KAAK;AAClC,QAAM,OAAO,KAAK,IAAI,KAAK,IAAI;AAC/B,SAAO,EAAE,GAAG,MAAM,MAAM,OAAO,GAAG,KAAK,GAAG,MAAM,QAAQ,QAAQ,YAAY;AAC9E;AA8BO,SAAS,MAAM,OAAgB,SAA0C;AAC9E,QAAM,OAAO,gBAAgB,KAAK;AAClC,QAAM,OAAO,QAAQ,QAAQ;AAC7B,QAAM,aAAa,QAAQ,qBAAqB,KAAK,YAAY,QAAQ,IAAI;AAC7E,MAAI,aAAa,GAAG;AAClB,UAAM,IAAI,MAAM,oEAAoE,QAAQ,iBAAiB,IAAI;AAAA,EACnH;AACA,QAAM,KAAK,KAAK,IAAI;AACpB,QAAM,UAAU,UAAU,IAAI,MAAM,SAAS;AAC7C,SAAO,OAAO,SAAS,IAAI;AAC7B;AAEO,SAAS,MAAM,OAAgB,MAAoB,oBAA4B,GAAsB;AAC1G,SAAO,MAAM,OAAO,EAAE,MAAM,MAAM,SAAS,kBAAkB,CAAC;AAChE;AAEO,SAAS,KAAK,OAAgB,MAAoB,oBAA4B,GAAsB;AACzG,SAAO,MAAM,OAAO,EAAE,MAAM,MAAM,QAAQ,kBAAkB,CAAC;AAC/D;AAEO,SAAS,SAAS,OAAgB,MAAoB,oBAA4B,GAAsB;AAC7G,SAAO,MAAM,OAAO,EAAE,MAAM,MAAM,SAAS,kBAAkB,CAAC;AAChE;AAuBA,IAAM,sBAAoD;AAAA,EACxD,OAAO;AAAA;AAAA,EACP,QAAQ;AAAA;AAAA,EACR,OAAO;AAAA;AAAA,EACP,MAAM,SAAU;AAAA,EAChB,OAAO,QAAS;AAAA,EAChB,SAAS,MAAM;AAAA,EACf,SAAS;AAAA,EACT,cAAc;AAAA,EACd,cAAc;AAAA,EACd,aAAa;AACf;AAQA,SAAS,gBAAgB,MAA6B;AACpD,SAAO,SAAS,WAAW,SAAS,YAAY,SAAS;AAC3D;AAEO,SAAS,cAAc,UAA0B,SAIrC;AACjB,QAAM,OAAO,QAAQ,QAAQ;AAC7B,QAAM,YAAY,OAAO,QAAQ,qBAAqB,CAAC;AACvD,MAAI,aAAa,IAAI;AACnB,UAAM,IAAI,MAAM,4EAA4E,QAAQ,iBAAiB,IAAI;AAAA,EAC3H;AACA,MAAI,gBAAgB,QAAQ,IAAI,GAAG;AACjC,UAAM,IAAI;AAAA,MACR,qCAAqC,QAAQ,IAAI;AAAA,IAEnD;AAAA,EACF;AAGA,MAAI,UAAU;AACd,aAAW,KAAK,CAAC,QAAQ,SAAS,WAAW,WAAW,gBAAgB,gBAAgB,aAAa,GAAqB;AACxH,UAAM,IAAI,SAAS,CAAC,KAAK;AACzB,eAAW,OAAO,CAAC,IAAI,oBAAoB,CAAC;AAAA,EAC9C;AAGA,QAAM,SAAS,YAAY,oBAAoB,QAAQ,IAAI;AAC3D,QAAM,OAAO,UAAU,KAAK,CAAC,KAAK;AAClC,QAAM,MAAM,UAAU,KAAK,CAAC,UAAU;AACtC,QAAM,UAAU,MAAM;AACtB,QAAM,YAAY,MAAM;AACxB,MAAI;AACJ,UAAQ,MAAM;AAAA,IACZ,KAAK;AAKH,gBAAU,YAAY,MAAM,SAAS,UAAU,KAAK;AACpD;AAAA,IACF,KAAK;AAAS,gBAAU;AAAS;AAAA,IACjC,KAAK;AAAQ,gBAAU,YAAY,KAAK,UAAU,KAAK;AAAS;AAAA,IAChE,KAAK;AAAS,gBAAU;AAAS;AAAA,EACnC;AACA,QAAM,aAAa,OAAO,UAAU;AAKpC,QAAM,SAAyB,EAAE,GAAG,SAAS;AAC7C,MAAI,YAAY;AAEhB,aAAW,KAAK,CAAC,QAAQ,SAAS,WAAW,WAAW,gBAAgB,gBAAgB,aAAa,GAAqB;AACxH,QAAI,MAAM,QAAQ,QAAQ,aAAa,GAAG,QAAQ,IAAI,KAAK,MAAM,QAAQ,MAAM;AAAA,IAG/E;AACA,WAAO,CAAC,IAAI;AAAA,EACd;AAIA,QAAM,eAA+B,CAAC,QAAQ,SAAS,WAAW,WAAW,gBAAgB,gBAAgB,aAAa;AAC1H,QAAM,WAAW,aAAa,QAAQ,QAAQ,IAAI;AAGlD,WAAS,IAAI,GAAG,KAAK,UAAU,KAAK;AAClC,UAAM,IAAI,aAAa,CAAC;AACxB,UAAM,SAAS,oBAAoB,CAAC;AAOpC,QAAI,WAAW,GAAI;AACnB,UAAM,QAAQ,YAAY;AAC1B,iBAAa,QAAQ;AACrB,QAAI,UAAU,IAAI;AAChB,aAAO,CAAC,IAAI,OAAO,KAAK;AAAA,IAC1B;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,aAAa,IAAkB,IAA2B;AAGjE,SAAO;AACT;;;ACzOA,IAAM,iBAA4C;AAAA,EAChD,GAAG,EAAE,cAAc,QAAQ,YAAY,SAAS,eAAe,MAAM,aAAa,OAAO,OAAO,SAAS,UAAU,OAAO;AAAA,EAC1H,GAAG,EAAE,cAAc,SAAS,YAAY,UAAU,eAAe,MAAM,aAAa,OAAO,OAAO,UAAU,UAAU,QAAQ;AAAA,EAC9H,GAAG,EAAE,cAAc,QAAQ,YAAY,SAAS,eAAe,MAAM,aAAa,OAAO,OAAO,SAAS,UAAU,OAAO;AAAA,EAC1H,GAAG,EAAE,cAAc,OAAO,YAAY,QAAQ,eAAe,KAAK,aAAa,KAAK,OAAO,QAAQ,UAAU,MAAM;AAAA,EACnH,GAAG,EAAE,cAAc,QAAQ,YAAY,SAAS,eAAe,KAAK,aAAa,KAAK,OAAO,SAAS,UAAU,OAAO;AAAA,EACvH,GAAG,EAAE,cAAc,UAAU,YAAY,WAAW,eAAe,KAAK,aAAa,KAAK,OAAO,WAAW,UAAU,SAAS;AAAA,EAC/H,GAAG,EAAE,cAAc,UAAU,YAAY,WAAW,eAAe,KAAK,aAAa,KAAK,OAAO,WAAW,UAAU,SAAS;AAAA,EAC/H,GAAG,EAAE,cAAc,eAAe,YAAY,gBAAgB,eAAe,MAAM,aAAa,MAAM,OAAO,gBAAgB,UAAU,cAAc;AACvJ;AAOA,IAAM,yBAAyB,OAAO,KAAK,cAAc,EACtD,QAAQ,CAAC,WAAW,CAAC,SAAS,SAAS,QAAQ,SAAS,QAAQ,MAAM,CAAC,EACvE,KAAK,CAAC,GAAG,MAAM,EAAE,SAAS,EAAE,MAAM;AAUrC,SAAS,iBAAiB,QAAyB;AACjD,QAAM,SAAkB,CAAC;AACzB,MAAI,IAAI;AACR,SAAO,IAAI,OAAO,QAAQ;AACxB,UAAM,KAAK,OAAO,CAAC;AACnB,QAAI,OAAO,KAAK;AACd,UAAI,OAAO,IAAI,CAAC,MAAM,KAAK;AACzB,sBAAc,QAAQ,GAAG;AACzB,aAAK;AACL;AAAA,MACF;AACA,UAAI,IAAI,IAAI;AACZ,UAAI,UAAU;AACd,UAAI,SAAS;AACb,aAAO,IAAI,OAAO,QAAQ;AACxB,YAAI,OAAO,CAAC,MAAM,KAAK;AACrB,cAAI,OAAO,IAAI,CAAC,MAAM,KAAK;AACzB,uBAAW;AACX,iBAAK;AACL;AAAA,UACF;AACA,mBAAS;AACT,eAAK;AACL;AAAA,QACF;AACA,mBAAW,OAAO,CAAC;AACnB,aAAK;AAAA,MACP;AACA,UAAI,CAAC,QAAQ;AACX,cAAM,IAAI,MAAM,+DAA+D,MAAM,GAAG;AAAA,MAC1F;AACA,oBAAc,QAAQ,OAAO;AAC7B,UAAI;AACJ;AAAA,IACF;AACA,UAAM,QAAQ,uBAAuB,KAAK,CAAC,QAAQ,OAAO,WAAW,KAAK,CAAC,CAAC;AAC5E,QAAI,OAAO;AAET,YAAM,OAAO,MAAM,CAAC;AACpB,YAAM,OAAO,MAAM,WAAW,IAAI,YAAY,MAAM,WAAW,IAAI,UAAU;AAC7E,aAAO,KAAK,EAAE,MAAM,SAAS,OAAO,OAAO,MAAM,KAAK,CAAC;AACvD,WAAK,MAAM;AACX;AAAA,IACF;AACA,kBAAc,QAAQ,EAAE;AACxB,SAAK;AAAA,EACP;AACA,SAAO;AACT;AAEA,SAAS,cAAc,QAAiB,OAAqB;AAC3D,QAAM,OAAO,OAAO,OAAO,SAAS,CAAC;AACrC,MAAI,QAAQ,KAAK,SAAS,WAAW;AACnC,SAAK,SAAS;AAAA,EAChB,OAAO;AACL,WAAO,KAAK,EAAE,MAAM,WAAW,MAAM,CAAC;AAAA,EACxC;AACF;AAOA,SAAS,SAAS,UAAmC,OAAwB;AAC3E,QAAM,QAAQ,SAAS,KAAK;AAC5B,MAAI,UAAU,UAAa,UAAU,KAAM,QAAO;AAClD,MAAI,OAAO,UAAU,YAAY,OAAO,SAAS,KAAK,EAAG,QAAO;AAIhE,QAAM,UAAU,OAAO,KAAK;AAC5B,MAAI,CAAC,OAAO,SAAS,OAAO,GAAG;AAC7B,UAAM,IAAI;AAAA,MACR,iCAAiC,KAAK,iCAAiC,OAAO,KAAK,CAAC;AAAA,IACtF;AAAA,EACF;AACA,SAAO;AACT;AAQA,IAAM,qBAAqB,oBAAI,IAA+B;AAC9D,IAAM,gCAAgC;AAEtC,SAAS,iBAAiB,QAAgB,UAAkB,aAAkD;AAC5G,QAAM,MAAM,GAAG,kBAAkB,MAAM,CAAC,IAAI,QAAQ,IAAI,WAAW;AACnE,QAAM,SAAS,mBAAmB,IAAI,GAAG;AACzC,MAAI,OAAQ,QAAO;AACnB,MAAI,mBAAmB,QAAQ,+BAA+B;AAC5D,UAAM,YAAY,mBAAmB,KAAK,EAAE,KAAK,EAAE;AACnD,QAAI,cAAc,OAAW,oBAAmB,OAAO,SAAS;AAAA,EAClE;AACA,QAAM,YAAY,IAAI,KAAK,aAAa,QAAQ;AAAA,IAC9C,OAAO;AAAA,IACP,MAAM;AAAA,IACN;AAAA,EACF,CAAC;AACD,qBAAmB,IAAI,KAAK,SAAS;AACrC,SAAO;AACT;AAqCO,SAAS,eACd,UACA,WACA,UAAiC,CAAC,GAC1B;AACR,MAAI,UAAU,SAAS,mBAAmB;AACxC,UAAM,IAAI;AAAA,MACR,kEAAkE,iBAAiB,oBAC3E,UAAU,MAAM;AAAA,IAC1B;AAAA,EACF;AAEA,QAAM,aAAa,QAAQ,mBAAmB;AAO9C,QAAM,SAAS,QAAQ;AACvB,QAAM,UAAU,WAAW;AAE3B,QAAM,SAAS,iBAAiB,SAAS;AACzC,MAAI,SAAS;AAEb,aAAW,SAAS,QAAQ;AAC1B,QAAI,MAAM,SAAS,WAAW;AAC5B,gBAAU,MAAM;AAChB;AAAA,IACF;AAEA,UAAM,OAAO,eAAe,MAAM,IAAI;AAOtC,QAAI,CAAC,MAAM;AACT,YAAM,IAAI,MAAM,yCAAyC,MAAM,KAAK,GAAG;AAAA,IACzE;AAGA,UAAM,QAAQ,SAAS,UAAU,KAAK,KAAK;AAC3C,QAAI,UAAU,KAAK,CAAC,YAAY;AAM9B;AAAA,IACF;AAEA,QAAI,MAAM,SAAS,WAAW;AAC5B,gBAAU,OAAO,KAAK;AACtB;AAAA,IACF;AAEA,QAAI,SAAS;AAKX,YAAM,cAAc,MAAM,SAAS,UAAU,UAAU;AACvD,YAAM,YAAY,iBAAiB,QAAS,KAAK,UAAU,WAAW;AACtE,gBAAU,UAAU,OAAO,KAAK;AAChC;AAAA,IACF;AAMA,QAAI,MAAM,SAAS,SAAS;AAC1B,gBAAU,SAAS,UAAU,KAAK,UAAU,KAAK,KAAK,gBAAgB,KAAK;AAAA,IAC7E,OAAO;AACL,gBAAU,QAAQ,OAAO,UAAU,KAAK,UAAU,KAAK,KAAK,eAAe,KAAK;AAAA,IAClF;AAAA,EACF;AAEA,SAAO;AACT;;;AChQO,SAAS,sBACd,UACA,WACA,UAAgD,CAAC,GACjC;AAMhB,QAAM,OAAO,eAAe,UAAU,WAAW,OAAO;AAKxD,QAAM,SAAS,uBAAuB,SAAS;AAC/C,QAAM,QAAwB,CAAC;AAC/B,MAAI,MAAM;AACV,aAAW,OAAO,QAAQ;AACxB,QAAI,IAAI,SAAS,WAAW;AAG1B,YAAM,MAAM,IAAI;AAChB,UAAI,KAAK,MAAM,KAAK,MAAM,IAAI,MAAM,MAAM,KAAK;AAC7C,cAAM,KAAK,EAAE,MAAM,WAAW,OAAO,IAAI,CAAC;AAC1C,eAAO,IAAI;AAAA,QACb;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAOA,OAAO;AAAA,MAGP;AAAA,MACA;AAAA,IACF,OAAO;AAGL,YAAM,mBAAmB,qBAAqB,QAAQ,KAAK,MAAM,GAAG;AACpE,YAAM,QAAQ,KAAK,MAAM,KAAK,gBAAgB;AAC9C,YAAM,KAAK,EAAE,MAAM,SAAS,OAAO,OAAO,IAAI,MAAM,CAAC;AACrD,YAAM;AAAA,IACR;AAAA,EACF;AACA,SAAO;AACT;AAIA,SAAS,uBAAuB,WAAqD;AACnF,QAAMA,0BAAyB,CAAC,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,GAAG,EACnE,QAAQ,CAAC,WAAW,CAAC,SAAS,SAAS,QAAQ,SAAS,QAAQ,MAAM,CAAC,EACvE,KAAK,CAAC,GAAG,MAAM,EAAE,SAAS,EAAE,MAAM;AACrC,QAAM,SAA2C,CAAC;AAClD,MAAI,IAAI;AACR,SAAO,IAAI,UAAU,QAAQ;AAC3B,UAAM,KAAK,UAAU,CAAC;AACtB,QAAI,OAAO,KAAK;AACd,UAAI,UAAU,IAAI,CAAC,MAAM,KAAK;AAC5B,QAAAC,eAAc,QAAQ,GAAG;AACzB,aAAK;AACL;AAAA,MACF;AACA,UAAI,IAAI,IAAI;AACZ,UAAI,UAAU;AACd,UAAI,SAAS;AACb,aAAO,IAAI,UAAU,QAAQ;AAC3B,YAAI,UAAU,CAAC,MAAM,KAAK;AACxB,cAAI,UAAU,IAAI,CAAC,MAAM,KAAK;AAC5B,uBAAW;AACX,iBAAK;AACL;AAAA,UACF;AACA,mBAAS;AACT,eAAK;AACL;AAAA,QACF;AACA,mBAAW,UAAU,CAAC;AACtB,aAAK;AAAA,MACP;AACA,UAAI,CAAC,OAAQ,OAAM,IAAI,kBAAkB,EAAE,QAAQ,WAAW,QAAQ,qBAAqB,CAAC;AAC5F,MAAAA,eAAc,QAAQ,OAAO;AAC7B,UAAI;AACJ;AAAA,IACF;AACA,UAAM,QAAQD,wBAAuB,KAAK,CAAC,QAAQ,UAAU,WAAW,KAAK,CAAC,CAAC;AAC/E,QAAI,OAAO;AACT,aAAO,KAAK,EAAE,MAAM,SAAS,OAAO,OAAO,MAAM,MAAM,CAAC,EAAE,CAAC;AAC3D,WAAK,MAAM;AACX;AAAA,IACF;AACA,IAAAC,eAAc,QAAQ,EAAE;AACxB,SAAK;AAAA,EACP;AACA,SAAO;AACT;AAEA,SAASA,eAAc,QAA0C,OAAqB;AACpF,QAAM,OAAO,OAAO,OAAO,SAAS,CAAC;AACrC,MAAI,QAAQ,KAAK,SAAS,WAAW;AACnC,SAAK,SAAS;AAAA,EAChB,OAAO;AACL,WAAO,KAAK,EAAE,MAAM,WAAW,MAAM,CAAC;AAAA,EACxC;AACF;AAEA,SAAS,qBACP,QACA,aACA,MACA,KACQ;AAWR,QAAM,MAAM,OAAO,QAAQ,WAAW;AACtC,WAAS,IAAI,MAAM,GAAG,IAAI,OAAO,QAAQ,KAAK;AAC5C,UAAM,IAAI,OAAO,CAAC;AAClB,QAAI,EAAE,SAAS,WAAW;AACxB,YAAM,QAAQ,KAAK,QAAQ,EAAE,OAAO,GAAG;AACvC,UAAI,SAAS,EAAG,QAAO;AAAA,IACzB;AAAA,EACF;AACA,SAAO,KAAK;AACd;AAKO,SAAS,iBAAiB,OAA+B;AAG9D,QAAM,KAAK;AACX,QAAM,IAAI,GAAG,KAAK,KAAK;AACvB,MAAI,CAAC,GAAG;AACN,UAAM,IAAI,qBAAqB,EAAE,OAAO,QAAQ,4EAA4E,CAAC;AAAA,EAC/H;AAEA,MAAI,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,MAAM,MAAS,GAAG;AAC5C,UAAM,IAAI,qBAAqB,EAAE,OAAO,QAAQ,uDAAuD,CAAC;AAAA,EAC1G;AACA,QAAM,QAAQ,CAAC,MAAkC,MAAM,SAAY,IAAI,OAAO,CAAC;AAC/E,SAAO;AAAA,IACL,OAAO,MAAM,EAAE,CAAC,CAAC;AAAA,IACjB,QAAQ,MAAM,EAAE,CAAC,CAAC;AAAA,IAClB,OAAO,MAAM,EAAE,CAAC,CAAC;AAAA,IACjB,MAAM,MAAM,EAAE,CAAC,CAAC;AAAA,IAChB,OAAO,MAAM,EAAE,CAAC,CAAC;AAAA,IACjB,SAAS,MAAM,EAAE,CAAC,CAAC;AAAA,IACnB,SAAS,MAAM,EAAE,CAAC,CAAC;AAAA,EACrB;AACF;AAEO,SAAS,kBAAkB,UAAkC;AAClE,QAAM,QAAkB,CAAC,GAAG;AAC5B,QAAM,QAAQ,SAAS,SAAS;AAChC,QAAM,SAAS,SAAS,UAAU;AAClC,QAAM,QAAQ,SAAS,SAAS;AAChC,QAAM,OAAO,SAAS,QAAQ;AAC9B,QAAM,QAAQ,SAAS,SAAS;AAChC,QAAM,UAAU,SAAS,WAAW;AACpC,QAAM,UAAU,SAAS,WAAW;AACpC,MAAI,MAAO,OAAM,KAAK,GAAG,KAAK,GAAG;AACjC,MAAI,OAAQ,OAAM,KAAK,GAAG,MAAM,GAAG;AACnC,MAAI,MAAO,OAAM,KAAK,GAAG,KAAK,GAAG;AACjC,MAAI,KAAM,OAAM,KAAK,GAAG,IAAI,GAAG;AAC/B,MAAI,SAAS,WAAW,SAAS;AAC/B,UAAM,KAAK,GAAG;AACd,QAAI,MAAO,OAAM,KAAK,GAAG,KAAK,GAAG;AACjC,QAAI,QAAS,OAAM,KAAK,GAAG,OAAO,GAAG;AACrC,QAAI,QAAS,OAAM,KAAK,GAAG,OAAO,GAAG;AAAA,EACvC;AAGA,MAAI,MAAM,WAAW,EAAG,OAAM,KAAK,IAAI;AACvC,SAAO,MAAM,KAAK,EAAE;AACtB;AAIO,SAAS,cAAc,OAAe,WAAmB,UAA+B,CAAC,GAAmB;AAGjH,QAAM,SAAS,uBAAuB,SAAS;AAC/C,QAAM,SAAyB,CAAC;AAChC,QAAM,aAAwF,CAAC;AAC/F,MAAI,QAAQ;AACZ,aAAW,OAAO,QAAQ;AACxB,QAAI,IAAI,SAAS,WAAW;AAC1B,eAAS,IAAI,MAAM,QAAQ,uBAAuB,MAAM;AAAA,IAC1D,OAAO;AACL,YAAM,OAAO,IAAI;AACjB,YAAM,OAAO,IAAI,MAAM,WAAW,IAAI,YAAY,IAAI,MAAM,WAAW,IAAI,UAAU;AAAA,MAErF;AAAA;AAAA;AAAA;AAAA;AAAA;AAMA,YAAM,MACJ,SAAS,MAAM,UACb,SAAS,MAAM,WACf,SAAS,MAAM,UACf,SAAS,MAAM,SACf,SAAS,MAAM,UACf,SAAS,MAAM,YACf,SAAS,MAAM,YACf,SAAS,MAAM,iBACf;AAEJ,UAAI,QAAQ,MAAM;AAChB,cAAM,IAAI,kBAAkB,EAAE,QAAQ,WAAW,OAAO,IAAI,OAAO,QAAQ,yBAAyB,CAAC;AAAA,MACvG;AAAA,MACA;AAIA,UAAI,SAAS,WAAW;AACtB,iBAAS;AAAA,MACX,OAAO;AAGL,iBAAS;AAAA,MACX;AACA,iBAAW,KAAK,EAAE,MAAM,KAAK,KAAK,CAAC;AAAA,IACrC;AAAA,EACF;AACA,QAAM,KAAK,IAAI,OAAO,IAAI,KAAK,KAAK,GAAG;AACvC,QAAM,IAAI,GAAG,KAAK,KAAK;AACvB,MAAI,CAAC,GAAG;AACN,UAAM,IAAI,kBAAkB,EAAE,OAAO,QAAQ,WAAW,QAAQ,iCAAiC,CAAC;AAAA,EACpG;AACA,aAAW,QAAQ,CAAC,MAAM,MAAM;AAC9B,UAAM,MAAM,EAAE,IAAI,CAAC;AACnB,UAAM,QAAQ,OAAO,GAAG;AACxB,QAAI,CAAC,OAAO,SAAS,KAAK,GAAG;AAC3B,YAAM,IAAI,qBAAqB,EAAE,OAAO,QAAQ,aAAa,KAAK,IAAI,aAAa,GAAG,8BAA8B,CAAC;AAAA,IACvH;AACA,WAAO,KAAK,IAAI,IAAI;AAAA,EACtB,CAAC;AACD,SAAO;AACT;AAMO,SAAS,gBAAgB,UAA0C;AACxE,QAAM,SAAgD;AAAA,IACpD,CAAC,QAAQ,SAAU,WAAc;AAAA,IACjC,CAAC,SAAS,QAAS,WAAc;AAAA,IACjC,CAAC,WAAW,MAAM,WAAc;AAAA,IAChC,CAAC,WAAW,WAAc;AAAA,IAC1B,CAAC,gBAAgB,QAAU;AAAA,IAC3B,CAAC,gBAAgB,KAAM;AAAA,IACvB,CAAC,eAAe,EAAE;AAAA,EACpB;AAEA,MAAI,UAAU;AACd,aAAW,CAAC,GAAG,KAAK,KAAK,QAAQ;AAC/B,UAAM,IAAI,SAAS,CAAC;AACpB,QAAI,OAAO,MAAM,SAAU,YAAW,OAAO,CAAC,IAAI;AAAA,EACpD;AAEA,QAAM,SAAyB,EAAE,GAAG,SAAS;AAC7C,MAAI,YAAY;AAChB,aAAW,CAAC,GAAG,KAAK,KAAK,QAAQ;AAC/B,UAAM,QAAQ,YAAY;AAC1B,iBAAa,QAAQ;AACrB,WAAO,CAAC,IAAI,OAAO,KAAK;AAAA,EAC1B;AACA,SAAO;AACT;AAKO,SAAS,cAAc,UAA0B,MAA0G;AAChK,QAAM,SAAiC;AAAA,IACrC,MAAM,SAAU;AAAA,IAChB,OAAO,QAAS;AAAA,IAChB,SAAS,MAAM;AAAA,IACf,SAAS;AAAA,IACT,cAAc;AAAA,IACd,cAAc;AAAA,IACd,aAAa;AAAA,EACf;AACA,MAAI,UAAU;AACd,aAAW,KAAK,OAAO,KAAK,MAAM,GAAiC;AACjE,UAAM,IAAI,SAAS,CAAyB;AAC5C,QAAI,OAAO,MAAM,SAAU,YAAW,OAAO,CAAC,IAAI,OAAO,CAAC;AAAA,EAC5D;AACA,QAAM,UAAU,OAAO,IAAI;AAC3B,MAAI,CAAC,SAAS;AACZ,UAAM,IAAI,qBAAqB,EAAE,QAAQ,0CAA0C,IAAI,6CAA6C,CAAC;AAAA,EACvI;AACA,SAAO,OAAO,OAAO,IAAI,OAAO,OAAO;AACzC;AAGO,SAAS,gBAAgB,GAAmB,GAA2B;AAC5E,QAAM,MAAM,gBAAgB,CAAC;AAC7B,QAAM,MAAM,gBAAgB,CAAC;AAC7B,MAAI,MAAM,IAAK,QAAO;AACtB,MAAI,MAAM,IAAK,QAAO;AACtB,SAAO;AACT;AAEA,SAAS,gBAAgB,GAA2B;AAClD,QAAM,SAAiC;AAAA,IACrC,MAAM,SAAU;AAAA,IAChB,OAAO,QAAS;AAAA,IAChB,SAAS,MAAM;AAAA,IACf,SAAS;AAAA,IACT,cAAc;AAAA,IACd,cAAc;AAAA,IACd,aAAa;AAAA,EACf;AACA,MAAI,QAAQ;AACZ,aAAW,KAAK,OAAO,KAAK,MAAM,GAAG;AACnC,UAAM,IAAI,EAAE,CAAyB;AACrC,QAAI,OAAO,MAAM,SAAU,UAAS,OAAO,CAAC,IAAI,OAAO,CAAC;AAAA,EAC1D;AACA,SAAO;AACT;AAIO,SAAS,YAAY,GAAmB,GAAmC;AAChF,QAAM,OAAoC,CAAC,SAAS,UAAU,SAAS,QAAQ,SAAS,WAAW,WAAW,gBAAgB,gBAAgB,aAAa;AAC3J,QAAM,SAAyB,CAAC;AAChC,aAAW,KAAK,MAAM;AACpB,WAAO,CAAC,KAAK,EAAE,CAAC,KAAK,MAAM,EAAE,CAAC,KAAK;AAAA,EACrC;AACA,SAAO;AACT;AAEO,SAAS,iBAAiB,GAAmB,GAAmC;AACrF,SAAO,YAAY,GAAG,eAAe,CAAC,CAAC;AACzC;AAEA,SAAS,eAAe,GAAmC;AACzD,QAAM,SAAyB,CAAC;AAChC,aAAW,KAAK,OAAO,KAAK,CAAC,GAAkC;AAC7D,WAAO,CAAC,IAAI,EAAE,EAAE,CAAC,KAAK;AAAA,EACxB;AACA,SAAO;AACT;","names":["DURATION_TOKEN_STRINGS","appendLiteral"]}
|
package/dist/chunk-KDYK72Z2.js
DELETED
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
InvalidLocaleError,
|
|
3
|
-
getLocaleVocab,
|
|
4
|
-
registerLocaleVocab
|
|
5
|
-
} from "./chunk-O6GVMBLW.js";
|
|
6
|
-
|
|
7
|
-
// src/localeRegistry.ts
|
|
8
|
-
var extendedVocabs = /* @__PURE__ */ new Map();
|
|
9
|
-
var MAX_LOCALE_TAG_LENGTH = 256;
|
|
10
|
-
var MAX_VOCAB_STRING_LENGTH = 256;
|
|
11
|
-
function canonicalKey(locale) {
|
|
12
|
-
try {
|
|
13
|
-
return new Intl.Locale(locale.replace(/_/g, "-")).toString().toLowerCase();
|
|
14
|
-
} catch {
|
|
15
|
-
return locale;
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
function registerLocale(locale, vocab) {
|
|
19
|
-
if (typeof locale !== "string" || locale.length === 0) {
|
|
20
|
-
throw new InvalidLocaleError({ actual: String(locale), reason: "locale string must be non-empty" });
|
|
21
|
-
}
|
|
22
|
-
if (locale.length > MAX_LOCALE_TAG_LENGTH) {
|
|
23
|
-
throw new InvalidLocaleError({ actual: String(locale.length), reason: `locale tag must be at most ${MAX_LOCALE_TAG_LENGTH} characters` });
|
|
24
|
-
}
|
|
25
|
-
registerLocaleVocab(locale, vocab);
|
|
26
|
-
validateExtended(vocab, locale);
|
|
27
|
-
const key = canonicalKey(locale);
|
|
28
|
-
extendedVocabs.set(key, { ...vocab });
|
|
29
|
-
}
|
|
30
|
-
function validateExtended(vocab, locale) {
|
|
31
|
-
const checkArray = (val, key, length) => {
|
|
32
|
-
if (val === void 0) return;
|
|
33
|
-
if (!Array.isArray(val)) {
|
|
34
|
-
throw new InvalidLocaleError({ actual: String(val), reason: `locale "${locale}": "${key}" must be an array` });
|
|
35
|
-
}
|
|
36
|
-
if (val.length !== length) {
|
|
37
|
-
throw new InvalidLocaleError({ actual: String(val.length), reason: `locale "${locale}": "${key}" must have ${length} entries (got ${val.length})` });
|
|
38
|
-
}
|
|
39
|
-
for (let i = 0; i < val.length; i++) {
|
|
40
|
-
if (typeof val[i] !== "string" || val[i].length === 0) {
|
|
41
|
-
throw new InvalidLocaleError({ actual: String(val[i]), reason: `locale "${locale}": "${key}[${i}]" must be a non-empty string` });
|
|
42
|
-
}
|
|
43
|
-
if (val[i].length > MAX_VOCAB_STRING_LENGTH) {
|
|
44
|
-
throw new InvalidLocaleError({ actual: String(val[i].length), reason: `locale "${locale}": "${key}[${i}]" must be at most ${MAX_VOCAB_STRING_LENGTH} characters` });
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
};
|
|
48
|
-
checkArray(vocab.quartersLong, "quartersLong", 4);
|
|
49
|
-
checkArray(vocab.quartersShort, "quartersShort", 4);
|
|
50
|
-
checkArray(vocab.erasLong, "erasLong", 2);
|
|
51
|
-
checkArray(vocab.erasShort, "erasShort", 2);
|
|
52
|
-
checkArray(vocab.ordinals, "ordinals", 4);
|
|
53
|
-
}
|
|
54
|
-
function getLocale(locale) {
|
|
55
|
-
const key = canonicalKey(locale);
|
|
56
|
-
const ext = extendedVocabs.get(key);
|
|
57
|
-
if (ext) return ext;
|
|
58
|
-
try {
|
|
59
|
-
const base = getLocaleVocab(locale);
|
|
60
|
-
return { ...base };
|
|
61
|
-
} catch {
|
|
62
|
-
return void 0;
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
function hasLocale(locale) {
|
|
66
|
-
const key = canonicalKey(locale);
|
|
67
|
-
return extendedVocabs.has(key);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
export {
|
|
71
|
-
registerLocale,
|
|
72
|
-
getLocale,
|
|
73
|
-
hasLocale
|
|
74
|
-
};
|
|
75
|
-
//# sourceMappingURL=chunk-KDYK72Z2.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/localeRegistry.ts"],"sourcesContent":["// Locale registration and lookup. Extends the\n// existing registerLocaleVocab() surface in localeVocab.ts with\n// registerLocale() / getLocale() / hasLocale() and a richer LocaleVocab\n// that covers quarters, eras, ordinals, duration units, and relative-\n// time language.\n//\n// The existing localeVocab.ts stays as the implementation for months/\n// weekdays/day-periods (those are what tokens.ts's MMMM/MMM/EEEE/EEE/a\n// tokens read). This module wraps it and adds the extended fields.\n\nimport { registerLocaleVocab, getLocaleVocab, type LocaleVocab as BaseLocaleVocab } from './localeVocab.js';\nimport { InvalidLocaleError } from './errors.js';\n\nexport interface ExtendedLocaleVocab extends BaseLocaleVocab {\n // Quarters: Q1..Q4 long form (\"First quarter\", etc.). Default falls\n // back to \"Q1\"..\"Q4\" if not registered.\n quartersLong?: string[];\n quartersShort?: string[];\n // Eras: [\"BC\", \"AD\"] for gregory, etc. Default [\"BC\", \"AD\"].\n erasLong?: string[];\n erasShort?: string[];\n // Ordinal suffix rules: [\"st\", \"nd\", \"rd\", \"th\"] for English. Default\n // empty (the existing 'do' token has English-only ordinal logic baked\n // into tokens.ts; this field lets a custom locale override it).\n ordinals?: string[];\n // Duration unit names: long singular/plural for each unit. Used by\n // formatDuration's locale-aware path when Intl.NumberFormat isn't\n // desired (e.g. for a made-up locale Intl doesn't know about).\n durationUnits?: {\n years: [string, string];\n months: [string, string];\n weeks: [string, string];\n days: [string, string];\n hours: [string, string];\n minutes: [string, string];\n seconds: [string, string];\n milliseconds: [string, string];\n };\n // Relative-time language: words for \"ago\" / \"in\" / \"now\". Used by\n // formatRelative()'s locale-aware path when Intl.RelativeTimeFormat\n // isn't available or doesn't cover a registered locale.\n relativeTime?: {\n past: string;\n future: string;\n now: string;\n };\n}\n\nconst extendedVocabs = new Map<string, ExtendedLocaleVocab>();\nconst MAX_LOCALE_TAG_LENGTH = 256;\nconst MAX_VOCAB_STRING_LENGTH = 256;\n\nfunction canonicalKey(locale: string): string {\n try {\n return new Intl.Locale(locale.replace(/_/g, '-')).toString().toLowerCase();\n } catch {\n return locale;\n }\n}\n\nexport function registerLocale(locale: string, vocab: ExtendedLocaleVocab): void {\n if (typeof locale !== 'string' || locale.length === 0) {\n throw new InvalidLocaleError({ actual: String(locale), reason: 'locale string must be non-empty' });\n }\n if (locale.length > MAX_LOCALE_TAG_LENGTH) {\n throw new InvalidLocaleError({ actual: String(locale.length), reason: `locale tag must be at most ${MAX_LOCALE_TAG_LENGTH} characters` });\n }\n // Validate the base vocab fields (months/weekdays/day-periods) via\n // the existing registerLocaleVocab — it has the strict-shape checks.\n registerLocaleVocab(locale, vocab);\n // Extended fields are optional; only validate the ones that are present.\n validateExtended(vocab, locale);\n const key = canonicalKey(locale);\n extendedVocabs.set(key, { ...vocab });\n}\n\nfunction validateExtended(vocab: ExtendedLocaleVocab, locale: string): void {\n const checkArray = (val: unknown, key: string, length: number) => {\n if (val === undefined) return;\n if (!Array.isArray(val)) {\n throw new InvalidLocaleError({ actual: String(val), reason: `locale \"${locale}\": \"${key}\" must be an array` });\n }\n if (val.length !== length) {\n throw new InvalidLocaleError({ actual: String(val.length), reason: `locale \"${locale}\": \"${key}\" must have ${length} entries (got ${val.length})` });\n }\n for (let i = 0; i < val.length; i++) {\n if (typeof val[i] !== 'string' || val[i]!.length === 0) {\n throw new InvalidLocaleError({ actual: String(val[i]), reason: `locale \"${locale}\": \"${key}[${i}]\" must be a non-empty string` });\n }\n if (val[i]!.length > MAX_VOCAB_STRING_LENGTH) {\n throw new InvalidLocaleError({ actual: String(val[i]!.length), reason: `locale \"${locale}\": \"${key}[${i}]\" must be at most ${MAX_VOCAB_STRING_LENGTH} characters` });\n }\n }\n };\n checkArray(vocab.quartersLong, 'quartersLong', 4);\n checkArray(vocab.quartersShort, 'quartersShort', 4);\n checkArray(vocab.erasLong, 'erasLong', 2);\n checkArray(vocab.erasShort, 'erasShort', 2);\n checkArray(vocab.ordinals, 'ordinals', 4);\n}\n\nexport function getLocale(locale: string): ExtendedLocaleVocab | undefined {\n // Returns the extended vocab for a locale, OR undefined (falls back\n // to Intl). Combines the base vocab (from localeVocab.ts, which\n // includes Intl defaults) with any extended fields registered here.\n const key = canonicalKey(locale);\n const ext = extendedVocabs.get(key);\n if (ext) return ext;\n // Fall back to base vocab (Intl-derived) without extended fields.\n try {\n const base = getLocaleVocab(locale);\n return { ...base };\n } catch {\n return undefined;\n }\n}\n\nexport function hasLocale(locale: string): boolean {\n const key = canonicalKey(locale);\n return extendedVocabs.has(key);\n}\n\n// Re-export the base vocab types for callers that want them.\nexport type { LocaleVocab } from './localeVocab.js';\n"],"mappings":";;;;;;;AAgDA,IAAM,iBAAiB,oBAAI,IAAiC;AAC5D,IAAM,wBAAwB;AAC9B,IAAM,0BAA0B;AAEhC,SAAS,aAAa,QAAwB;AAC5C,MAAI;AACF,WAAO,IAAI,KAAK,OAAO,OAAO,QAAQ,MAAM,GAAG,CAAC,EAAE,SAAS,EAAE,YAAY;AAAA,EAC3E,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEO,SAAS,eAAe,QAAgB,OAAkC;AAC/E,MAAI,OAAO,WAAW,YAAY,OAAO,WAAW,GAAG;AACrD,UAAM,IAAI,mBAAmB,EAAE,QAAQ,OAAO,MAAM,GAAG,QAAQ,kCAAkC,CAAC;AAAA,EACpG;AACA,MAAI,OAAO,SAAS,uBAAuB;AACzC,UAAM,IAAI,mBAAmB,EAAE,QAAQ,OAAO,OAAO,MAAM,GAAG,QAAQ,8BAA8B,qBAAqB,cAAc,CAAC;AAAA,EAC1I;AAGA,sBAAoB,QAAQ,KAAK;AAEjC,mBAAiB,OAAO,MAAM;AAC9B,QAAM,MAAM,aAAa,MAAM;AAC/B,iBAAe,IAAI,KAAK,EAAE,GAAG,MAAM,CAAC;AACtC;AAEA,SAAS,iBAAiB,OAA4B,QAAsB;AAC1E,QAAM,aAAa,CAAC,KAAc,KAAa,WAAmB;AAChE,QAAI,QAAQ,OAAW;AACvB,QAAI,CAAC,MAAM,QAAQ,GAAG,GAAG;AACvB,YAAM,IAAI,mBAAmB,EAAE,QAAQ,OAAO,GAAG,GAAG,QAAQ,WAAW,MAAM,OAAO,GAAG,qBAAqB,CAAC;AAAA,IAC/G;AACA,QAAI,IAAI,WAAW,QAAQ;AACzB,YAAM,IAAI,mBAAmB,EAAE,QAAQ,OAAO,IAAI,MAAM,GAAG,QAAQ,WAAW,MAAM,OAAO,GAAG,eAAe,MAAM,iBAAiB,IAAI,MAAM,IAAI,CAAC;AAAA,IACrJ;AACA,aAAS,IAAI,GAAG,IAAI,IAAI,QAAQ,KAAK;AACnC,UAAI,OAAO,IAAI,CAAC,MAAM,YAAY,IAAI,CAAC,EAAG,WAAW,GAAG;AACtD,cAAM,IAAI,mBAAmB,EAAE,QAAQ,OAAO,IAAI,CAAC,CAAC,GAAG,QAAQ,WAAW,MAAM,OAAO,GAAG,IAAI,CAAC,gCAAgC,CAAC;AAAA,MAClI;AACA,UAAI,IAAI,CAAC,EAAG,SAAS,yBAAyB;AAC5C,cAAM,IAAI,mBAAmB,EAAE,QAAQ,OAAO,IAAI,CAAC,EAAG,MAAM,GAAG,QAAQ,WAAW,MAAM,OAAO,GAAG,IAAI,CAAC,sBAAsB,uBAAuB,cAAc,CAAC;AAAA,MACrK;AAAA,IACF;AAAA,EACF;AACA,aAAW,MAAM,cAAc,gBAAgB,CAAC;AAChD,aAAW,MAAM,eAAe,iBAAiB,CAAC;AAClD,aAAW,MAAM,UAAU,YAAY,CAAC;AACxC,aAAW,MAAM,WAAW,aAAa,CAAC;AAC1C,aAAW,MAAM,UAAU,YAAY,CAAC;AAC1C;AAEO,SAAS,UAAU,QAAiD;AAIzE,QAAM,MAAM,aAAa,MAAM;AAC/B,QAAM,MAAM,eAAe,IAAI,GAAG;AAClC,MAAI,IAAK,QAAO;AAEhB,MAAI;AACF,UAAM,OAAO,eAAe,MAAM;AAClC,WAAO,EAAE,GAAG,KAAK;AAAA,EACnB,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEO,SAAS,UAAU,QAAyB;AACjD,QAAM,MAAM,aAAa,MAAM;AAC/B,SAAO,eAAe,IAAI,GAAG;AAC/B;","names":[]}
|
package/dist/chunk-M33NSC4E.js
DELETED
|
@@ -1,169 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
TOKENS
|
|
3
|
-
} from "./chunk-VSUCBUTC.js";
|
|
4
|
-
import {
|
|
5
|
-
FormatSyntaxError,
|
|
6
|
-
InvalidLocaleError,
|
|
7
|
-
UnknownTokenError
|
|
8
|
-
} from "./chunk-O6GVMBLW.js";
|
|
9
|
-
|
|
10
|
-
// src/tokenize.ts
|
|
11
|
-
var SORTED_TOKEN_STRINGS = TOKENS.map(([tok]) => tok).sort((a, b) => b.length - a.length);
|
|
12
|
-
function tokenize(format) {
|
|
13
|
-
const pieces = [];
|
|
14
|
-
let i = 0;
|
|
15
|
-
while (i < format.length) {
|
|
16
|
-
const ch = format[i];
|
|
17
|
-
if (ch === "'") {
|
|
18
|
-
if (format[i + 1] === "'") {
|
|
19
|
-
appendLiteral(pieces, "'");
|
|
20
|
-
i += 2;
|
|
21
|
-
continue;
|
|
22
|
-
}
|
|
23
|
-
let j = i + 1;
|
|
24
|
-
let literal = "";
|
|
25
|
-
let closed = false;
|
|
26
|
-
while (j < format.length) {
|
|
27
|
-
if (format[j] === "'") {
|
|
28
|
-
if (format[j + 1] === "'") {
|
|
29
|
-
literal += "'";
|
|
30
|
-
j += 2;
|
|
31
|
-
continue;
|
|
32
|
-
}
|
|
33
|
-
closed = true;
|
|
34
|
-
j += 1;
|
|
35
|
-
break;
|
|
36
|
-
}
|
|
37
|
-
literal += format[j];
|
|
38
|
-
j += 1;
|
|
39
|
-
}
|
|
40
|
-
if (!closed) {
|
|
41
|
-
throw new FormatSyntaxError({
|
|
42
|
-
format,
|
|
43
|
-
message: `temporal-fmt: unterminated quote in format string "${format}"`
|
|
44
|
-
});
|
|
45
|
-
}
|
|
46
|
-
appendLiteral(pieces, literal);
|
|
47
|
-
i = j;
|
|
48
|
-
continue;
|
|
49
|
-
}
|
|
50
|
-
const match = SORTED_TOKEN_STRINGS.find((tok) => format.startsWith(tok, i));
|
|
51
|
-
if (match) {
|
|
52
|
-
const runChar = match[match.length - 1];
|
|
53
|
-
if (format[i + match.length] === runChar) {
|
|
54
|
-
let end = i + match.length;
|
|
55
|
-
while (format[end] === runChar) end += 1;
|
|
56
|
-
throw new UnknownTokenError({
|
|
57
|
-
format,
|
|
58
|
-
token: format.slice(i, end),
|
|
59
|
-
message: `temporal-fmt: "${format.slice(i, end)}" in format string "${format}" isn't a recognized token \u2014 did you mean "${match}"?`
|
|
60
|
-
});
|
|
61
|
-
}
|
|
62
|
-
pieces.push({ kind: "token", value: match });
|
|
63
|
-
i += match.length;
|
|
64
|
-
continue;
|
|
65
|
-
}
|
|
66
|
-
appendLiteral(pieces, ch);
|
|
67
|
-
i += 1;
|
|
68
|
-
}
|
|
69
|
-
return pieces;
|
|
70
|
-
}
|
|
71
|
-
function appendLiteral(pieces, value) {
|
|
72
|
-
const last = pieces[pieces.length - 1];
|
|
73
|
-
if (last && last.kind === "literal") {
|
|
74
|
-
last.value += value;
|
|
75
|
-
} else {
|
|
76
|
-
pieces.push({ kind: "literal", value });
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// src/numbering.ts
|
|
81
|
-
var SUPPORTED_NUMBERING_SYSTEMS = /* @__PURE__ */ new Set([
|
|
82
|
-
"latn",
|
|
83
|
-
"arab",
|
|
84
|
-
"deva",
|
|
85
|
-
"beng",
|
|
86
|
-
"guru",
|
|
87
|
-
"gujr",
|
|
88
|
-
"orya",
|
|
89
|
-
"tamldec",
|
|
90
|
-
"telu",
|
|
91
|
-
"knda",
|
|
92
|
-
"mlym",
|
|
93
|
-
"fullwide",
|
|
94
|
-
"hanidec"
|
|
95
|
-
]);
|
|
96
|
-
var digitMapCache = /* @__PURE__ */ new Map();
|
|
97
|
-
function getDigitMap(system) {
|
|
98
|
-
let map = digitMapCache.get(system);
|
|
99
|
-
if (map) return map;
|
|
100
|
-
/* c8 ignore start @preserve -- dead by construction, not just
|
|
101
|
-
untested: both callers (convertDigits, convertDigitsToAscii) already
|
|
102
|
-
return early on system === 'latn' before calling getDigitMap at all,
|
|
103
|
-
so this function is never invoked with 'latn'. Kept as a defensive
|
|
104
|
-
fallback rather than trusting that stays true for any future
|
|
105
|
-
caller. */
|
|
106
|
-
if (system === "latn") {
|
|
107
|
-
map = {};
|
|
108
|
-
for (let i = 0; i < 10; i++) map[String(i)] = String(i);
|
|
109
|
-
} else {
|
|
110
|
-
/* c8 ignore stop @preserve */
|
|
111
|
-
const fmt = new Intl.NumberFormat("en-US-u-nu-" + system, { useGrouping: false });
|
|
112
|
-
map = {};
|
|
113
|
-
for (let i = 0; i < 10; i++) {
|
|
114
|
-
map[String(i)] = fmt.format(i);
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
digitMapCache.set(system, map);
|
|
118
|
-
return map;
|
|
119
|
-
}
|
|
120
|
-
function convertDigits(s, system) {
|
|
121
|
-
if (system === "latn") return s;
|
|
122
|
-
if (!SUPPORTED_NUMBERING_SYSTEMS.has(system)) {
|
|
123
|
-
throw new InvalidLocaleError({ actual: system, reason: `numbering system "${system}" is not supported. Supported: ${[...SUPPORTED_NUMBERING_SYSTEMS].join(", ")}.` });
|
|
124
|
-
}
|
|
125
|
-
const map = getDigitMap(system);
|
|
126
|
-
let result = "";
|
|
127
|
-
for (const ch of s) {
|
|
128
|
-
if (ch >= "0" && ch <= "9") {
|
|
129
|
-
result += map[ch] ?? ch;
|
|
130
|
-
} else {
|
|
131
|
-
result += ch;
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
return result;
|
|
135
|
-
}
|
|
136
|
-
function convertDigitsToAscii(s, system) {
|
|
137
|
-
if (system === "latn") return s;
|
|
138
|
-
if (!SUPPORTED_NUMBERING_SYSTEMS.has(system)) {
|
|
139
|
-
throw new InvalidLocaleError({ actual: system, reason: `numbering system "${system}" is not supported.` });
|
|
140
|
-
}
|
|
141
|
-
const map = getDigitMap(system);
|
|
142
|
-
const reverse = {};
|
|
143
|
-
for (const k of Object.keys(map)) reverse[map[k]] = k;
|
|
144
|
-
let result = "";
|
|
145
|
-
for (const ch of s) {
|
|
146
|
-
result += reverse[ch] ?? ch;
|
|
147
|
-
}
|
|
148
|
-
return result;
|
|
149
|
-
}
|
|
150
|
-
function applyNumbering(s, options) {
|
|
151
|
-
const system = options.numberingSystem ?? "latn";
|
|
152
|
-
if (system === "latn") return s;
|
|
153
|
-
return convertDigits(s, system);
|
|
154
|
-
}
|
|
155
|
-
function applyParseNumbering(s, options) {
|
|
156
|
-
const system = options.parseNumberingSystem ?? "latn";
|
|
157
|
-
if (system === "latn") return s;
|
|
158
|
-
return convertDigitsToAscii(s, system);
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
export {
|
|
162
|
-
tokenize,
|
|
163
|
-
SUPPORTED_NUMBERING_SYSTEMS,
|
|
164
|
-
convertDigits,
|
|
165
|
-
convertDigitsToAscii,
|
|
166
|
-
applyNumbering,
|
|
167
|
-
applyParseNumbering
|
|
168
|
-
};
|
|
169
|
-
//# sourceMappingURL=chunk-M33NSC4E.js.map
|
package/dist/chunk-MXXYIMFJ.js
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
// src/temporalProvider.ts
|
|
2
|
-
var injectedTemporal;
|
|
3
|
-
var onTemporalChanged = [];
|
|
4
|
-
function subscribeToTemporalChanges(listener) {
|
|
5
|
-
onTemporalChanged.push(listener);
|
|
6
|
-
}
|
|
7
|
-
function setTemporal(temporal) {
|
|
8
|
-
injectedTemporal = temporal;
|
|
9
|
-
for (const listener of onTemporalChanged) listener();
|
|
10
|
-
}
|
|
11
|
-
function resolveTemporal() {
|
|
12
|
-
return injectedTemporal ?? globalThis.Temporal;
|
|
13
|
-
}
|
|
14
|
-
function getTemporal() {
|
|
15
|
-
const temporal = resolveTemporal();
|
|
16
|
-
if (!temporal) {
|
|
17
|
-
throw new Error(
|
|
18
|
-
"temporal-fmt: parse() needs a Temporal implementation to construct its result. Call setTemporal(Temporal) once at startup, or assign one to globalThis.Temporal (native on Node 26+, or a polyfill like temporal-polyfill)."
|
|
19
|
-
);
|
|
20
|
-
}
|
|
21
|
-
return temporal;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export {
|
|
25
|
-
subscribeToTemporalChanges,
|
|
26
|
-
setTemporal,
|
|
27
|
-
getTemporal
|
|
28
|
-
};
|
|
29
|
-
//# sourceMappingURL=chunk-MXXYIMFJ.js.map
|
package/dist/chunk-NFKQ3EXC.js
DELETED
|
@@ -1,188 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
asDateFieldView
|
|
3
|
-
} from "./chunk-4ZBGVYLR.js";
|
|
4
|
-
|
|
5
|
-
// src/comparison.ts
|
|
6
|
-
function toComparableMs(view) {
|
|
7
|
-
const REFERENCE_YEAR = 2e3;
|
|
8
|
-
const DAYS_PER_YEAR_AVG = 365.2425;
|
|
9
|
-
const MS_PER_DAY = 864e5;
|
|
10
|
-
const MS_PER_HOUR = 36e5;
|
|
11
|
-
const MS_PER_MINUTE = 6e4;
|
|
12
|
-
const MS_PER_SECOND = 1e3;
|
|
13
|
-
const year = view.year;
|
|
14
|
-
const yearsFromRef = year - REFERENCE_YEAR;
|
|
15
|
-
const yearMs = yearsFromRef * DAYS_PER_YEAR_AVG * MS_PER_DAY;
|
|
16
|
-
const CUMULATIVE_DAYS = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334];
|
|
17
|
-
let doy = CUMULATIVE_DAYS[view.month - 1] + view.day - 1;
|
|
18
|
-
const isLeap = year % 4 === 0 && year % 100 !== 0 || year % 400 === 0;
|
|
19
|
-
if (view.month > 2 && isLeap) doy += 1;
|
|
20
|
-
const dayMs = doy * MS_PER_DAY;
|
|
21
|
-
const hourMs = (view.hour ?? 0) * MS_PER_HOUR;
|
|
22
|
-
const minuteMs = (view.minute ?? 0) * MS_PER_MINUTE;
|
|
23
|
-
const secondMs = (view.second ?? 0) * MS_PER_SECOND;
|
|
24
|
-
const msMs = view.millisecond ?? 0;
|
|
25
|
-
return yearMs + dayMs + hourMs + minuteMs + secondMs + msMs;
|
|
26
|
-
}
|
|
27
|
-
function asDateTimeFieldView(value) {
|
|
28
|
-
const v = asDateFieldView(value);
|
|
29
|
-
return v;
|
|
30
|
-
}
|
|
31
|
-
function compare(a, b) {
|
|
32
|
-
const av = asDateTimeFieldView(a);
|
|
33
|
-
const bv = asDateTimeFieldView(b);
|
|
34
|
-
const am = toComparableMs(av);
|
|
35
|
-
const bm = toComparableMs(bv);
|
|
36
|
-
if (am < bm) return -1;
|
|
37
|
-
if (am > bm) return 1;
|
|
38
|
-
return 0;
|
|
39
|
-
}
|
|
40
|
-
function isEqual(a, b) {
|
|
41
|
-
return compare(a, b) === 0;
|
|
42
|
-
}
|
|
43
|
-
function isBefore(a, b) {
|
|
44
|
-
return compare(a, b) < 0;
|
|
45
|
-
}
|
|
46
|
-
function isAfter(a, b) {
|
|
47
|
-
return compare(a, b) > 0;
|
|
48
|
-
}
|
|
49
|
-
function min(values) {
|
|
50
|
-
if (!Array.isArray(values) || values.length === 0) {
|
|
51
|
-
throw new Error("temporal-fmt: min() requires a non-empty array of values.");
|
|
52
|
-
}
|
|
53
|
-
let best = values[0];
|
|
54
|
-
for (let i = 1; i < values.length; i++) {
|
|
55
|
-
if (compare(values[i], best) < 0) best = values[i];
|
|
56
|
-
}
|
|
57
|
-
return best;
|
|
58
|
-
}
|
|
59
|
-
function max(values) {
|
|
60
|
-
if (!Array.isArray(values) || values.length === 0) {
|
|
61
|
-
throw new Error("temporal-fmt: max() requires a non-empty array of values.");
|
|
62
|
-
}
|
|
63
|
-
let best = values[0];
|
|
64
|
-
for (let i = 1; i < values.length; i++) {
|
|
65
|
-
if (compare(values[i], best) > 0) best = values[i];
|
|
66
|
-
}
|
|
67
|
-
return best;
|
|
68
|
-
}
|
|
69
|
-
function clamp(value, lo, hi) {
|
|
70
|
-
if (compare(value, lo) < 0) return lo;
|
|
71
|
-
if (compare(value, hi) > 0) return hi;
|
|
72
|
-
return value;
|
|
73
|
-
}
|
|
74
|
-
function isBetween(value, lo, hi) {
|
|
75
|
-
return compare(value, lo) >= 0 && compare(value, hi) <= 0;
|
|
76
|
-
}
|
|
77
|
-
function readYearMonthDay(value) {
|
|
78
|
-
const v = asDateFieldView(value);
|
|
79
|
-
return { year: v.year, month: v.month, day: v.day };
|
|
80
|
-
}
|
|
81
|
-
function todayYearMonthDay() {
|
|
82
|
-
const now = /* @__PURE__ */ new Date();
|
|
83
|
-
return { year: now.getFullYear(), month: now.getMonth() + 1, day: now.getDate() };
|
|
84
|
-
}
|
|
85
|
-
function dateEqualYearMonthDay(a, b) {
|
|
86
|
-
return a.year === b.year && a.month === b.month && a.day === b.day;
|
|
87
|
-
}
|
|
88
|
-
function isSameDay(value, reference) {
|
|
89
|
-
return dateEqualYearMonthDay(readYearMonthDay(value), readYearMonthDay(reference));
|
|
90
|
-
}
|
|
91
|
-
function isToday(value) {
|
|
92
|
-
return dateEqualYearMonthDay(readYearMonthDay(value), todayYearMonthDay());
|
|
93
|
-
}
|
|
94
|
-
function tomorrowYearMonthDay() {
|
|
95
|
-
const d = /* @__PURE__ */ new Date();
|
|
96
|
-
d.setDate(d.getDate() + 1);
|
|
97
|
-
return { year: d.getFullYear(), month: d.getMonth() + 1, day: d.getDate() };
|
|
98
|
-
}
|
|
99
|
-
function yesterdayYearMonthDay() {
|
|
100
|
-
const d = /* @__PURE__ */ new Date();
|
|
101
|
-
d.setDate(d.getDate() - 1);
|
|
102
|
-
return { year: d.getFullYear(), month: d.getMonth() + 1, day: d.getDate() };
|
|
103
|
-
}
|
|
104
|
-
function isTomorrow(value) {
|
|
105
|
-
return dateEqualYearMonthDay(readYearMonthDay(value), tomorrowYearMonthDay());
|
|
106
|
-
}
|
|
107
|
-
function isYesterday(value) {
|
|
108
|
-
return dateEqualYearMonthDay(readYearMonthDay(value), yesterdayYearMonthDay());
|
|
109
|
-
}
|
|
110
|
-
function sameWeek(yearA, monthA, dayA, yearB, monthB, dayB) {
|
|
111
|
-
const DAYS_PER_400_YEARS = 146097;
|
|
112
|
-
const daysSince = (y, m, d) => {
|
|
113
|
-
const y2 = m <= 2 ? y - 1 : y;
|
|
114
|
-
const era = (y2 >= 0 ? y2 : y2 - 399) / 400 | 0;
|
|
115
|
-
const yoe = y2 - era * 400;
|
|
116
|
-
const m2 = m > 2 ? m - 3 : m + 9;
|
|
117
|
-
const doy = Math.floor(Math.floor(153 * m2 + 2) / 5 + d - 1);
|
|
118
|
-
const doe = yoe * 365 + Math.floor(yoe / 4) - Math.floor(yoe / 100) + doy;
|
|
119
|
-
return era * DAYS_PER_400_YEARS + doe - 719468;
|
|
120
|
-
};
|
|
121
|
-
const a = daysSince(yearA, monthA, dayA);
|
|
122
|
-
const b = daysSince(yearB, monthB, dayB);
|
|
123
|
-
if (Math.abs(a - b) >= 7) return false;
|
|
124
|
-
const weekdayOf = (y, m, d) => {
|
|
125
|
-
const days = daysSince(y, m, d);
|
|
126
|
-
const date = new Date(Date.UTC(y, m - 1, d));
|
|
127
|
-
const jsDow = date.getUTCDay();
|
|
128
|
-
return jsDow === 0 ? 7 : jsDow;
|
|
129
|
-
};
|
|
130
|
-
const wa = a - (weekdayOf(yearA, monthA, dayA) - 1);
|
|
131
|
-
const wb = b - (weekdayOf(yearB, monthB, dayB) - 1);
|
|
132
|
-
return wa === wb;
|
|
133
|
-
}
|
|
134
|
-
function isSameWeek(value, reference) {
|
|
135
|
-
const a = readYearMonthDay(value);
|
|
136
|
-
const b = readYearMonthDay(reference);
|
|
137
|
-
return sameWeek(a.year, a.month, a.day, b.year, b.month, b.day);
|
|
138
|
-
}
|
|
139
|
-
function isSameMonth(value, reference) {
|
|
140
|
-
const a = readYearMonthDay(value);
|
|
141
|
-
const b = readYearMonthDay(reference);
|
|
142
|
-
return a.year === b.year && a.month === b.month;
|
|
143
|
-
}
|
|
144
|
-
function isSameQuarter(value, reference) {
|
|
145
|
-
const a = readYearMonthDay(value);
|
|
146
|
-
const b = readYearMonthDay(reference);
|
|
147
|
-
return a.year === b.year && Math.ceil(a.month / 3) === Math.ceil(b.month / 3);
|
|
148
|
-
}
|
|
149
|
-
function isSameYear(value, reference) {
|
|
150
|
-
return readYearMonthDay(value).year === readYearMonthDay(reference).year;
|
|
151
|
-
}
|
|
152
|
-
function isWeekend(value) {
|
|
153
|
-
if (typeof value !== "object" || value === null) {
|
|
154
|
-
throw new Error(`temporal-fmt: isWeekend() expected a Temporal value, got ${String(value)}.`);
|
|
155
|
-
}
|
|
156
|
-
const v = value;
|
|
157
|
-
if (typeof v.dayOfWeek !== "number") {
|
|
158
|
-
throw new Error(
|
|
159
|
-
`temporal-fmt: isWeekend() needs a value with a dayOfWeek field (PlainDate / PlainDateTime / ZonedDateTime).`
|
|
160
|
-
);
|
|
161
|
-
}
|
|
162
|
-
return v.dayOfWeek === 6 || v.dayOfWeek === 7;
|
|
163
|
-
}
|
|
164
|
-
function isWeekday(value) {
|
|
165
|
-
return !isWeekend(value);
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
export {
|
|
169
|
-
compare,
|
|
170
|
-
isEqual,
|
|
171
|
-
isBefore,
|
|
172
|
-
isAfter,
|
|
173
|
-
min,
|
|
174
|
-
max,
|
|
175
|
-
clamp,
|
|
176
|
-
isBetween,
|
|
177
|
-
isSameDay,
|
|
178
|
-
isToday,
|
|
179
|
-
isTomorrow,
|
|
180
|
-
isYesterday,
|
|
181
|
-
isSameWeek,
|
|
182
|
-
isSameMonth,
|
|
183
|
-
isSameQuarter,
|
|
184
|
-
isSameYear,
|
|
185
|
-
isWeekend,
|
|
186
|
-
isWeekday
|
|
187
|
-
};
|
|
188
|
-
//# sourceMappingURL=chunk-NFKQ3EXC.js.map
|