temporal-fmt 0.9.1 → 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.
Files changed (78) hide show
  1. package/dist/chunk-2JHEV7ZF.js +7 -0
  2. package/dist/chunk-2JHEV7ZF.js.map +1 -0
  3. package/dist/{chunk-DXLBLPLQ.js → chunk-4JWGUR4O.js} +2 -2
  4. package/dist/{chunk-H52AMABK.js → chunk-5U5WJ465.js} +6 -6
  5. package/dist/chunk-5U5WJ465.js.map +1 -0
  6. package/dist/chunk-C5YESWFT.js +2 -0
  7. package/dist/chunk-C5YESWFT.js.map +1 -0
  8. package/dist/chunk-F4RGUDA3.js +2 -0
  9. package/dist/chunk-F4RGUDA3.js.map +1 -0
  10. package/dist/chunk-NBXF7V5B.js +2 -0
  11. package/dist/chunk-NBXF7V5B.js.map +1 -0
  12. package/dist/{chunk-M5WL5VGW.js → chunk-R52YOKI3.js} +2 -2
  13. package/dist/chunk-UJR6DHXY.js +2 -0
  14. package/dist/chunk-UJR6DHXY.js.map +1 -0
  15. package/dist/{chunk-6UNSCZPL.js → chunk-XH46JNPK.js} +2 -2
  16. package/dist/chunk-XH46JNPK.js.map +1 -0
  17. package/dist/chunk-YB6YAG7E.js +35 -0
  18. package/dist/chunk-YB6YAG7E.js.map +1 -0
  19. package/dist/chunk-ZGMTY52O.js +2 -0
  20. package/dist/chunk-ZGMTY52O.js.map +1 -0
  21. package/dist/duration.cjs +4 -10
  22. package/dist/duration.cjs.map +1 -1
  23. package/dist/duration.js +1 -1
  24. package/dist/format.cjs +6 -6
  25. package/dist/format.cjs.map +1 -1
  26. package/dist/format.js +1 -1
  27. package/dist/formatDuration.d.cts +6 -0
  28. package/dist/formatDuration.d.ts +6 -0
  29. package/dist/index.cjs +37 -43
  30. package/dist/index.cjs.map +1 -1
  31. package/dist/index.js +15 -15
  32. package/dist/index.js.map +1 -1
  33. package/dist/interval.cjs +6 -6
  34. package/dist/interval.cjs.map +1 -1
  35. package/dist/interval.js +1 -1
  36. package/dist/localeRegistry.cjs +4 -4
  37. package/dist/localeRegistry.cjs.map +1 -1
  38. package/dist/localeRegistry.js +1 -1
  39. package/dist/localeVocab.d.cts +3 -0
  40. package/dist/localeVocab.d.ts +3 -0
  41. package/dist/parse.cjs +9 -9
  42. package/dist/parse.cjs.map +1 -1
  43. package/dist/parse.js +1 -1
  44. package/dist/parsePattern.d.cts +1 -0
  45. package/dist/parsePattern.d.ts +1 -0
  46. package/dist/pattern.d.cts +1 -0
  47. package/dist/pattern.d.ts +1 -0
  48. package/dist/recurrence.cjs +4 -4
  49. package/dist/recurrence.cjs.map +1 -1
  50. package/dist/recurrence.js +1 -1
  51. package/dist/relativeTime.cjs +24 -1
  52. package/dist/relativeTime.cjs.map +1 -1
  53. package/dist/relativeTime.js +1 -1
  54. package/dist/rounding.d.cts +1 -0
  55. package/dist/rounding.d.ts +1 -0
  56. package/dist/serialization.d.cts +1 -1
  57. package/dist/serialization.d.ts +1 -1
  58. package/dist/typeGuards.d.cts +9 -9
  59. package/dist/typeGuards.d.ts +9 -9
  60. package/package.json +1 -1
  61. package/dist/chunk-6JAWE65A.js +0 -2
  62. package/dist/chunk-6JAWE65A.js.map +0 -1
  63. package/dist/chunk-6UNSCZPL.js.map +0 -1
  64. package/dist/chunk-CUTVOO4Y.js +0 -35
  65. package/dist/chunk-CUTVOO4Y.js.map +0 -1
  66. package/dist/chunk-H52AMABK.js.map +0 -1
  67. package/dist/chunk-ILXRFTAE.js +0 -13
  68. package/dist/chunk-ILXRFTAE.js.map +0 -1
  69. package/dist/chunk-LYWOLBMV.js +0 -2
  70. package/dist/chunk-LYWOLBMV.js.map +0 -1
  71. package/dist/chunk-QASXSTBN.js +0 -2
  72. package/dist/chunk-QASXSTBN.js.map +0 -1
  73. package/dist/chunk-YAVXHM4W.js +0 -2
  74. package/dist/chunk-YAVXHM4W.js.map +0 -1
  75. package/dist/chunk-ZYVMZRMR.js +0 -2
  76. package/dist/chunk-ZYVMZRMR.js.map +0 -1
  77. /package/dist/{chunk-DXLBLPLQ.js.map → chunk-4JWGUR4O.js.map} +0 -0
  78. /package/dist/{chunk-M5WL5VGW.js.map → chunk-R52YOKI3.js.map} +0 -0
