{"version":3,"sources":["../src/duration.ts","../src/errors.ts","../src/rounding.ts","../src/constants.ts","../src/localeVocab.ts","../src/formatDuration.ts"],"sourcesContent":["/*\n * Copyright 2026 DirazCoder\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// 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, fieldToNs, type DurationFields } from './rounding.js';\nimport { formatDuration, renderDurationPieces } 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()'s own piece renderer, so the joined\n// string and the parts can never disagree. (This used to re-tokenize the\n// format string and re-derive part boundaries by searching the rendered\n// string with indexOf — which mis-sliced whenever a literal's text also\n// occurred inside an earlier token's rendered value, e.g. a \"s\"\n// separator against \"5 seconds\".) Zero-suppressed tokens emit an empty\n// token part and their neighboring literals are kept, matching the\n// documented formatDurationToParts contract.\nexport function formatDurationToParts(\n  duration: Record<string, unknown>,\n  formatStr: string,\n  options: Parameters<typeof formatDuration>[2] = {},\n): DurationPart[] {\n  return renderDurationPieces(duration, formatStr, options).map((p) =>\n    p.kind === 'token' ? { type: 'token' as const, value: p.value, token: p.token } : { type: 'literal' as const, value: p.value },\n  );\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. The optional\n  // leading '-' is ISO's negative-duration form (the same sign\n  // formatISODuration now emits — it used to produce \"PT-30S\", which\n  // this parser rejects, so the pair didn't round-trip).\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(2).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  // Avoid -0 (deep-equal-unequal to 0 and confusing in JSON): only\n  // actual nonzero values pick up the sign.\n  const negate = (n: number): number => m[1] === '-' && n !== 0 ? -n : n;\n  return {\n    years: negate(toNum(m[2])),\n    months: negate(toNum(m[3])),\n    weeks: negate(toNum(m[4])),\n    days: negate(toNum(m[5])),\n    hours: negate(toNum(m[6])),\n    minutes: negate(toNum(m[7])),\n    seconds: negate(toNum(m[8])),\n  };\n}\n\nexport function formatISODuration(duration: DurationFields): string {\n  // ISO 8601 expresses a negative duration with a leading '-' on the\n  // whole designator (\"−PT30S\"), never with negative components — the\n  // old format emitted \"PT-30S\", which parseISODuration (and every\n  // other ISO 8601 reader) rejects. Emit the sign up front and absolute\n  // components after it, so the output round-trips through this\n  // module's own parser.\n  const negative = (duration.years ?? 0) < 0 || (duration.months ?? 0) < 0 || (duration.weeks ?? 0) < 0\n    || (duration.days ?? 0) < 0 || (duration.hours ?? 0) < 0 || (duration.minutes ?? 0) < 0\n    || (duration.seconds ?? 0) < 0;\n  const sign = negative ? '-' : '';\n  const years = Math.abs(duration.years ?? 0);\n  const months = Math.abs(duration.months ?? 0);\n  const weeks = Math.abs(duration.weeks ?? 0);\n  const days = Math.abs(duration.days ?? 0);\n  const hours = Math.abs(duration.hours ?? 0);\n  const minutes = Math.abs(duration.minutes ?? 0);\n  const seconds = Math.abs(duration.seconds ?? 0);\n  const parts: string[] = [`${sign}P`];\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'] (or ['-P']) when every field is zero -- ISO has\n  // no empty duration, so fall back to P0D.\n  if (parts.length === 1) parts.push('0D');\n  return parts.join('');\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// 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 += fieldToNs(v, nsPer, k);\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 += fieldToNs(v, NS_PER[k]!, 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 += fieldToNs(v, NS_PER[k]!, 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 * Copyright 2026 DirazCoder\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// structured error classes for parse()/format() failures.\n//\n// as of 0.9.0 every throw site on the parse/format data path —\n// tokenize.ts, pattern.ts, format.ts, parse.ts (parse/safeParse/tryParse/\n// parseToParts/compileParser), plus the two data-path throws in\n// localeVocab.ts (partValue/assertNoCollision on the getLocaleVocab side)\n// — throws one of these typed classes directly instead of a plain\n// `new Error(message)`. every site we migrated kept its exact pre-0.9.0\n// message text, and since subclasses of TemporalFmtError still pass\n// `instanceof Error` and match the same message regexes (e.g.\n// `/token \"HH\" requires/`), this didn't need a semver-major bump for\n// either of those checks. what DOES break in 0.9.0: if anyone was\n// specifically checking `err.constructor === Error` or `err.name ===\n// 'Error'`, that'll now see a different name (e.g. 'FormatSyntaxError').\n// noted in the 0.9.0 changelog.\n//\n// deliberately did NOT migrate:\n//  - localeVocab.ts's registration-time throws (assertValidVocab,\n//    registerLocaleVocab itself) — these are config-time API-misuse\n//    errors on data a developer hands in once at startup, not runtime\n//    parse/format failures, so they don't really fit this module's\n//    TemporalFmtErrorCode taxonomy as-is. assertNoCollision is shared\n//    between the registration path and the data path (getLocaleVocab)\n//    though, so its throw stays a plain Error for both until that gets\n//    split apart — see the tracking note where it's called.\n//\n// wrapUntypedError() below is still around for localeVocab.ts's\n// registration throws, and for anything a caller passes into\n// safeParse()/tryParse() from outside this package. on the data path\n// itself, safeParse's `if (err instanceof TemporalFmtError) return { ok:\n// false, error: err }` catches everything before it'd even reach the\n// classifier here, so honestly the regex branches below are dead for\n// parse/format/tokenize/pattern at this point — kept as the fallback for\n// whatever hasn't been migrated to a typed throw site yet\n\nexport type TemporalFmtErrorCode =\n  | 'FORMAT_SYNTAX_ERROR'\n  | 'UNKNOWN_TOKEN'\n  | 'PARSE_MISMATCH'\n  | 'INVALID_DATE'\n  | 'INVALID_TIME'\n  | 'INVALID_OFFSET'\n  | 'INVALID_TIME_ZONE'\n  | 'INVALID_CALENDAR'\n  | 'AMBIGUOUS_INPUT'\n  | 'INVALID_LOCALE'\n  | 'INVALID_DURATION';\n\nexport interface TemporalFmtErrorFields {\n  code: TemporalFmtErrorCode;\n  input?: string;\n  format?: string;\n  token?: string;\n  position?: number;\n  expected?: string;\n  actual?: string;\n  reason?: string;\n}\n\n// base class. `message` is the human-readable summary, the structured\n// fields are the machine-readable bits a linter or codemod would report\n// on. calling Error.captureStackTrace manually (where it exists) so the\n// stack points at wherever this got thrown from, not at this constructor\n// — same trick Node uses for its own error classes\nexport class TemporalFmtError extends Error {\n  readonly code: TemporalFmtErrorCode;\n  readonly input?: string;\n  readonly format?: string;\n  readonly token?: string;\n  readonly position?: number;\n  readonly expected?: string;\n  readonly actual?: string;\n  readonly reason?: string;\n\n  constructor(message: string, fields: TemporalFmtErrorFields) {\n    super(message);\n    this.name = 'TemporalFmtError';\n    this.code = fields.code;\n    this.input = fields.input;\n    this.format = fields.format;\n    this.token = fields.token;\n    this.position = fields.position;\n    this.expected = fields.expected;\n    this.actual = fields.actual;\n    this.reason = fields.reason;\n    // don't want the stack pointing at this constructor line — captureStackTrace\n    // is V8-only though, so on other engines callers just get the default\n    // stack (pointing here) since there's nothing better to do about it\n    const capture = (Error as unknown as { captureStackTrace?: (target: Error, ctor?: Function) => void }).captureStackTrace;\n    if (typeof capture === 'function') {\n      capture(this, this.constructor);\n    }\n  }\n\n  // lets callers just call err.toJSON() for logging. plain Error doesn't\n  // serialize its non-enumerable fields on its own, so this picks them\n  // up explicitly\n  toJSON(): TemporalFmtErrorFields & { name: string; message: string } {\n    return {\n      name: this.name,\n      message: this.message,\n      code: this.code,\n      input: this.input,\n      format: this.format,\n      token: this.token,\n      position: this.position,\n      expected: this.expected,\n      actual: this.actual,\n      reason: this.reason,\n    };\n  }\n}\n\n// each subclass just fixes `code` so callers can switch on it without\n// having to re-check message text. the constructor only takes the\n// fields that actually vary per call; `code` and the default message\n// template come from the subclass itself\n\nexport class FormatSyntaxError extends TemporalFmtError {\n  constructor(fields: Omit<TemporalFmtErrorFields, 'code'> & { message?: string }) {\n    const { message, ...rest } = fields;\n    super(\n      message ?? `format string \"${fields.format ?? ''}\" has a syntax error${fields.reason ? `: ${fields.reason}` : ''}.`,\n      { code: 'FORMAT_SYNTAX_ERROR', ...rest },\n    );\n    this.name = 'FormatSyntaxError';\n  }\n}\n\nexport class UnknownTokenError extends TemporalFmtError {\n  constructor(fields: Omit<TemporalFmtErrorFields, 'code'> & { message?: string }) {\n    const { message, ...rest } = fields;\n    super(\n      message ?? `token \"${fields.token ?? ''}\" is not a recognized temporal-fmt token${fields.format ? ` in format string \"${fields.format}\"` : ''}.`,\n      { code: 'UNKNOWN_TOKEN', ...rest },\n    );\n    this.name = 'UnknownTokenError';\n  }\n}\n\nexport class ParseMismatchError extends TemporalFmtError {\n  constructor(fields: Omit<TemporalFmtErrorFields, 'code'> & { message?: string }) {\n    const { message, ...rest } = fields;\n    super(\n      message ?? `input \"${fields.input ?? ''}\" does not match format \"${fields.format ?? ''}\"${fields.reason ? `: ${fields.reason}` : ''}.`,\n      { code: 'PARSE_MISMATCH', ...rest },\n    );\n    this.name = 'ParseMismatchError';\n  }\n}\n\nexport class InvalidDateError extends TemporalFmtError {\n  constructor(fields: Omit<TemporalFmtErrorFields, 'code'> & { message?: string }) {\n    const { message, ...rest } = fields;\n    super(\n      message ?? `input \"${fields.input ?? ''}\" does not describe a valid date${fields.reason ? `: ${fields.reason}` : ''}.`,\n      { code: 'INVALID_DATE', ...rest },\n    );\n    this.name = 'InvalidDateError';\n  }\n}\n\n/* c8 ignore start @preserve -- InvalidTimeError is part of the public\n   error-class surface (exported from index.ts, code 'INVALID_TIME') but\n   nothing in this package actually constructs one. went and checked\n   whether it could get wired in the same way InvalidTimeZoneError just\n   was (see parse.ts's zzz-validation loop) — does hour/minute/second have\n   a post-match semantic range check the way zone ids do? nope.\n   pattern.ts's regex fragments for HH/H/hh/h/mm/m/ss/s already enforce\n   their valid ranges right at the regex level (e.g. HH is\n   '(?:[01]\\d|2[0-3])', which literally can't match \"99\"), so an\n   out-of-range time just gets rejected as a plain shape mismatch before\n   any semantic check would even run. no live gap to hook this into\n   without inventing a redundant check purely to give this class a body.\n   leaving it unconstructed until the library actually hits a real\n   invalid-time case worth reporting */\nexport class InvalidTimeError extends TemporalFmtError {\n  constructor(fields: Omit<TemporalFmtErrorFields, 'code'> & { message?: string }) {\n    const { message, ...rest } = fields;\n    super(\n      message ?? `input \"${fields.input ?? ''}\" does not describe a valid time${fields.reason ? `: ${fields.reason}` : ''}.`,\n      { code: 'INVALID_TIME', ...rest },\n    );\n    this.name = 'InvalidTimeError';\n  }\n}\n/* c8 ignore stop @preserve */\n\nexport class InvalidOffsetError extends TemporalFmtError {\n  constructor(fields: Omit<TemporalFmtErrorFields, 'code'> & { message?: string }) {\n    const { message, ...rest } = fields;\n    super(\n      message ?? `offset \"${fields.actual ?? ''}\" is invalid${fields.reason ? `: ${fields.reason}` : ''}.`,\n      { code: 'INVALID_OFFSET', ...rest },\n    );\n    this.name = 'InvalidOffsetError';\n  }\n}\n\nexport class InvalidTimeZoneError extends TemporalFmtError {\n  constructor(fields: Omit<TemporalFmtErrorFields, 'code'> & { message?: string }) {\n    const { message, ...rest } = fields;\n    super(\n      message ?? `time zone \"${fields.actual ?? ''}\" is not a recognized IANA time zone or fixed offset${fields.reason ? `: ${fields.reason}` : ''}.`,\n      { code: 'INVALID_TIME_ZONE', ...rest },\n    );\n    this.name = 'InvalidTimeZoneError';\n  }\n}\n\n/* c8 ignore start @preserve -- InvalidCalendarError is part of the\n   public error-class surface (exported from index.ts, code\n   'INVALID_CALENDAR') but same story as InvalidTimeError above — nothing\n   in this package constructs one. checked for a wiring opportunity the\n   same way: there's no user-supplied calendar identifier anywhere in the\n   library that we'd validate against a supported list. resolveCalendar()\n   in parse.ts derives the calendar entirely from Intl's own resolution\n   of the locale string — it's never handed an arbitrary \"calendar\" value\n   a caller could actually get wrong. no live input to reject here.\n   leaving it unconstructed until the library accepts an actual calendar\n   parameter that could be invalid */\nexport class InvalidCalendarError extends TemporalFmtError {\n  constructor(fields: Omit<TemporalFmtErrorFields, 'code'> & { message?: string }) {\n    const { message, ...rest } = fields;\n    super(\n      message ?? `calendar \"${fields.actual ?? ''}\" is not supported${fields.reason ? `: ${fields.reason}` : ''}.`,\n      { code: 'INVALID_CALENDAR', ...rest },\n    );\n    this.name = 'InvalidCalendarError';\n  }\n}\n/* c8 ignore stop @preserve */\n\nexport class AmbiguousInputError extends TemporalFmtError {\n  constructor(fields: Omit<TemporalFmtErrorFields, 'code'> & { message?: string }) {\n    const { message, ...rest } = fields;\n    super(\n      message ?? `input \"${fields.input ?? ''}\" is ambiguous${fields.reason ? `: ${fields.reason}` : ''}.`,\n      { code: 'AMBIGUOUS_INPUT', ...rest },\n    );\n    this.name = 'AmbiguousInputError';\n  }\n}\n\nexport class InvalidLocaleError extends TemporalFmtError {\n  constructor(fields: Omit<TemporalFmtErrorFields, 'code'> & { message?: string }) {\n    const { message, ...rest } = fields;\n    super(\n      message ?? `locale \"${fields.actual ?? ''}\" is not a valid BCP-47 tag${fields.reason ? `: ${fields.reason}` : ''}.`,\n      { code: 'INVALID_LOCALE', ...rest },\n    );\n    this.name = 'InvalidLocaleError';\n  }\n}\n\nexport class InvalidDurationError extends TemporalFmtError {\n  constructor(fields: Omit<TemporalFmtErrorFields, 'code'> & { message?: string }) {\n    const { message, ...rest } = fields;\n    super(\n      message ?? `duration is invalid${fields.reason ? `: ${fields.reason}` : ''}.`,\n      { code: 'INVALID_DURATION', ...rest },\n    );\n    this.name = 'InvalidDurationError';\n  }\n}\n\n// wraps a plain Error thrown from a code path that hasn't been migrated\n// to typed errors yet. keeps the original message around in `reason` so\n// callers reading the typed surface still see what actually failed.\n// used by safeParse() in parse.ts.\n//\n// after the 0.9.0 migration, every throw site on the parse/format data\n// path throws a TemporalFmtError directly, so safeParse's `instanceof\n// TemporalFmtError` check always passes before this function would even\n// get called — nothing in the current test suite actually reaches any\n// branch below. kept as the safety net for whenever someone adds an\n// unmigrated throw site down the line (see the c8-ignored call in\n// parse.ts's safeParse) — same reasoning as that call site: removing\n// this would silently break the \"safeParse always returns a\n// TemporalFmtError\" contract the moment anyone adds a bare\n// `throw new Error(...)` without wiring up a typed class for it\n/* c8 ignore start @preserve -- unreachable from the current test suite,\n   see rationale above */\nexport function wrapUntypedError(err: Error, context: { input?: string; format?: string }): TemporalFmtError {\n  // try to classify by looking at the message — covers the existing\n  // parse()/format() throw sites without having to touch them. anything\n  // that doesn't match a known pattern falls through to a generic\n  // ParseMismatchError, still with the structured fields intact\n  const msg = err.message;\n  if (/unknown token|isn't a recognized token/.test(msg)) {\n    return new UnknownTokenError({ input: context.input, format: context.format, reason: msg });\n  }\n  if (/ambiguous/i.test(msg)) {\n    return new AmbiguousInputError({ input: context.input, format: context.format, reason: msg });\n  }\n  if (/offset/.test(msg) && /out of range|exceeds|doesn't match the shape/i.test(msg)) {\n    return new InvalidOffsetError({ input: context.input, format: context.format, reason: msg });\n  }\n  if (/no valid pattern matches/i.test(msg)) {\n    return new ParseMismatchError({ input: context.input, format: context.format, reason: msg });\n  }\n  if (/doesn't describe a valid date\\/time|incomplete date|weekday token|quarter token/.test(msg)) {\n    return new InvalidDateError({ input: context.input, format: context.format, reason: msg });\n  }\n  const lowerMsg = msg.toLowerCase();\n  const mentionsLocale = lowerMsg.includes('locale');\n  if (\n    (mentionsLocale && (lowerMsg.includes('produced no') || lowerMsg.includes('not a valid'))) ||\n    lowerMsg.includes('cutoffs must be')\n  ) {\n    return new InvalidLocaleError({ input: context.input, format: context.format, reason: msg });\n  }\n  if (/format string exceeds maximum length|input exceeds maximum length|unterminated quote|isn't a recognized token/i.test(msg)) {\n    return new FormatSyntaxError({ input: context.input, format: context.format, reason: msg });\n  }\n  return new ParseMismatchError({ input: context.input, format: context.format, reason: msg });\n}\n/* c8 ignore stop @preserve */","/*\n * Copyright 2026 DirazCoder\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// Rounding helpers. Pure functions over field bags,\n// same convention as arithmetic.ts/calendarUtils.ts.\n\nimport { asDateFieldView, type DateFieldView } from './calendarUtils.js';\nimport { InvalidDurationError } from './errors.js';\nimport { daysFromCivil } from './isoWeek.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  // Operates on the SIGNED value. The absolute-value-then-reapply-sign\n  // version this replaced inverted floor/ceil for every negative input\n  // (Unix-epoch ms < 0 = any date before 1970-01-01): floor(-0.5 days)\n  // came out as 0 (= 1970-01-01) instead of -1 (= 1969-12-31), i.e.\n  // floor and ceil were literally swapped for pre-epoch dates, and\n  // 'nearest' broke ties away-from-epoch (10:30 pre-epoch → 10:00 while\n  // the identical post-epoch clock time → 11:00). Signed Math.* calls\n  // are epoch-consistent by construction: floor always moves toward\n  // −∞ (the earlier boundary), ceil toward +∞, trunc toward zero,\n  // nearest ties up (Math.round semantics, both sides of the epoch).\n  const stepped = ms / 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 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. daysFromCivil is O(1) and correct for negative years;\n  // the forward era previously inlined here (Math.floor of the -399-\n  // offset numerator) shifted pre-year-0 dates by one day.\n  const days = daysFromCivil(v.year!, v.month!, v.day!);\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. The era term MUST use truncating division — the\n  // -146096 offset exists precisely so trunc lands the era correctly;\n  // Math.floor double-shifts every z < 0 by one day. (Same fix as\n  // arithmetic.ts's shiftDays.)\n  const z = totalDays + 719468;\n  const era = Math.trunc((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  // daysFromCivil (isoWeek.ts): O(1) and correct for negative years —\n  // the forward era previously inlined here (Math.floor of Hinnant's\n  // -399-offset numerator) double-corrected, and the inverse era's\n  // Math.floor (see fromMs) canceled it only for round-trips, leaving\n  // one-way consumers off by a day for pre-year-0 dates.\n  const totalDays = daysFromCivil(v.year!, v.month!, v.day!) + days;\n  const z = totalDays + 719468;\n  const era2 = Math.trunc((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\n// Converts one absolute-unit field to nanoseconds, exactly. Integer\n// values keep the exact BigInt path (arbitrary magnitude). Fractional\n// values — which parseISODuration legitimately produces (\"P1.5D\") —\n// scale in floating point and are accepted only when the product is a\n// safe integer, so P1.5D balances to exactly 1 day + 12 hours. A\n// fractional value whose product can't be represented exactly (large\n// fractional day counts) throws the typed InvalidDurationError instead\n// of the opaque \"TypeError: Cannot convert 1.5 to a BigInt\" the old\n// BigInt(v) call produced. Shared by roundDuration (here) and the\n// duration.ts arithmetic helpers — duration.ts already imports from\n// this module, so exporting keeps the dependency direction acyclic.\nexport function fieldToNs(v: number, nsPer: bigint, field: string): bigint {\n  if (Number.isInteger(v)) return BigInt(v) * nsPer;\n  const scaled = v * Number(nsPer);\n  if (Number.isSafeInteger(scaled)) return BigInt(scaled);\n  throw new InvalidDurationError({\n    reason: `field \"${field}\" (${v}) cannot be converted to nanoseconds exactly — the value is too large for a fractional unit; use smaller units`,\n  });\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  // Default first, then validate: roundDuration({hours:1}, {unit:'hours'})\n  // (no increment) must keep meaning \"increment 1\", not trip the guard.\n  // A fractional value (2.5) used to reach BigInt() and throw a raw V8\n  // RangeError (\"The number 2.5 cannot be converted to a BigInt\") —\n  // inconsistent with round(), which accepts fractional increments via\n  // float math, and with the typed-error surface this library promises.\n  const incrementValue = options.roundingIncrement ?? 1;\n  if (typeof incrementValue !== 'number' || !Number.isInteger(incrementValue) || incrementValue < 1) {\n    throw new InvalidDurationError({\n      reason:\n        `temporal-fmt: roundDuration() requires a positive roundingIncrement (got ${String(incrementValue)})` +\n        (Number.isInteger(incrementValue) ? '' : ' — fractional increments aren\\'t supported here (use round() for those)'),\n    });\n  }\n  const increment = BigInt(incrementValue);\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 += fieldToNs(v, DURATION_UNIT_TO_NS[u], u);\n  }\n\n  // Apply rounding on the SIGNED total (see applyMode for why the\n  // abs-then-sign form was wrong for negatives: floor/ceil inverted,\n  // ties asymmetric). BigInt division truncates toward zero, so floor\n  // and ceil need explicit adjustment for negative remainders, and\n  // 'nearest' follows Math.round's ties-toward-+∞ exactly (r of exactly\n  // −½ steps stays at the truncation quotient, which is the higher\n  // boundary — matching round() above instead of the old away-from-zero\n  // tie break that disagreed with it for negative values).\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    // floor/ceil are computed from the truncating quotient adjusted by\n    // the SIGN of totalNs: floor moves toward −∞ (one step earlier for a\n    // negative value with a remainder), ceil toward +∞.\n    case 'floor': rounded = sign < 0n && remainder > 0n ? stepped + 1n : stepped; break;\n    case 'ceil': rounded = sign > 0n && 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 so\n  // the balanced output has all its value concentrated at the target unit\n  // and below.\n  const unitsInOrder: DurationUnit[] = ['days', 'hours', 'minutes', 'seconds', 'milliseconds', 'microseconds', 'nanoseconds'];\n  const startIdx = unitsInOrder.indexOf(options.unit);\n  // Units finer than the rounding target are zeroed: all their value was\n  // folded into `remaining` above, and whatever survives rounding now\n  // lives at the target unit and above. (Units coarser than the target\n  // are re-written by the distribution loop below — their value is\n  // already inside `remaining`, so the spread copy is overwritten with\n  // the same number.)\n  for (let i = startIdx + 1; i < unitsInOrder.length; i++) {\n    result[unitsInOrder[i]!] = 0;\n  }\n  // Re-populate from the largest absolute unit down to the target, so the\n  // result has all its value concentrated at the target unit and above.\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    // Assign unconditionally (zero counts write 0): the units at and above\n    // the target are fully re-derived from the rounded total, so any\n    // original spread-copied value must be overwritten either way —\n    // conditionally skipping zero counts would leave the caller's stale\n    // value sitting in the field.\n    result[u] = Number(count);\n  }\n  return result;\n}","/*\n * Copyright 2026 DirazCoder\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// format strings are basically always short hand-written stuff like \"yyyy-MM-dd\"\n// so if something's way longer than that, cap it before tokenize() chokes on it\nexport const MAX_FORMAT_LENGTH = 1000;\nexport const MAX_INPUT_LENGTH = 100_000;","/*\n * Copyright 2026 DirazCoder\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// Name lists for the locale-aware tokens (MMMM, MMM, EEEE, EEE, a). Each\n// list is small and fixed (12 months, 7 weekdays, 2 day periods), so we\n// generate the real Intl strings for a locale once and cache them.\n\nimport { InvalidLocaleError } from './errors.js';\n\nexport interface LocaleVocab {\n  monthLong: string[]; // index 0 = January\n  monthShort: string[];\n  weekdayLong: string[]; // index 0 = Monday, per Temporal's dayOfWeek numbering\n  weekdayShort: string[];\n  dayPeriod: string[]; // typically [AM-ish, PM-ish], deduped\n}\n\n// Custom vocabs registered by callers for locales Intl doesn't cover well\n// (e.g. a 13-month Hebrew leap year, where Intl's 12-month vocabulary\n// silently loses a whole month). Keyed by canonical cache key so the\n// same locale string spelling variants fold together — same convention\n// as the Intl-derived vocab cache above.\nconst customVocabs = new Map<string, LocaleVocab>();\nconst MAX_CUSTOM_VOCABS = 500;\nconst MAX_LOCALE_TAG_LENGTH = 256;\nconst MAX_VOCAB_ENTRY_LENGTH = 256;\n\nfunction assertValidVocab(vocab: Partial<LocaleVocab>, locale: string): void {\n  // Strict shape validation at registration time, not lazily on first\n  // use — the README's promise is that a malformed registration throws\n  // descriptively here, rather than failing later inside format()/parse()\n  // with a confusing \"no month part\" or wrong-month error the caller\n  // can't trace back to the bad registration.\n  const required: Array<{ key: keyof LocaleVocab; length: number; label: string }> = [\n    { key: 'monthLong', length: 12, label: 'long month names' },\n    { key: 'monthShort', length: 12, label: 'short month names' },\n    { key: 'weekdayLong', length: 7, label: 'long weekday names' },\n    { key: 'weekdayShort', length: 7, label: 'short weekday names' },\n    { key: 'dayPeriod', length: 2, label: 'day period markers (AM/PM-equivalent)' },\n  ];\n\n  for (const { key, length, label } of required) {\n    const value = vocab[key];\n    if (value === undefined) {\n      throw new Error(\n        `temporal-fmt: registerLocaleVocab for locale \"${locale}\" is missing required field \"${key}\" (${label}).`\n      );\n    }\n    if (!Array.isArray(value)) {\n      throw new Error(\n        `temporal-fmt: registerLocaleVocab for locale \"${locale}\": \"${key}\" must be an array, got ${typeof value}.`\n      );\n    }\n    if (value.length !== length) {\n      throw new Error(\n        `temporal-fmt: registerLocaleVocab for locale \"${locale}\": \"${key}\" must have exactly ${length} entries (got ${value.length}) — ${label}.`\n      );\n    }\n    value.forEach((entry, i) => {\n      if (typeof entry !== 'string' || entry.length === 0) {\n        throw new Error(\n          `temporal-fmt: registerLocaleVocab for locale \"${locale}\": \"${key}[${i}]\" must be a non-empty string, got ${String(entry)}.`\n        );\n      }\n      if (entry.length > MAX_VOCAB_ENTRY_LENGTH) {\n        throw new RangeError(\n          `temporal-fmt: registerLocaleVocab for locale \"${locale}\": \"${key}[${i}]\" is too long (maximum ${MAX_VOCAB_ENTRY_LENGTH} characters).`\n        );\n      }\n    });\n  }\n\n  // Reuse the same collision check the Intl-derived path uses — a\n  // duplicate month name is just as ambiguous when supplied by a caller\n  // as when produced by Intl.\n  assertNoCollision(vocab.monthLong!, 'MMMM month', locale);\n  assertNoCollision(vocab.monthShort!, 'MMM month', locale);\n  assertNoCollision(vocab.weekdayLong!, 'EEEE weekday', locale);\n  assertNoCollision(vocab.weekdayShort!, 'EEE weekday', locale);\n\n  // dayPeriod entries must differ from each other, or parse()'s\n  // isPM check (raw === vocab.dayPeriod[1]) can never return true and\n  // every 12-hour parse silently resolves to AM. The Intl-derived path\n  // dedupes a same-AM/PM collision to length 1, but a caller passing\n  // both entries identical is a real bug to surface — not something to\n  // dedupe around.\n  if (vocab.dayPeriod![0] === vocab.dayPeriod![1]) {\n    throw new Error(\n      `temporal-fmt: registerLocaleVocab for locale \"${locale}\": dayPeriod entries must differ ` +\n      `(both are \"${vocab.dayPeriod![0]}\"); otherwise parse() can't tell AM from PM.`\n    );\n  }\n}\n\n// Anything that caches a result derived from *which* vocabulary is\n// active for a locale (right now: parse.ts's compiled pattern cache,\n// whose MMMM/MMM/EEEE/EEE/a fragments embed the vocab's alternations)\n// subscribes here so it gets invalidated when a registration swaps the\n// vocab — mirrors temporalProvider.ts's subscribeToTemporalChanges.\nconst onVocabChanged: Array<() => void> = [];\n\nexport function subscribeToVocabChanges(listener: () => void): void {\n  onVocabChanged.push(listener);\n}\n\n/**\n * Supply a custom month/weekday/day-period vocabulary for a locale key,\n * overriding the Intl-derived vocab this library would otherwise build\n * for that key. Useful for locales Intl doesn't cover well — the\n * Parsing section of the README (\"MMMM/MMM assume a 12-month calendar\")\n * calls out the Hebrew leap-month gap as a specific case this addresses.\n *\n * Throws descriptively on malformed input (wrong array lengths, empty\n * strings, duplicate entries, missing fields) rather than failing later\n * during format/parse.\n *\n * Registered vocab takes precedence over the Intl-derived vocab for that\n * locale key, including for already-cached entries — registering\n * invalidates the prior cache entry for that locale so the next call\n * picks up the new vocab.\n *\n * @example\n * registerLocaleVocab('en-u-ca-hebrew-leap', {\n *   monthLong: ['Nisan','Iyar','Sivan','Tammuz','Av','Elul','Tishrei','Marcheshvan','Kislev','Tevet','Shevat','Adar I','Adar II'],\n *   monthShort: ['Nis','Iyy','Siv','Tam','Av','Elu','Tish','Chesh','Kis','Tev','Shv','Ad1','Ad2'],\n *   weekdayLong: ['Monday','Tuesday','Wednesday','Thursday','Friday','Saturday','Sunday'],\n *   weekdayShort: ['Mon','Tue','Wed','Thu','Fri','Sat','Sun'],\n *   dayPeriod: ['AM','PM'],\n * });\n */\nexport function registerLocaleVocab(locale: string, vocab: Partial<LocaleVocab>): void {\n  if (typeof locale !== 'string' || locale.length === 0) {\n    throw new Error(`temporal-fmt: registerLocaleVocab requires a non-empty locale string, got ${String(locale)}.`);\n  }\n  if (locale.length > MAX_LOCALE_TAG_LENGTH) {\n    throw new RangeError(`temporal-fmt: registerLocaleVocab locale is too long (maximum ${MAX_LOCALE_TAG_LENGTH} characters).`);\n  }\n  assertValidVocab(vocab, locale);\n\n  const cacheKey = canonicalCacheKey(locale);\n  if (!customVocabs.has(cacheKey) && customVocabs.size >= MAX_CUSTOM_VOCABS) {\n    throw new RangeError(`temporal-fmt: registerLocaleVocab reached the ${MAX_CUSTOM_VOCABS}-locale limit.`);\n  }\n  customVocabs.set(cacheKey, {\n    monthLong: [...vocab.monthLong!],\n    monthShort: [...vocab.monthShort!],\n    weekdayLong: [...vocab.weekdayLong!],\n    weekdayShort: [...vocab.weekdayShort!],\n    dayPeriod: [...vocab.dayPeriod!],\n  });\n  // Invalidate the Intl-derived cache entry so any prior format/parse\n  // result cached for this locale is rebuilt against the new vocab. Not\n  // strictly necessary (getLocaleVocab checks customVocabs first), but\n  // cheap and keeps the two caches from drifting out of sync.\n  vocabCache.delete(cacheKey);\n  // Also invalidate downstream caches that baked the previous vocab in\n  // at build time (parse.ts's pattern cache is the one that matters:\n  // without this, a cached pattern would keep matching the OLD month /\n  // weekday names while format() renders the new ones, so the library's\n  // own format() output would fail to parse back).\n  for (const listener of onVocabChanged) listener();\n}\n\n// Every locale-keyed cache in this library (this one, formatterCache in\n// tokens.ts, patternCache/calendarCache in parse.ts) used to key on the\n// exact locale string a caller passed in. Intl treats spelling variants of\n// the same locale as equivalent ('en-US' / 'en-us' / 'en_US' all resolve\n// the same way), but a plain string-keyed Map doesn't — so callers mixing\n// spellings for what's really one locale would silently fragment across\n// separate cache entries instead of sharing one, making the bounded\n// eviction limits (MAX_*_CACHE_SIZE) less effective than they look. This\n// doesn't change any cache's *correctness* (each entry is still built from\n// -- and valid for -- whatever locale string produced it), only how many\n// distinct entries equivalent spellings end up costing. Falls back to the\n// original string on a malformed/unrecognized tag rather than throwing —\n// cache-key normalization shouldn't be where a bad locale first surfaces\n// as an error; whatever actually calls `new Intl.DateTimeFormat(locale)`\n// downstream is the right place for that.\n// Intl constructors (DateTimeFormat/NumberFormat/RelativeTimeFormat)\n// reject underscore-separated tags like 'en_US' outright, while\n// canonicalCacheKey and every locale-parsing path in this library\n// tolerates them by normalizing to BCP-47 hyphens. Centralize that\n// normalization so every `new Intl.*(locale)` construction site\n// accepts the same spellings the cache keys do. Genuinely malformed\n// tags still throw downstream, unchanged.\nexport function normalizeLocaleTag(locale: string): string {\n  return locale.replace(/_/g, '-');\n}\n\n// Memoized so hot paths (parse()'s resolveCalendar keys off this on\n// every call, and every locale-keyed cache re-derives it) don't build a\n// fresh Intl.Locale per invocation — construction is comparatively\n// expensive. Bounded like every other cache in this library.\nconst canonicalKeyCache = new Map<string, string>();\nconst MAX_CANONICAL_KEY_CACHE = 500;\n\nexport function canonicalCacheKey(locale: string): string {\n  const hit = canonicalKeyCache.get(locale);\n  if (hit !== undefined) return hit;\n  let key: string;\n  try {\n    // Intl.Locale requires BCP-47 hyphens and rejects underscore-separated\n    // tags like 'en_US' outright (RangeError), rather than normalizing\n    // them — so without this replace, that spelling would just fall\n    // through to the catch below and never fold with 'en-US'.\n    key = new Intl.Locale(locale.replace(/_/g, '-')).toString().toLowerCase();\n  } catch {\n    // Malformed tag: key on the raw string. Callers that must reject\n    // malformed tags do it via assertValidLocaleTag() before/instead of\n    // relying on this function — cache-key normalization isn't where a\n    // bad locale should surface as an error (unchanged behavior).\n    key = locale;\n  }\n  if (canonicalKeyCache.size >= MAX_CANONICAL_KEY_CACHE) {\n    const oldestKey = canonicalKeyCache.keys().next().value;\n    if (oldestKey !== undefined) canonicalKeyCache.delete(oldestKey);\n  }\n  canonicalKeyCache.set(locale, key);\n  return key;\n}\n\n// Single validation choke point for locale tags at public boundaries.\n// Intl constructors reject malformed tags with a bare engine RangeError\n// (\"Incorrect locale information provided\") that carries none of the\n// library's structured error context; this rethrows as the typed\n// InvalidLocaleError with the offending tag in its fields. Accepts the\n// same spellings as canonicalCacheKey (underscore tags normalized).\nexport function assertValidLocaleTag(locale: string): void {\n  try {\n    new Intl.Locale(normalizeLocaleTag(locale));\n  } catch {\n    throw new InvalidLocaleError({\n      actual: locale,\n      reason: 'not a valid BCP-47 locale tag',\n    });\n  }\n}\n\nconst vocabCache = new Map<string, LocaleVocab>();\nconst MAX_VOCAB_CACHE_SIZE = 500;\n\n// Some locales (ja-JP) split a field across two parts — month \"8\" plus a\n// counter suffix \"月\" as a separate sibling \"literal\" — while format()'s\n// post-1582 tokens go through toLocaleString(), which concatenates\n// everything into \"8月\", and the pre-1582 path in tokens.ts\n// (preCutoverGregorianName) formats one field at a time the same way this\n// does. Reading only the type-tagged part used to drop that suffix, so\n// this locale's vocab never matched what format() actually produced.\n// Only merges *adjacent* literals, not the whole string, since the\n// dayPeriod/weekday formatters below carry an extra hour part that a\n// join-everything approach would wrongly absorb.\nexport function partValue(formatter: Intl.DateTimeFormat, date: Date, type: Intl.DateTimeFormatPartTypes): string {\n  const parts = formatter.formatToParts(date);\n  const index = parts.findIndex((p) => p.type === type);\n  /* c8 ignore start @preserve -- defensive guard against a real but\n     unreproducible failure mode: an Intl implementation that omits the\n     requested part type entirely for some locale. Checked every locale\n     with unusual dayPeriod/weekday/month rendering available in this\n     runtime's ICU data (ja-JP, zh-CN, th-TH, he-IL, ar-SA, ko-KR, fa-IR)\n     against the exact formatter options getLocaleVocab uses (notably\n     hour12: true for the dayPeriod formatter, which is what makes every\n     locale here actually emit a dayPeriod part — omitting it is what\n     produced a false \"gap\" during investigation). None omit their part\n     on this runtime. A different ICU version or a non-Node Intl\n     implementation could plausibly behave differently, so this stays a\n     real check rather than an assertion. */\n  /* c8 ignore start @preserve */\n  if (index === -1) {\n    throw new InvalidLocaleError({\n      message: `temporal-fmt: locale produced no \"${type}\" part while building match vocabulary.`,\n    });\n  }\n  /* c8 ignore stop @preserve */\n  let value = parts[index]!.value;\n  const prev = parts[index - 1];\n  const next = parts[index + 1];\n  // skip whitespace literals (the separator before \"AM\") — only a\n  // no-space suffix like ja-JP's \"月\" should get folded in\n  if (prev?.type === 'literal' && !/\\s/.test(prev.value)) value = prev.value + value;\n  if (next?.type === 'literal' && !/\\s/.test(next.value)) value = value + next.value;\n  return value;\n}\n\n// Two entries rendering identically means parse()'s reverse lookup\n// (indexOf) can never tell them apart. Weekday collisions already surface\n// via parse()'s dayOfWeek cross-check, but with a confusing same-string\n// error; months have no equivalent cross-check, so a collision there would\n// otherwise resolve silently to the wrong month. Catching both here, once\n// at build time, gives one clear error instead.\nfunction assertNoCollision(names: string[], label: string, locale: string): void {\n  const seen = new Map<string, number>();\n  for (let i = 0; i < names.length; i++) {\n    const prior = seen.get(names[i]!);\n    if (prior !== undefined) {\n      // Not migrated to a typed error: this function is shared between\n      // getLocaleVocab's Intl-derived path (data-path error, would be a\n      // good InvalidLocaleError candidate) and assertValidVocab's\n      // registration-time check (out of scope for this pass — see the\n      // localeVocab.ts registration-error follow-up). Splitting this into\n      // two near-duplicate functions just to route error types\n      // differently isn't worth it for one throw; revisit together with\n      // the registration-error work instead.\n      throw new Error(\n        `temporal-fmt: locale \"${locale}\" renders ${label} index ${prior} and ${i} identically ` +\n        `(\"${names[i]}\"). parse() can't reliably tell these apart for this locale/token, so this ` +\n        `combination isn't supported.`\n      );\n    }\n    seen.set(names[i]!, i);\n  }\n}\n\n// Exposed for tokens.ts: when a custom vocab is registered for this\n// locale, format()'s locale-aware tokens (MMMM/MMM/EEEE/EEE/a) read\n// straight from the registered array instead of going through\n// Intl.DateTimeFormat. Without this override, format() would silently\n// keep producing Intl's strings while parse() matched against the\n// registered vocab — the two would round-trip-fail.\nexport function getCustomVocab(locale: string): LocaleVocab | undefined {\n  const cacheKey = canonicalCacheKey(locale);\n  return customVocabs.get(cacheKey);\n}\n\nexport function getLocaleVocab(locale: string): LocaleVocab {\n  // Validate before building vocab: a malformed tag used to surface as a\n  // raw RangeError from the first new Intl.DateTimeFormat below.\n  assertValidLocaleTag(locale);\n  const cacheKey = canonicalCacheKey(locale);\n  // Registered vocabs take precedence over the Intl-derived one — this\n  // is the override mechanism registerLocaleVocab() promises. Checking\n  // here, before the Intl cache lookup, means a registration that\n  // happens *after* the first Intl-derived vocab was built still takes\n  // effect on the next call.\n  const custom = customVocabs.get(cacheKey);\n  if (custom) {\n    return custom;\n  }\n  const cached = vocabCache.get(cacheKey);\n  if (cached) {\n    return cached;\n  }\n\n  const intlLocale = normalizeLocaleTag(locale);\n  const monthLongFmt = new Intl.DateTimeFormat(intlLocale, { month: 'long', timeZone: 'UTC' });\n  const monthShortFmt = new Intl.DateTimeFormat(intlLocale, { month: 'short', timeZone: 'UTC' });\n  const monthLong: string[] = [];\n  const monthShort: string[] = [];\n  for (let m = 0; m < 12; m++) {\n    const date = new Date(Date.UTC(2020, m, 1));\n    monthLong.push(partValue(monthLongFmt, date, 'month'));\n    monthShort.push(partValue(monthShortFmt, date, 'month'));\n  }\n  assertNoCollision(monthLong, 'MMMM month', locale);\n  assertNoCollision(monthShort, 'MMM month', locale);\n\n  const weekdayLongFmt = new Intl.DateTimeFormat(intlLocale, { weekday: 'long', timeZone: 'UTC' });\n  const weekdayShortFmt = new Intl.DateTimeFormat(intlLocale, { weekday: 'short', timeZone: 'UTC' });\n  const weekdayLong: string[] = [];\n  const weekdayShort: string[] = [];\n  // 2024-01-01 is a Monday (UTC) — walk 7 days from there for weekday names\n  for (let d = 0; d < 7; d++) {\n    const date = new Date(Date.UTC(2024, 0, 1 + d));\n    weekdayLong.push(partValue(weekdayLongFmt, date, 'weekday'));\n    weekdayShort.push(partValue(weekdayShortFmt, date, 'weekday'));\n  }\n  // redundant with parse()'s dayOfWeek cross-check, but gives a clearer error\n  assertNoCollision(weekdayLong, 'EEEE weekday', locale);\n  assertNoCollision(weekdayShort, 'EEE weekday', locale);\n\n  const dayPeriodFmt = new Intl.DateTimeFormat(intlLocale, { hour: 'numeric', hour12: true, timeZone: 'UTC' });\n  const am = partValue(dayPeriodFmt, new Date(Date.UTC(2020, 0, 1, 1)), 'dayPeriod');\n  const pm = partValue(dayPeriodFmt, new Date(Date.UTC(2020, 0, 1, 13)), 'dayPeriod');\n  const dayPeriod = [...new Set([am, pm])];\n\n  const vocab: LocaleVocab = { monthLong, monthShort, weekdayLong, weekdayShort, dayPeriod };\n  if (vocabCache.size >= MAX_VOCAB_CACHE_SIZE) {\n    const oldestKey = vocabCache.keys().next().value;\n    if (oldestKey !== undefined) vocabCache.delete(oldestKey);\n  }\n  vocabCache.set(cacheKey, vocab);\n  return vocab;\n}","/*\n * Copyright 2026 DirazCoder\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { type FormatOptions } from './tokens.js';\nimport { tokenize } from './tokenize.js';\nimport { MAX_FORMAT_LENGTH } from './constants.js';\nimport { canonicalCacheKey, normalizeLocaleTag } from './localeVocab.js';\nimport { InvalidLocaleError } from './errors.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  let formatter: Intl.NumberFormat;\n  try {\n    formatter = new Intl.NumberFormat(normalizeLocaleTag(locale), {\n      style: 'unit',\n      unit: intlUnit,\n      unitDisplay,\n    });\n  } catch (err) {\n    // Malformed locale tags reach Intl as a bare RangeError; surface the\n    // library's typed error instead. (Every failure mode of these\n    // constructors with a string locale is a RangeError, so converting\n    // unconditionally loses nothing — the original message is preserved\n    // in `reason` either way.)\n    throw new InvalidLocaleError({ actual: locale, reason: (err as Error).message });\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  return renderDurationPieces(duration, formatStr, options).map((p) => p.value).join('');\n}\n\n// One rendered piece of a duration format string: either literal text or\n// a token with its already-rendered value (empty string for a\n// zero-suppressed token, matching formatDurationToParts' documented\n// contract of emitting an empty token part rather than dropping it).\nexport interface RenderedDurationPiece {\n  kind: 'literal' | 'token';\n  value: string;\n  token?: string;\n}\n\n// Shared renderer behind both formatDuration() (joins the pieces) and\n// formatDurationToParts() (returns them). Single source of truth, so the\n// joined string and the parts can never disagree — the old\n// formatDurationToParts re-derived part boundaries by searching the\n// rendered string with indexOf and mis-sliced whenever a literal's text\n// also occurred inside an earlier token's rendered value.\nexport function renderDurationPieces(\n  duration: Record<string, unknown>,\n  formatStr: string,\n  options: DurationFormatOptions = {},\n): RenderedDurationPiece[] {\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  const out: RenderedDurationPiece[] = [];\n\n  for (const piece of pieces) {\n    if (piece.kind === 'literal') {\n      out.push({ kind: 'literal', value: 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      out.push({ kind: 'token', value: '', token: piece.value });\n      continue;\n    }\n\n    if (piece.form === 'numeric') {\n      out.push({ kind: 'token', value: String(value), token: piece.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      out.push({ kind: 'token', value: formatter.format(value), token: piece.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      out.push({ kind: 'token', value: value + (value === 1 || value === -1 ? unit.shortSingular : unit.shortPlural), token: piece.value });\n    } else {\n      out.push({ kind: 'token', value: value + ' ' + (value === 1 || value === -1 ? unit.longSingular : unit.longPlural), token: piece.value });\n    }\n  }\n\n  return out;\n}"],"mappings":"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","names":["DURATION_TOKEN_STRINGS","appendLiteral"]}