package/dist/duration.cjs CHANGED
@@ -1,4 +1,4 @@
1
- "use strict";var _=Object.defineProperty;var O=Object.getOwnPropertyDescriptor;var U=Object.getOwnPropertyNames;var V=Object.prototype.hasOwnProperty;var R=(e,t)=>{for(var n in t)_(e,n,{get:t[n],enumerable:!0})},v=(e,t,n,o)=>{if(t&&typeof t=="object"||typeof t=="function")for(let r of U(t))!V.call(e,r)&&r!==n&&_(e,r,{get:()=>t[r],enumerable:!(o=O(t,r))||o.enumerable});return e};var C=e=>v(_({},"__esModule",{value:!0}),e);var se={};R(se,{addDuration:()=>P,balanceDuration:()=>ee,compareDuration:()=>ne,formatDurationToParts:()=>X,formatISODuration:()=>J,parseDuration:()=>Q,parseISODuration:()=>Y,roundDuration:()=>$,subtractDuration:()=>oe,totalDuration:()=>te});module.exports=C(se);var b={years:0n,months:0n,weeks:0n,days:86400n*1000000000n,hours:3600n*1000000000n,minutes:60n*1000000000n,seconds:1000000000n,milliseconds:1000000n,microseconds:1000n,nanoseconds:1n};function z(e){return e==="years"||e==="months"||e==="weeks"}function $(e,t){let n=t.mode??"nearest",o=BigInt(t.roundingIncrement??1);if(o<=0n)throw new Error(`temporal-fmt: roundDuration() requires a positive roundingIncrement (got ${t.roundingIncrement}).`);if(z(t.unit))throw new Error(`temporal-fmt: roundDuration() to "${t.unit}" requires a Temporal.Duration with a relativeTo \u2014 this helper operates on plain field bags without calendar context. Use Temporal.Duration.prototype.round() directly.`);let r=0n;for(let d of["days","hours","minutes","seconds","milliseconds","microseconds","nanoseconds"]){let y=e[d]??0;r+=BigInt(y)*b[d]}let s=o*b[t.unit],i=r<0n?-1n:1n,a=r<0n?-r:r,u=a/s,l=a%s,c;switch(n){case"nearest":c=l*2n>=s?u+1n:u;break;case"floor":c=u;break;case"ceil":c=l>0n?u+1n:u;break;case"trunc":c=u;break}let h=i*c*s,m={...e},E=h;for(let d of["days","hours","minutes","seconds","milliseconds","microseconds","nanoseconds"])d===t.unit||K(d,t.unit)||t.unit,m[d]=0;let M=["days","hours","minutes","seconds","milliseconds","microseconds","nanoseconds"],S=M.indexOf(t.unit);for(let d=0;d<=S;d++){let y=M[d],k=b[y];if(k===0n)continue;let D=E/k;E-=D*k,D!==0n&&(m[y]=Number(D))}return m}function K(e,t){return!1}var w=class extends Error{code;input;format;token;position;expected;actual;reason;constructor(t,n){super(t),this.name="TemporalFmtError",this.code=n.code,this.input=n.input,this.format=n.format,this.token=n.token,this.position=n.position,this.expected=n.expected,this.actual=n.actual,this.reason=n.reason;let o=Error.captureStackTrace;typeof o=="function"&&o(this,this.constructor)}toJSON(){return{name:this.name,message:this.message,code:this.code,input:this.input,format:this.format,token:this.token,position:this.position,expected:this.expected,actual:this.actual,reason:this.reason}}},f=class extends w{constructor(t){let{message:n,...o}=t;super(n??`format string "${t.format??""}" has a syntax error${t.reason?`: ${t.reason}`:""}.`,{code:"FORMAT_SYNTAX_ERROR",...o}),this.name="FormatSyntaxError"}};/* c8 ignore start @preserve -- InvalidTimeError is part of the public
1
+ "use strict";var T=Object.defineProperty;var R=Object.getOwnPropertyDescriptor;var U=Object.getOwnPropertyNames;var C=Object.prototype.hasOwnProperty;var z=(e,t)=>{for(var n in t)T(e,n,{get:t[n],enumerable:!0})},K=(e,t,n,o)=>{if(t&&typeof t=="object"||typeof t=="function")for(let r of U(t))!C.call(e,r)&&r!==n&&T(e,r,{get:()=>t[r],enumerable:!(o=R(t,r))||o.enumerable});return e};var Z=e=>K(T({},"__esModule",{value:!0}),e);var ue={};z(ue,{addDuration:()=>v,balanceDuration:()=>oe,compareDuration:()=>se,formatDurationToParts:()=>J,formatISODuration:()=>ee,parseDuration:()=>ne,parseISODuration:()=>Q,roundDuration:()=>A,subtractDuration:()=>ie,totalDuration:()=>re});module.exports=Z(ue);var p=class extends Error{code;input;format;token;position;expected;actual;reason;constructor(t,n){super(t),this.name="TemporalFmtError",this.code=n.code,this.input=n.input,this.format=n.format,this.token=n.token,this.position=n.position,this.expected=n.expected,this.actual=n.actual,this.reason=n.reason;let o=Error.captureStackTrace;typeof o=="function"&&o(this,this.constructor)}toJSON(){return{name:this.name,message:this.message,code:this.code,input:this.input,format:this.format,token:this.token,position:this.position,expected:this.expected,actual:this.actual,reason:this.reason}}},f=class extends p{constructor(t){let{message:n,...o}=t;super(n??`format string "${t.format??""}" has a syntax error${t.reason?`: ${t.reason}`:""}.`,{code:"FORMAT_SYNTAX_ERROR",...o}),this.name="FormatSyntaxError"}};/* c8 ignore start @preserve -- InvalidTimeError is part of the public
2
2
  error-class surface (exported from index.ts, code 'INVALID_TIME')
3
3
  but nothing in this package constructs one. Investigated wiring it
4
4
  in the same way InvalidTimeZoneError was just wired (see parse.ts's
@@ -20,17 +20,11 @@
20
20
  of the locale string — it's never handed an arbitrary "calendar"
21
21
  value a caller could get wrong. There's no live input to reject.
22
22
  Left unconstructed until the library actually accepts a calendar
23
- parameter that could be invalid. *//* c8 ignore stop @preserve */var g=class extends w{constructor(t){let{message:n,...o}=t;super(n??`duration is invalid${t.reason?`: ${t.reason}`:""}.`,{code:"INVALID_DURATION",...o}),this.name="InvalidDurationError"}};/* c8 ignore start @preserve -- unreachable from the current test suite,
24
- see rationale above *//* c8 ignore stop @preserve */function I(e){try{return new Intl.Locale(e.replace(/_/g,"-")).toString().toLowerCase()}catch{return e}}var N={y:{longSingular:"year",longPlural:"years",shortSingular:"yr",shortPlural:"yrs",field:"years",intlUnit:"year"},o:{longSingular:"month",longPlural:"months",shortSingular:"mo",shortPlural:"mos",field:"months",intlUnit:"month"},w:{longSingular:"week",longPlural:"weeks",shortSingular:"wk",shortPlural:"wks",field:"weeks",intlUnit:"week"},d:{longSingular:"day",longPlural:"days",shortSingular:"d",shortPlural:"d",field:"days",intlUnit:"day"},h:{longSingular:"hour",longPlural:"hours",shortSingular:"h",shortPlural:"h",field:"hours",intlUnit:"hour"},m:{longSingular:"minute",longPlural:"minutes",shortSingular:"m",shortPlural:"m",field:"minutes",intlUnit:"minute"},s:{longSingular:"second",longPlural:"seconds",shortSingular:"s",shortPlural:"s",field:"seconds",intlUnit:"second"},S:{longSingular:"millisecond",longPlural:"milliseconds",shortSingular:"ms",shortPlural:"ms",field:"milliseconds",intlUnit:"millisecond"}},Z=Object.keys(N).flatMap(e=>[e+e+e,e+e,e]).sort((e,t)=>t.length-e.length);function q(e){let t=[],n=0;for(;n<e.length;){let o=e[n];if(o==="'"){if(e[n+1]==="'"){F(t,"'"),n+=2;continue}let s=n+1,i="",a=!1;for(;s<e.length;){if(e[s]==="'"){if(e[s+1]==="'"){i+="'",s+=2;continue}a=!0,s+=1;break}i+=e[s],s+=1}if(!a)throw new Error(`temporal-fmt: unterminated quote in duration format string "${e}"`);F(t,i),n=s;continue}let r=Z.find(s=>e.startsWith(s,n));if(r){let s=r[0],i=r.length===1?"numeric":r.length===2?"short":"long";t.push({kind:"token",value:r,unit:s,form:i}),n+=r.length;continue}F(t,o),n+=1}return t}function F(e,t){let n=e[e.length-1];n&&n.kind==="literal"?n.value+=t:e.push({kind:"literal",value:t})}function H(e,t){let n=e[t];if(n==null)return 0;if(typeof n=="number"&&Number.isFinite(n))return n;let o=Number(n);if(!Number.isFinite(o))throw new Error(`temporal-fmt: duration field "${t}" is not a finite number (got ${String(n)}).`);return o}var p=new Map,j=200;function G(e,t,n){let o=`${I(e)}|${t}|${n}`,r=p.get(o);if(r)return r;if(p.size>=j){let i=p.keys().next().value;i!==void 0&&p.delete(i)}let s=new Intl.NumberFormat(e,{style:"unit",unit:t,unitDisplay:n});return p.set(o,s),s}function x(e,t,n={}){if(t.length>1e3)throw new Error(`temporal-fmt: duration format string exceeds maximum length of ${1e3} characters (got ${t.length}).`);let o=n.showZeroValues===!0,r=n.locale,s=r!==void 0,i=q(t),a="";for(let u of i){if(u.kind==="literal"){a+=u.value;continue}let l=N[u.unit];if(!l)throw new Error(`temporal-fmt: unknown duration token "${u.value}"`);let c=H(e,l.field);if(!(c===0&&!o)){if(u.form==="numeric"){a+=String(c);continue}if(s){let h=u.form==="short"?"short":"long",m=G(r,l.intlUnit,h);a+=m.format(c);continue}u.form==="short"?a+=c+(c===1||c===-1?l.shortSingular:l.shortPlural):a+=c+" "+(c===1||c===-1?l.longSingular:l.longPlural)}}return a}function X(e,t,n={}){let o=x(e,t,n),r=L(t),s=[],i=0;for(let a of r)if(a.kind==="literal"){let u=a.value;if(o.slice(i,i+u.length)===u){s.push({type:"literal",value:u}),i+=u.length;/* c8 ignore start @preserve -- unreachable given current formatDuration(): every
25
- literal is appended to `full` unconditionally regardless of adjacent
26
- zero-value tokens, and pos tracking never desyncs from the true
27
- literal position (verified via adversarial testing: duplicate
28
- literals, skipped tokens before/after, ambiguous text — all still
29
- found via indexOf as expected). Kept as a guard against a future
30
- formatDuration() change that conditionally drops literals. */}/* c8 ignore stop @preserve */}else{let u=W(r,a,o,i),l=o.slice(i,u);s.push({type:"token",value:l,token:a.value}),i=u}return s}function L(e){let t=["y","o","w","d","h","m","s","S"].flatMap(r=>[r+r+r,r+r,r]).sort((r,s)=>s.length-r.length),n=[],o=0;for(;o<e.length;){let r=e[o];if(r==="'"){if(e[o+1]==="'"){T(n,"'"),o+=2;continue}let i=o+1,a="",u=!1;for(;i<e.length;){if(e[i]==="'"){if(e[i+1]==="'"){a+="'",i+=2;continue}u=!0,i+=1;break}a+=e[i],i+=1}if(!u)throw new f({format:e,reason:"unterminated quote"});T(n,a),o=i;continue}let s=t.find(i=>e.startsWith(i,o));if(s){n.push({kind:"token",value:s,unit:s[0]}),o+=s.length;continue}T(n,r),o+=1}return n}function T(e,t){let n=e[e.length-1];n&&n.kind==="literal"?n.value+=t:e.push({kind:"literal",value:t})}function W(e,t,n,o){let r=e.indexOf(t);for(let s=r+1;s<e.length;s++){let i=e[s];if(i.kind==="literal"){let a=n.indexOf(i.value,o);if(a>=0)return a}}return n.length}function Y(e){let n=/^P(?:(?:(\d+(?:\.\d+)?)Y)?(?:(\d+(?:\.\d+)?)M)?(?:(\d+(?:\.\d+)?)W)?(?:(\d+(?:\.\d+)?)D)?)?(?:T(?:(\d+(?:\.\d+)?)H)?(?:(\d+(?:\.\d+)?)M)?(?:(\d+(?:\.\d+)?)S)?)?$/.exec(e);if(!n)throw new g({input:e,reason:"does not match ISO 8601 duration grammar (P[n]Y[n]M[n]W[n]DT[n]H[n]M[n]S)"});if(n.slice(1).every(r=>r===void 0))throw new g({input:e,reason:'duration has no fields (use "P0D" for zero duration)'});let o=r=>r===void 0?0:Number(r);return{years:o(n[1]),months:o(n[2]),weeks:o(n[3]),days:o(n[4]),hours:o(n[5]),minutes:o(n[6]),seconds:o(n[7])}}function J(e){let t=["P"],n=e.years??0,o=e.months??0,r=e.weeks??0,s=e.days??0,i=e.hours??0,a=e.minutes??0,u=e.seconds??0;return n&&t.push(`${n}Y`),o&&t.push(`${o}M`),r&&t.push(`${r}W`),s&&t.push(`${s}D`),(i||a||u)&&(t.push("T"),i&&t.push(`${i}H`),a&&t.push(`${a}M`),u&&t.push(`${u}S`)),t.length===1&&t.push("0D"),t.join("")}function Q(e,t,n={}){let o=L(t),r={},s=[],i="";for(let l of o)if(l.kind==="literal")i+=l.value.replace(/[.*+?^${}()|[\]\\]/g,"\\$&");else{let c=l.unit,h=l.value.length===1?"numeric":l.value.length===2?"short":"long";/* c8 ignore start @preserve -- the final `: null` branch, and the
23
+ parameter that could be invalid. *//* c8 ignore stop @preserve */var b=class extends p{constructor(t){let{message:n,...o}=t;super(n??`locale "${t.actual??""}" is not a valid BCP-47 tag${t.reason?`: ${t.reason}`:""}.`,{code:"INVALID_LOCALE",...o}),this.name="InvalidLocaleError"}},g=class extends p{constructor(t){let{message:n,...o}=t;super(n??`duration is invalid${t.reason?`: ${t.reason}`:""}.`,{code:"INVALID_DURATION",...o}),this.name="InvalidDurationError"}};/* c8 ignore start @preserve -- unreachable from the current test suite,
24
+ see rationale above *//* c8 ignore stop @preserve */function y(e,t,n){if(Number.isInteger(e))return BigInt(e)*t;let o=e*Number(t);if(Number.isSafeInteger(o))return BigInt(o);throw new g({reason:`field "${n}" (${e}) cannot be converted to nanoseconds exactly \u2014 the value is too large for a fractional unit; use smaller units`})}var E={years:0n,months:0n,weeks:0n,days:86400n*1000000000n,hours:3600n*1000000000n,minutes:60n*1000000000n,seconds:1000000000n,milliseconds:1000000n,microseconds:1000n,nanoseconds:1n};function q(e){return e==="years"||e==="months"||e==="weeks"}function A(e,t){let n=t.mode??"nearest",o=BigInt(t.roundingIncrement??1);if(o<=0n)throw new Error(`temporal-fmt: roundDuration() requires a positive roundingIncrement (got ${t.roundingIncrement}).`);if(q(t.unit))throw new Error(`temporal-fmt: roundDuration() to "${t.unit}" requires a Temporal.Duration with a relativeTo \u2014 this helper operates on plain field bags without calendar context. Use Temporal.Duration.prototype.round() directly.`);let r=0n;for(let d of["days","hours","minutes","seconds","milliseconds","microseconds","nanoseconds"]){let _=e[d]??0;r+=y(_,E[d],d)}let s=o*E[t.unit],i=r<0n?-1n:1n,u=r<0n?-r:r,a=u/s,l=u%s,c;switch(n){case"nearest":c=l*2n>=s?a+1n:a;break;case"floor":c=a;break;case"ceil":c=l>0n?a+1n:a;break;case"trunc":c=a;break}let h=i*c*s,m={...e},I=h,D=["days","hours","minutes","seconds","milliseconds","microseconds","nanoseconds"],N=D.indexOf(t.unit);for(let d=N+1;d<D.length;d++)m[D[d]]=0;for(let d=0;d<=N;d++){let _=D[d],F=E[_];if(F===0n)continue;let x=I/F;I-=x*F,m[_]=Number(x)}return m}function L(e){return e.replace(/_/g,"-")}var w=new Map,B=500;function P(e){let t=w.get(e);if(t!==void 0)return t;let n;try{n=new Intl.Locale(e.replace(/_/g,"-")).toString().toLowerCase()}catch{n=e}if(w.size>=B){let o=w.keys().next().value;o!==void 0&&w.delete(o)}return w.set(e,n),n}var S={y:{longSingular:"year",longPlural:"years",shortSingular:"yr",shortPlural:"yrs",field:"years",intlUnit:"year"},o:{longSingular:"month",longPlural:"months",shortSingular:"mo",shortPlural:"mos",field:"months",intlUnit:"month"},w:{longSingular:"week",longPlural:"weeks",shortSingular:"wk",shortPlural:"wks",field:"weeks",intlUnit:"week"},d:{longSingular:"day",longPlural:"days",shortSingular:"d",shortPlural:"d",field:"days",intlUnit:"day"},h:{longSingular:"hour",longPlural:"hours",shortSingular:"h",shortPlural:"h",field:"hours",intlUnit:"hour"},m:{longSingular:"minute",longPlural:"minutes",shortSingular:"m",shortPlural:"m",field:"minutes",intlUnit:"minute"},s:{longSingular:"second",longPlural:"seconds",shortSingular:"s",shortPlural:"s",field:"seconds",intlUnit:"second"},S:{longSingular:"millisecond",longPlural:"milliseconds",shortSingular:"ms",shortPlural:"ms",field:"milliseconds",intlUnit:"millisecond"}},j=Object.keys(S).flatMap(e=>[e+e+e,e+e,e]).sort((e,t)=>t.length-e.length);function X(e){let t=[],n=0;for(;n<e.length;){let o=e[n];if(o==="'"){if(e[n+1]==="'"){M(t,"'"),n+=2;continue}let s=n+1,i="",u=!1;for(;s<e.length;){if(e[s]==="'"){if(e[s+1]==="'"){i+="'",s+=2;continue}u=!0,s+=1;break}i+=e[s],s+=1}if(!u)throw new Error(`temporal-fmt: unterminated quote in duration format string "${e}"`);M(t,i),n=s;continue}let r=j.find(s=>e.startsWith(s,n));if(r){let s=r[0],i=r.length===1?"numeric":r.length===2?"short":"long";t.push({kind:"token",value:r,unit:s,form:i}),n+=r.length;continue}M(t,o),n+=1}return t}function M(e,t){let n=e[e.length-1];n&&n.kind==="literal"?n.value+=t:e.push({kind:"literal",value:t})}function G(e,t){let n=e[t];if(n==null)return 0;if(typeof n=="number"&&Number.isFinite(n))return n;let o=Number(n);if(!Number.isFinite(o))throw new Error(`temporal-fmt: duration field "${t}" is not a finite number (got ${String(n)}).`);return o}var k=new Map,Y=200;function W(e,t,n){let o=`${P(e)}|${t}|${n}`,r=k.get(o);if(r)return r;if(k.size>=Y){let i=k.keys().next().value;i!==void 0&&k.delete(i)}let s;try{s=new Intl.NumberFormat(L(e),{style:"unit",unit:t,unitDisplay:n})}catch(i){throw new b({actual:e,reason:i.message})}return k.set(o,s),s}function O(e,t,n={}){if(t.length>1e3)throw new Error(`temporal-fmt: duration format string exceeds maximum length of ${1e3} characters (got ${t.length}).`);let o=n.showZeroValues===!0,r=n.locale,s=r!==void 0,i=X(t),u=[];for(let a of i){if(a.kind==="literal"){u.push({kind:"literal",value:a.value});continue}let l=S[a.unit];if(!l)throw new Error(`temporal-fmt: unknown duration token "${a.value}"`);let c=G(e,l.field);if(c===0&&!o){u.push({kind:"token",value:"",token:a.value});continue}if(a.form==="numeric"){u.push({kind:"token",value:String(c),token:a.value});continue}if(s){let h=a.form==="short"?"short":"long",m=W(r,l.intlUnit,h);u.push({kind:"token",value:m.format(c),token:a.value});continue}a.form==="short"?u.push({kind:"token",value:c+(c===1||c===-1?l.shortSingular:l.shortPlural),token:a.value}):u.push({kind:"token",value:c+" "+(c===1||c===-1?l.longSingular:l.longPlural),token:a.value})}return u}function J(e,t,n={}){return O(e,t,n).map(o=>o.kind==="token"?{type:"token",value:o.value,token:o.token}:{type:"literal",value:o.value})}function Q(e){let n=/^P(?:(?:(\d+(?:\.\d+)?)Y)?(?:(\d+(?:\.\d+)?)M)?(?:(\d+(?:\.\d+)?)W)?(?:(\d+(?:\.\d+)?)D)?)?(?:T(?:(\d+(?:\.\d+)?)H)?(?:(\d+(?:\.\d+)?)M)?(?:(\d+(?:\.\d+)?)S)?)?$/.exec(e);if(!n)throw new g({input:e,reason:"does not match ISO 8601 duration grammar (P[n]Y[n]M[n]W[n]DT[n]H[n]M[n]S)"});if(n.slice(1).every(r=>r===void 0))throw new g({input:e,reason:'duration has no fields (use "P0D" for zero duration)'});let o=r=>r===void 0?0:Number(r);return{years:o(n[1]),months:o(n[2]),weeks:o(n[3]),days:o(n[4]),hours:o(n[5]),minutes:o(n[6]),seconds:o(n[7])}}function ee(e){let t=["P"],n=e.years??0,o=e.months??0,r=e.weeks??0,s=e.days??0,i=e.hours??0,u=e.minutes??0,a=e.seconds??0;return n&&t.push(`${n}Y`),o&&t.push(`${o}M`),r&&t.push(`${r}W`),s&&t.push(`${s}D`),(i||u||a)&&(t.push("T"),i&&t.push(`${i}H`),u&&t.push(`${u}M`),a&&t.push(`${a}S`)),t.length===1&&t.push("0D"),t.join("")}function te(e){let t=["y","o","w","d","h","m","s","S"].flatMap(r=>[r+r+r,r+r,r]).sort((r,s)=>s.length-r.length),n=[],o=0;for(;o<e.length;){let r=e[o];if(r==="'"){if(e[o+1]==="'"){$(n,"'"),o+=2;continue}let i=o+1,u="",a=!1;for(;i<e.length;){if(e[i]==="'"){if(e[i+1]==="'"){u+="'",i+=2;continue}a=!0,i+=1;break}u+=e[i],i+=1}if(!a)throw new f({format:e,reason:"unterminated quote"});$(n,u),o=i;continue}let s=t.find(i=>e.startsWith(i,o));if(s){n.push({kind:"token",value:s,unit:s[0]}),o+=s.length;continue}$(n,r),o+=1}return n}function $(e,t){let n=e[e.length-1];n&&n.kind==="literal"?n.value+=t:e.push({kind:"literal",value:t})}function ne(e,t,n={}){let o=te(t),r={},s=[],i="";for(let l of o)if(l.kind==="literal")i+=l.value.replace(/[.*+?^${}()|[\]\\]/g,"\\$&");else{let c=l.unit,h=l.value.length===1?"numeric":l.value.length===2?"short":"long";/* c8 ignore start @preserve -- the final `: null` branch, and the
31
25
  `if (key === null)` throw it feeds, are both unreachable: tok.unit
32
26
  is always one of the 8 letters in DURATION_TOKEN_STRINGS (the same
33
27
  source set this ternary checks against), so `null` can't be
34
28
  produced by any token the tokenizer emits. Kept as a guard against
35
- a future letter being added to one set without the other. */let m=c==="y"?"years":c==="o"?"months":c==="w"?"weeks":c==="d"?"days":c==="h"?"hours":c==="m"?"minutes":c==="s"?"seconds":c==="S"?"milliseconds":null;if(m===null)throw new f({format:t,token:l.value,reason:"unknown duration token"});/* c8 ignore stop @preserve */h==="numeric"?i+="(-?\\d+)":i+="(\\d+)\\s*\\w*",s.push({unit:m,form:h})}let u=new RegExp(`^${i}$`,"u").exec(e);if(!u)throw new f({input:e,format:t,reason:"does not match duration format"});return s.forEach((l,c)=>{let h=u[c+1],m=Number(h);if(!Number.isFinite(m))throw new g({input:e,reason:`token for ${l.unit} matched "${h}" but isn't a finite number`});r[l.unit]=m}),r}function ee(e){let t=[["days",86400n*1000000000n],["hours",3600n*1000000000n],["minutes",60n*1000000000n],["seconds",1000000000n],["milliseconds",1000000n],["microseconds",1000n],["nanoseconds",1n]],n=0n;for(let[s,i]of t){let a=e[s];typeof a=="number"&&(n+=BigInt(a)*i)}let o={...e},r=n;for(let[s,i]of t){let a=r/i;r-=a*i,o[s]=Number(a)}return o}function te(e,t){let n={days:86400n*1000000000n,hours:3600n*1000000000n,minutes:60n*1000000000n,seconds:1000000000n,milliseconds:1000000n,microseconds:1000n,nanoseconds:1n},o=0n;for(let s of Object.keys(n)){let i=e[s];typeof i=="number"&&(o+=BigInt(i)*n[s])}let r=n[t];if(!r)throw new g({reason:`totalDuration() does not support unit "${t}" (calendar-bound units need a relativeTo)`});return Number(o)/Number(r)}function ne(e,t){let n=A(e),o=A(t);return n<o?-1:n>o?1:0}function A(e){let t={days:86400n*1000000000n,hours:3600n*1000000000n,minutes:60n*1000000000n,seconds:1000000000n,milliseconds:1000000n,microseconds:1000n,nanoseconds:1n},n=0n;for(let o of Object.keys(t)){let r=e[o];typeof r=="number"&&(n+=BigInt(r)*t[o])}return n}function P(e,t){let n=["years","months","weeks","days","hours","minutes","seconds","milliseconds","microseconds","nanoseconds"],o={};for(let r of n)o[r]=(e[r]??0)+(t[r]??0);return o}function oe(e,t){return P(e,re(t))}function re(e){let t={};for(let n of Object.keys(e))t[n]=-(e[n]??0);return t}0&&(module.exports={addDuration,balanceDuration,compareDuration,formatDurationToParts,formatISODuration,parseDuration,parseISODuration,roundDuration,subtractDuration,totalDuration});
29
+ a future letter being added to one set without the other. */let m=c==="y"?"years":c==="o"?"months":c==="w"?"weeks":c==="d"?"days":c==="h"?"hours":c==="m"?"minutes":c==="s"?"seconds":c==="S"?"milliseconds":null;if(m===null)throw new f({format:t,token:l.value,reason:"unknown duration token"});/* c8 ignore stop @preserve */h==="numeric"?i+="(-?\\d+)":i+="(\\d+)\\s*\\w*",s.push({unit:m,form:h})}let a=new RegExp(`^${i}$`,"u").exec(e);if(!a)throw new f({input:e,format:t,reason:"does not match duration format"});return s.forEach((l,c)=>{let h=a[c+1],m=Number(h);if(!Number.isFinite(m))throw new g({input:e,reason:`token for ${l.unit} matched "${h}" but isn't a finite number`});r[l.unit]=m}),r}function oe(e){let t=[["days",86400n*1000000000n],["hours",3600n*1000000000n],["minutes",60n*1000000000n],["seconds",1000000000n],["milliseconds",1000000n],["microseconds",1000n],["nanoseconds",1n]],n=0n;for(let[s,i]of t){let u=e[s];typeof u=="number"&&(n+=y(u,i,s))}let o={...e},r=n;for(let[s,i]of t){let u=r/i;r-=u*i,o[s]=Number(u)}return o}function re(e,t){let n={days:86400n*1000000000n,hours:3600n*1000000000n,minutes:60n*1000000000n,seconds:1000000000n,milliseconds:1000000n,microseconds:1000n,nanoseconds:1n},o=0n;for(let s of Object.keys(n)){let i=e[s];typeof i=="number"&&(o+=y(i,n[s],s))}let r=n[t];if(!r)throw new g({reason:`totalDuration() does not support unit "${t}" (calendar-bound units need a relativeTo)`});return Number(o)/Number(r)}function se(e,t){let n=V(e),o=V(t);return n<o?-1:n>o?1:0}function V(e){let t={days:86400n*1000000000n,hours:3600n*1000000000n,minutes:60n*1000000000n,seconds:1000000000n,milliseconds:1000000n,microseconds:1000n,nanoseconds:1n},n=0n;for(let o of Object.keys(t)){let r=e[o];typeof r=="number"&&(n+=y(r,t[o],o))}return n}function v(e,t){let n=["years","months","weeks","days","hours","minutes","seconds","milliseconds","microseconds","nanoseconds"],o={};for(let r of n)o[r]=(e[r]??0)+(t[r]??0);return o}function ie(e,t){return v(e,ae(t))}function ae(e){let t={};for(let n of Object.keys(e))t[n]=-(e[n]??0);return t}0&&(module.exports={addDuration,balanceDuration,compareDuration,formatDurationToParts,formatISODuration,parseDuration,parseISODuration,roundDuration,subtractDuration,totalDuration});
36
30
  //# sourceMappingURL=duration.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/duration.ts","../src/rounding.ts","../src/errors.ts","../src/localeVocab.ts","../src/formatDuration.ts"],"sourcesContent":["// 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","// 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}","// 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), and the two data-path throws in\n// localeVocab.ts (partValue/assertNoCollision on the getLocaleVocab side)\n// — throws these typed classes directly instead of plain\n// `new Error(message)`. Every migrated site kept its exact pre-0.9.0\n// message text — subclasses of TemporalFmtError still satisfy\n// `instanceof Error` and message-matching regexes (e.g.\n// `/token \"HH\" requires/`), so this was safe to do without a semver-major\n// bump for either check. What *is* a breaking change for 0.9.0: code that\n// specifically checked `err.constructor === Error` or `err.name ===\n// 'Error'` will see a different name now (e.g. 'FormatSyntaxError'). See\n// the 0.9.0 changelog entry.\n//\n// Not migrated, on purpose:\n// - localeVocab.ts's registration-time throws (assertValidVocab,\n// registerLocaleVocab itself) — these are config-time API-misuse\n// errors on data the developer supplies once at startup, not runtime\n// parse/format failures, so they don't fit this module's\n// TemporalFmtErrorCode taxonomy as-is. assertNoCollision is shared\n// between the registration path and the data path (getLocaleVocab), so\n// its throw stays plain `Error` for both until that split is done —\n// see the tracking note at its call site.\n//\n// wrapUntypedError() below still exists for localeVocab.ts's registration\n// throws and for anything a caller passes into safeParse()/tryParse() from\n// outside this package. On the data path, safeParse's `if (err instanceof\n// TemporalFmtError) return { ok: false, error: err }` now catches every\n// throw before it would reach the classifier, so the regex branches here\n// are effectively dead for parse/format/tokenize/pattern — left in place\n// as the fallback for anything not yet on a typed throw site.\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 parts a linter or codemod reports on.\n// `Error.captureStackTrace` is called manually (where available) so\n// the stack points at the call site, not at this constructor — same\n// pattern 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 // Avoid setting the stack to point at this constructor — keeps it\n // useful. captureStackTrace is V8-only; on other engines the\n // default stack (this constructor line) is what callers get.\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 do `err.toJSON()` for logging. Plain `Error` doesn't\n // serialize its non-enumerable fields; this picks them 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 fixes `code` so callers can switch on it without\n// re-checking message text. The constructor takes only the fields\n// that vary per call; `code` and a default `message` template are\n// provided by the subclass.\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')\n but nothing in this package constructs one. Investigated wiring it\n in the same way InvalidTimeZoneError was just wired (see parse.ts's\n zzz-validation loop): checked whether hour/minute/second have a\n post-match semantic range check the way zone ids do. They don't —\n pattern.ts's regex fragments for HH/H/hh/h/mm/m/ss/s already enforce\n their valid ranges at the regex level (e.g. HH is '(?:[01]\\d|2[0-3])',\n which can't match \"99\" in the first place), so an out-of-range time\n is rejected as a plain shape mismatch before any semantic check\n could run. There's no live gap to hook this into without inventing a\n redundant check purely to give this class a body. Left unconstructed\n until the library actually has a real invalid-time case to report. */\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 nothing in this package constructs one.\n Checked for a wiring opportunity the same way InvalidTimeZoneError\n got one: there's no user-supplied calendar identifier anywhere in\n the library to 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\"\n value a caller could get wrong. There's no live input to reject.\n Left unconstructed until the library actually accepts a 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\n// migrated to typed errors yet. Carries the original message in\n// `reason` so callers reading the typed surface still see what\n// failed. Used by safeParse() in parse.ts.\n//\n// As of 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 be\n// called — nothing in the current test suite reaches any branch below.\n// Kept as the safety net for wrapping a future unmigrated throw site\n// (see the c8-ignored call in parse.ts's safeParse), same reasoning as\n// that call site: removing this would silently drop the \"safeParse\n// always returns a TemporalFmtError\" contract the moment anyone adds a\n// throw new Error(...) without wiring 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 inspecting the message — covers the existing\n // parse()/format() throw sites without modifying them. Anything\n // that doesn't match a known pattern falls through to a generic\n // ParseMismatchError, which still has the structured fields.\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 */","// 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/**\n * Supply a custom month/weekday/day-period vocabulary for a locale key,\n * overriding the Intl-derived vocab this library would otherwise build\n * for that key. Useful for locales Intl doesn't cover well — the README's\n * known-limitations section calls out the Hebrew leap-month gap as a\n * specific case this addresses.\n *\n * Throws descriptively on malformed input (wrong array lengths, empty\n * strings, duplicate entries, missing fields) rather than failing later\n * during format/parse.\n *\n * Registered vocab takes precedence over the Intl-derived vocab for that\n * locale key, including for already-cached entries — registering\n * invalidates the prior cache entry for that locale so the next call\n * picks up the new vocab.\n *\n * @example\n * registerLocaleVocab('en-u-ca-hebrew-leap', {\n * monthLong: ['Nisan','Iyar','Sivan','Tammuz','Av','Elul','Tishrei','Marcheshvan','Kislev','Tevet','Shevat','Adar I','Adar II'],\n * monthShort: ['Nis','Iyy','Siv','Tam','Av','Elu','Tish','Chesh','Kis','Tev','Shv','Ad1','Ad2'],\n * weekdayLong: ['Monday','Tuesday','Wednesday','Thursday','Friday','Saturday','Sunday'],\n * weekdayShort: ['Mon','Tue','Wed','Thu','Fri','Sat','Sun'],\n * dayPeriod: ['AM','PM'],\n * });\n */\nexport function registerLocaleVocab(locale: string, vocab: Partial<LocaleVocab>): void {\n if (typeof locale !== 'string' || locale.length === 0) {\n throw new Error(`temporal-fmt: registerLocaleVocab requires a non-empty locale string, got ${String(locale)}.`);\n }\n if (locale.length > MAX_LOCALE_TAG_LENGTH) {\n throw new RangeError(`temporal-fmt: registerLocaleVocab locale is too long (maximum ${MAX_LOCALE_TAG_LENGTH} characters).`);\n }\n assertValidVocab(vocab, locale);\n\n const cacheKey = canonicalCacheKey(locale);\n if (!customVocabs.has(cacheKey) && customVocabs.size >= MAX_CUSTOM_VOCABS) {\n throw new RangeError(`temporal-fmt: registerLocaleVocab reached the ${MAX_CUSTOM_VOCABS}-locale limit.`);\n }\n customVocabs.set(cacheKey, {\n monthLong: [...vocab.monthLong!],\n monthShort: [...vocab.monthShort!],\n weekdayLong: [...vocab.weekdayLong!],\n weekdayShort: [...vocab.weekdayShort!],\n dayPeriod: [...vocab.dayPeriod!],\n });\n // Invalidate the Intl-derived cache entry so any prior format/parse\n // result cached for this locale is rebuilt against the new vocab. Not\n // strictly necessary (getLocaleVocab checks customVocabs first), but\n // cheap and keeps the two caches from drifting out of sync.\n vocabCache.delete(cacheKey);\n}\n\n// Every locale-keyed cache in this library (this one, formatterCache in\n// tokens.ts, patternCache/calendarCache in parse.ts) used to key on the\n// exact locale string a caller passed in. Intl treats spelling variants of\n// the same locale as equivalent ('en-US' / 'en-us' / 'en_US' all resolve\n// the same way), but a plain string-keyed Map doesn't — so callers mixing\n// spellings for what's really one locale would silently fragment across\n// separate cache entries instead of sharing one, making the bounded\n// eviction limits (MAX_*_CACHE_SIZE) less effective than they look. This\n// doesn't change any cache's *correctness* (each entry is still built from\n// -- and valid for -- whatever locale string produced it), only how many\n// distinct entries equivalent spellings end up costing. Falls back to the\n// original string on a malformed/unrecognized tag rather than throwing —\n// cache-key normalization shouldn't be where a bad locale first surfaces\n// as an error; whatever actually calls `new Intl.DateTimeFormat(locale)`\n// downstream is the right place for that.\nexport function canonicalCacheKey(locale: string): string {\n try {\n // Intl.Locale requires BCP-47 hyphens and rejects underscore-separated\n // tags like 'en_US' outright (RangeError), rather than normalizing\n // them — so without this replace, that spelling would just fall\n // through to the catch below and never fold with 'en-US'.\n return new Intl.Locale(locale.replace(/_/g, '-')).toString().toLowerCase();\n } catch {\n return locale;\n }\n}\n\nconst vocabCache = new Map<string, LocaleVocab>();\nconst MAX_VOCAB_CACHE_SIZE = 500;\n\n// Some locales (ja-JP) split a field across two parts — month \"8\" plus a\n// counter suffix \"月\" as a separate sibling \"literal\" — while format()'s\n// tokens go through toLocaleString(), which concatenates everything into\n// \"8月\". Reading only the type-tagged part used to drop that suffix, so\n// this locale's vocab never matched what format() actually produced.\n// Only merges *adjacent* literals, not the whole string, since the\n// dayPeriod/weekday formatters below carry an extra hour part that a\n// join-everything approach would wrongly absorb.\nfunction partValue(formatter: Intl.DateTimeFormat, date: Date, type: Intl.DateTimeFormatPartTypes): string {\n const parts = formatter.formatToParts(date);\n const index = parts.findIndex((p) => p.type === type);\n /* c8 ignore start @preserve -- defensive guard against a real but\n unreproducible failure mode: an Intl implementation that omits the\n requested part type entirely for some locale. Checked every locale\n with unusual dayPeriod/weekday/month rendering available in this\n runtime's ICU data (ja-JP, zh-CN, th-TH, he-IL, ar-SA, ko-KR, fa-IR)\n against the exact formatter options getLocaleVocab uses (notably\n hour12: true for the dayPeriod formatter, which is what makes every\n locale here actually emit a dayPeriod part — omitting it is what\n produced a false \"gap\" during investigation). None omit their part\n on this runtime. A different ICU version or a non-Node Intl\n implementation could plausibly behave differently, so this stays a\n real check rather than an assertion. */\n /* c8 ignore start @preserve */\n if (index === -1) {\n throw new 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 const cacheKey = canonicalCacheKey(locale);\n // Registered vocabs take precedence over the Intl-derived one — this\n // is the override mechanism registerLocaleVocab() promises. Checking\n // here, before the Intl cache lookup, means a registration that\n // happens *after* the first Intl-derived vocab was built still takes\n // effect on the next call.\n const custom = customVocabs.get(cacheKey);\n if (custom) {\n return custom;\n }\n const cached = vocabCache.get(cacheKey);\n if (cached) {\n return cached;\n }\n\n const monthLongFmt = new Intl.DateTimeFormat(locale, { month: 'long', timeZone: 'UTC' });\n const monthShortFmt = new Intl.DateTimeFormat(locale, { month: 'short', timeZone: 'UTC' });\n const monthLong: string[] = [];\n const monthShort: string[] = [];\n for (let m = 0; m < 12; m++) {\n const date = new Date(Date.UTC(2020, m, 1));\n monthLong.push(partValue(monthLongFmt, date, 'month'));\n monthShort.push(partValue(monthShortFmt, date, 'month'));\n }\n assertNoCollision(monthLong, 'MMMM month', locale);\n assertNoCollision(monthShort, 'MMM month', locale);\n\n const weekdayLongFmt = new Intl.DateTimeFormat(locale, { weekday: 'long', timeZone: 'UTC' });\n const weekdayShortFmt = new Intl.DateTimeFormat(locale, { weekday: 'short', timeZone: 'UTC' });\n const weekdayLong: string[] = [];\n const weekdayShort: string[] = [];\n // 2024-01-01 is a Monday (UTC) — walk 7 days from there for weekday names\n for (let d = 0; d < 7; d++) {\n const date = new Date(Date.UTC(2024, 0, 1 + d));\n weekdayLong.push(partValue(weekdayLongFmt, date, 'weekday'));\n weekdayShort.push(partValue(weekdayShortFmt, date, 'weekday'));\n }\n // redundant with parse()'s dayOfWeek cross-check, but gives a clearer error\n assertNoCollision(weekdayLong, 'EEEE weekday', locale);\n assertNoCollision(weekdayShort, 'EEE weekday', locale);\n\n const dayPeriodFmt = new Intl.DateTimeFormat(locale, { hour: 'numeric', hour12: true, timeZone: 'UTC' });\n const am = partValue(dayPeriodFmt, new Date(Date.UTC(2020, 0, 1, 1)), 'dayPeriod');\n const pm = partValue(dayPeriodFmt, new Date(Date.UTC(2020, 0, 1, 13)), 'dayPeriod');\n const dayPeriod = [...new Set([am, pm])];\n\n const vocab: LocaleVocab = { monthLong, monthShort, weekdayLong, weekdayShort, dayPeriod };\n if (vocabCache.size >= MAX_VOCAB_CACHE_SIZE) {\n const oldestKey = vocabCache.keys().next().value;\n if (oldestKey !== undefined) vocabCache.delete(oldestKey);\n }\n vocabCache.set(cacheKey, vocab);\n return vocab;\n}","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}"],"mappings":"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","names":["duration_exports","__export","addDuration","balanceDuration","compareDuration","formatDurationToParts","formatISODuration","parseDuration","parseISODuration","roundDuration","subtractDuration","totalDuration","__toCommonJS","DURATION_UNIT_TO_NS","isCalendarBound","unit","roundDuration","duration","options","mode","increment","totalNs","u","v","stepNs","sign","abs","stepped","remainder","rounded","newTotalNs","result","remaining","isLargerUnit","unitsInOrder","startIdx","i","unitNs","count","_a","_b","TemporalFmtError","message","fields","capture","FormatSyntaxError","rest","InvalidDurationError","TemporalFmtError","fields","message","rest","canonicalCacheKey","locale","DURATION_UNITS","DURATION_TOKEN_STRINGS","single","a","b","tokenizeDuration","format","pieces","i","ch","appendLiteral","j","literal","closed","match","tok","unit","form","value","last","readUnit","duration","field","coerced","unitFormatterCache","MAX_UNIT_FORMATTER_CACHE_SIZE","getUnitFormatter","locale","intlUnit","unitDisplay","key","canonicalCacheKey","cached","oldestKey","formatter","formatDuration","formatStr","options","showZeroes","useIntl","result","piece","formatDurationToParts","duration","formatStr","options","full","formatDuration","tokens","tokenizeDurationFormat","parts","pos","tok","lit","nextLiteralStart","findNextLiteralStart","value","DURATION_TOKEN_STRINGS","single","a","b","pieces","i","ch","appendLiteral","j","literal","closed","FormatSyntaxError","match","last","_currentTok","idx","t","found","parseISODuration","input","m","InvalidDurationError","g","toNum","s","formatISODuration","years","months","weeks","days","hours","minutes","seconds","parseDuration","fields","groupSpecs","regex","unit","form","key","spec","raw","balanceDuration","NS_PER","totalNs","k","nsPer","v","result","remaining","count","totalDuration","divisor","compareDuration","aNs","totalDurationNs","bNs","d","total","addDuration","keys","subtractDuration","negateDuration"]}
1
+ {"version":3,"sources":["../src/duration.ts","../src/errors.ts","../src/rounding.ts","../src/localeVocab.ts","../src/formatDuration.ts"],"sourcesContent":["// 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.\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// 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","// 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), and the two data-path throws in\n// localeVocab.ts (partValue/assertNoCollision on the getLocaleVocab side)\n// — throws these typed classes directly instead of plain\n// `new Error(message)`. Every migrated site kept its exact pre-0.9.0\n// message text — subclasses of TemporalFmtError still satisfy\n// `instanceof Error` and message-matching regexes (e.g.\n// `/token \"HH\" requires/`), so this was safe to do without a semver-major\n// bump for either check. What *is* a breaking change for 0.9.0: code that\n// specifically checked `err.constructor === Error` or `err.name ===\n// 'Error'` will see a different name now (e.g. 'FormatSyntaxError'). See\n// the 0.9.0 changelog entry.\n//\n// Not migrated, on purpose:\n// - localeVocab.ts's registration-time throws (assertValidVocab,\n// registerLocaleVocab itself) — these are config-time API-misuse\n// errors on data the developer supplies once at startup, not runtime\n// parse/format failures, so they don't fit this module's\n// TemporalFmtErrorCode taxonomy as-is. assertNoCollision is shared\n// between the registration path and the data path (getLocaleVocab), so\n// its throw stays plain `Error` for both until that split is done —\n// see the tracking note at its call site.\n//\n// wrapUntypedError() below still exists for localeVocab.ts's registration\n// throws and for anything a caller passes into safeParse()/tryParse() from\n// outside this package. On the data path, safeParse's `if (err instanceof\n// TemporalFmtError) return { ok: false, error: err }` now catches every\n// throw before it would reach the classifier, so the regex branches here\n// are effectively dead for parse/format/tokenize/pattern — left in place\n// as the fallback for anything not yet on a typed throw site.\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 parts a linter or codemod reports on.\n// `Error.captureStackTrace` is called manually (where available) so\n// the stack points at the call site, not at this constructor — same\n// pattern 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 // Avoid setting the stack to point at this constructor — keeps it\n // useful. captureStackTrace is V8-only; on other engines the\n // default stack (this constructor line) is what callers get.\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 do `err.toJSON()` for logging. Plain `Error` doesn't\n // serialize its non-enumerable fields; this picks them 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 fixes `code` so callers can switch on it without\n// re-checking message text. The constructor takes only the fields\n// that vary per call; `code` and a default `message` template are\n// provided by the subclass.\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')\n but nothing in this package constructs one. Investigated wiring it\n in the same way InvalidTimeZoneError was just wired (see parse.ts's\n zzz-validation loop): checked whether hour/minute/second have a\n post-match semantic range check the way zone ids do. They don't —\n pattern.ts's regex fragments for HH/H/hh/h/mm/m/ss/s already enforce\n their valid ranges at the regex level (e.g. HH is '(?:[01]\\d|2[0-3])',\n which can't match \"99\" in the first place), so an out-of-range time\n is rejected as a plain shape mismatch before any semantic check\n could run. There's no live gap to hook this into without inventing a\n redundant check purely to give this class a body. Left unconstructed\n until the library actually has a real invalid-time case to report. */\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 nothing in this package constructs one.\n Checked for a wiring opportunity the same way InvalidTimeZoneError\n got one: there's no user-supplied calendar identifier anywhere in\n the library to 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\"\n value a caller could get wrong. There's no live input to reject.\n Left unconstructed until the library actually accepts a 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\n// migrated to typed errors yet. Carries the original message in\n// `reason` so callers reading the typed surface still see what\n// failed. Used by safeParse() in parse.ts.\n//\n// As of 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 be\n// called — nothing in the current test suite reaches any branch below.\n// Kept as the safety net for wrapping a future unmigrated throw site\n// (see the c8-ignored call in parse.ts's safeParse), same reasoning as\n// that call site: removing this would silently drop the \"safeParse\n// always returns a TemporalFmtError\" contract the moment anyone adds a\n// throw new Error(...) without wiring 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 inspecting the message — covers the existing\n // parse()/format() throw sites without modifying them. Anything\n // that doesn't match a known pattern falls through to a generic\n // ParseMismatchError, which still has the structured fields.\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 */","// 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';\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\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 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 += fieldToNs(v, DURATION_UNIT_TO_NS[u], 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 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}","// 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 README's\n * known-limitations section calls out the Hebrew leap-month gap as a\n * specific case this addresses.\n *\n * Throws descriptively on malformed input (wrong array lengths, empty\n * strings, duplicate entries, missing fields) rather than failing later\n * during format/parse.\n *\n * Registered vocab takes precedence over the Intl-derived vocab for that\n * locale key, including for already-cached entries — registering\n * invalidates the prior cache entry for that locale so the next call\n * picks up the new vocab.\n *\n * @example\n * registerLocaleVocab('en-u-ca-hebrew-leap', {\n * monthLong: ['Nisan','Iyar','Sivan','Tammuz','Av','Elul','Tishrei','Marcheshvan','Kislev','Tevet','Shevat','Adar I','Adar II'],\n * monthShort: ['Nis','Iyy','Siv','Tam','Av','Elu','Tish','Chesh','Kis','Tev','Shv','Ad1','Ad2'],\n * weekdayLong: ['Monday','Tuesday','Wednesday','Thursday','Friday','Saturday','Sunday'],\n * weekdayShort: ['Mon','Tue','Wed','Thu','Fri','Sat','Sun'],\n * dayPeriod: ['AM','PM'],\n * });\n */\nexport function registerLocaleVocab(locale: string, vocab: Partial<LocaleVocab>): void {\n if (typeof locale !== 'string' || locale.length === 0) {\n throw new Error(`temporal-fmt: registerLocaleVocab requires a non-empty locale string, got ${String(locale)}.`);\n }\n if (locale.length > MAX_LOCALE_TAG_LENGTH) {\n throw new RangeError(`temporal-fmt: registerLocaleVocab locale is too long (maximum ${MAX_LOCALE_TAG_LENGTH} characters).`);\n }\n assertValidVocab(vocab, locale);\n\n const cacheKey = canonicalCacheKey(locale);\n if (!customVocabs.has(cacheKey) && customVocabs.size >= MAX_CUSTOM_VOCABS) {\n throw new RangeError(`temporal-fmt: registerLocaleVocab reached the ${MAX_CUSTOM_VOCABS}-locale limit.`);\n }\n customVocabs.set(cacheKey, {\n monthLong: [...vocab.monthLong!],\n monthShort: [...vocab.monthShort!],\n weekdayLong: [...vocab.weekdayLong!],\n weekdayShort: [...vocab.weekdayShort!],\n dayPeriod: [...vocab.dayPeriod!],\n });\n // Invalidate the Intl-derived cache entry so any prior format/parse\n // result cached for this locale is rebuilt against the new vocab. Not\n // strictly necessary (getLocaleVocab checks customVocabs first), but\n // cheap and keeps the two caches from drifting out of sync.\n vocabCache.delete(cacheKey);\n // 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// tokens go through toLocaleString(), which concatenates everything into\n// \"8月\". Reading only the type-tagged part used to drop that suffix, so\n// this locale's vocab never matched what format() actually produced.\n// Only merges *adjacent* literals, not the whole string, since the\n// dayPeriod/weekday formatters below carry an extra hour part that a\n// join-everything approach would wrongly absorb.\nfunction partValue(formatter: Intl.DateTimeFormat, date: Date, type: Intl.DateTimeFormatPartTypes): string {\n const parts = formatter.formatToParts(date);\n const index = parts.findIndex((p) => p.type === type);\n /* c8 ignore start @preserve -- defensive guard against a real but\n unreproducible failure mode: an Intl implementation that omits the\n requested part type entirely for some locale. Checked every locale\n with unusual dayPeriod/weekday/month rendering available in this\n runtime's ICU data (ja-JP, zh-CN, th-TH, he-IL, ar-SA, ko-KR, fa-IR)\n against the exact formatter options getLocaleVocab uses (notably\n hour12: true for the dayPeriod formatter, which is what makes every\n locale here actually emit a dayPeriod part — omitting it is what\n produced a false \"gap\" during investigation). None omit their part\n on this runtime. A different ICU version or a non-Node Intl\n implementation could plausibly behave differently, so this stays a\n real check rather than an assertion. */\n /* c8 ignore start @preserve */\n if (index === -1) {\n throw new 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}","import { 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_exports","__export","addDuration","balanceDuration","compareDuration","formatDurationToParts","formatISODuration","parseDuration","parseISODuration","roundDuration","subtractDuration","totalDuration","__toCommonJS","TemporalFmtError","message","fields","capture","FormatSyntaxError","rest","InvalidLocaleError","TemporalFmtError","fields","message","rest","InvalidDurationError","fieldToNs","v","nsPer","field","scaled","InvalidDurationError","DURATION_UNIT_TO_NS","isCalendarBound","unit","roundDuration","duration","options","mode","increment","totalNs","u","stepNs","sign","abs","stepped","remainder","rounded","newTotalNs","result","remaining","unitsInOrder","startIdx","i","unitNs","count","normalizeLocaleTag","locale","canonicalKeyCache","MAX_CANONICAL_KEY_CACHE","canonicalCacheKey","hit","key","oldestKey","DURATION_UNITS","DURATION_TOKEN_STRINGS","single","a","b","tokenizeDuration","format","pieces","i","ch","appendLiteral","j","literal","closed","match","tok","unit","form","value","last","readUnit","duration","field","coerced","unitFormatterCache","MAX_UNIT_FORMATTER_CACHE_SIZE","getUnitFormatter","locale","intlUnit","unitDisplay","key","canonicalCacheKey","cached","oldestKey","formatter","normalizeLocaleTag","err","InvalidLocaleError","renderDurationPieces","duration","formatStr","options","showZeroes","locale","useIntl","pieces","tokenizeDuration","out","piece","unit","DURATION_UNITS","value","readUnit","unitDisplay","formatter","getUnitFormatter","formatDurationToParts","duration","formatStr","options","renderDurationPieces","p","parseISODuration","input","m","InvalidDurationError","g","toNum","s","formatISODuration","parts","years","months","weeks","days","hours","minutes","seconds","tokenizeDurationFormat","DURATION_TOKEN_STRINGS","single","a","b","pieces","i","ch","appendLiteral","j","literal","closed","FormatSyntaxError","match","tok","value","last","parseDuration","input","formatStr","options","tokens","tokenizeDurationFormat","fields","groupSpecs","regex","tok","unit","form","key","FormatSyntaxError","m","spec","i","raw","value","InvalidDurationError","balanceDuration","duration","NS_PER","totalNs","k","nsPer","v","fieldToNs","result","remaining","count","totalDuration","divisor","compareDuration","a","b","aNs","totalDurationNs","bNs","d","total","addDuration","keys","subtractDuration","negateDuration"]}
package/dist/duration.js CHANGED
@@ -1,2 +1,2 @@
1
- import{e as a,g as b,h as c,i as d,j as e,k as f,l as g,m as h,n as i,o as j}from"./chunk-ILXRFTAE.js";import"./chunk-3MZLTVP3.js";import"./chunk-H52AMABK.js";import"./chunk-EHLRZULM.js";import"./chunk-C6ZZ62ET.js";export{i as addDuration,f as balanceDuration,h as compareDuration,b as formatDurationToParts,d as formatISODuration,e as parseDuration,c as parseISODuration,a as roundDuration,j as subtractDuration,g as totalDuration};
1
+ import{e as a,g as b,h as c,i as d,j as e,k as f,l as g,m as h,n as i,o as j}from"./chunk-2JHEV7ZF.js";import"./chunk-5U5WJ465.js";import"./chunk-EHLRZULM.js";import"./chunk-C6ZZ62ET.js";export{i as addDuration,f as balanceDuration,h as compareDuration,b as formatDurationToParts,d as formatISODuration,e as parseDuration,c as parseISODuration,a as roundDuration,j as subtractDuration,g as totalDuration};
2
2
  //# sourceMappingURL=duration.js.map