temporal-fmt 0.9.2 → 0.9.31

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 (94) hide show
  1. package/README.md +168 -1
  2. package/dist/calendarUtils.cjs +259 -4
  3. package/dist/calendarUtils.cjs.map +1 -1
  4. package/dist/calendarUtils.js +33 -1
  5. package/dist/chunk-4N3WCGEY.js +183 -0
  6. package/dist/{chunk-4JWGUR4O.js.map → chunk-4N3WCGEY.js.map} +1 -1
  7. package/dist/chunk-5UTV5VP6.js +984 -0
  8. package/dist/chunk-5UTV5VP6.js.map +1 -0
  9. package/dist/chunk-ACKKWH5Z.js +578 -0
  10. package/dist/{chunk-2JHEV7ZF.js.map → chunk-ACKKWH5Z.js.map} +1 -1
  11. package/dist/chunk-DR62Z6GV.js +224 -0
  12. package/dist/{chunk-ZGMTY52O.js.map → chunk-DR62Z6GV.js.map} +1 -1
  13. package/dist/chunk-EGUUO6LE.js +439 -0
  14. package/dist/chunk-EGUUO6LE.js.map +1 -0
  15. package/dist/chunk-GPB4JHGI.js +65 -0
  16. package/dist/chunk-GPB4JHGI.js.map +1 -0
  17. package/dist/chunk-H7YQX25T.js +170 -0
  18. package/dist/{chunk-J2AQNXF2.js.map → chunk-H7YQX25T.js.map} +1 -1
  19. package/dist/chunk-HXY2GUUM.js +182 -0
  20. package/dist/{chunk-EHLRZULM.js.map → chunk-HXY2GUUM.js.map} +1 -1
  21. package/dist/chunk-L24NPTAJ.js +328 -0
  22. package/dist/chunk-L24NPTAJ.js.map +1 -0
  23. package/dist/chunk-M3GDTHMT.js +169 -0
  24. package/dist/chunk-M3GDTHMT.js.map +1 -0
  25. package/dist/chunk-MKPDLQKD.js +51 -0
  26. package/dist/chunk-MKPDLQKD.js.map +1 -0
  27. package/dist/chunk-P3G4JRYC.js +205 -0
  28. package/dist/{chunk-F4RGUDA3.js.map → chunk-P3G4JRYC.js.map} +1 -1
  29. package/dist/chunk-RIZ3HN37.js +181 -0
  30. package/dist/{chunk-UJR6DHXY.js.map → chunk-RIZ3HN37.js.map} +1 -1
  31. package/dist/chunk-TSLPIURC.js +242 -0
  32. package/dist/{chunk-VQXUMFB2.js.map → chunk-TSLPIURC.js.map} +1 -1
  33. package/dist/chunk-UOPAV3WX.js +75 -0
  34. package/dist/chunk-UOPAV3WX.js.map +1 -0
  35. package/dist/chunk-YEUASGDK.js +9 -0
  36. package/dist/chunk-YEUASGDK.js.map +1 -0
  37. package/dist/chunk-ZQZF5EXW.js +29 -0
  38. package/dist/chunk-ZQZF5EXW.js.map +1 -0
  39. package/dist/duration.cjs +638 -26
  40. package/dist/duration.cjs.map +1 -1
  41. package/dist/duration.js +28 -1
  42. package/dist/format.cjs +833 -35
  43. package/dist/format.cjs.map +1 -1
  44. package/dist/format.js +20 -1
  45. package/dist/index.cjs +6905 -163
  46. package/dist/index.cjs.map +1 -1
  47. package/dist/index.js +3091 -58
  48. package/dist/index.js.map +1 -1
  49. package/dist/interval.cjs +1015 -35
  50. package/dist/interval.cjs.map +1 -1
  51. package/dist/interval.js +41 -1
  52. package/dist/localeRegistry.cjs +375 -32
  53. package/dist/localeRegistry.cjs.map +1 -1
  54. package/dist/localeRegistry.js +11 -1
  55. package/dist/parse.cjs +1797 -70
  56. package/dist/parse.cjs.map +1 -1
  57. package/dist/parse.js +20 -1
  58. package/dist/pattern.d.cts +1 -0
  59. package/dist/pattern.d.ts +1 -0
  60. package/dist/recurrence.cjs +452 -20
  61. package/dist/recurrence.cjs.map +1 -1
  62. package/dist/recurrence.js +20 -1
  63. package/dist/relativeTime.cjs +268 -21
  64. package/dist/relativeTime.cjs.map +1 -1
  65. package/dist/relativeTime.js +14 -1
  66. package/dist/timezone.cjs +387 -20
  67. package/dist/timezone.cjs.map +1 -1
  68. package/dist/timezone.js +26 -1
  69. package/package.json +3 -5
  70. package/scripts/cli.mjs +17 -5
  71. package/dist/chunk-2JHEV7ZF.js +0 -7
  72. package/dist/chunk-3MZLTVP3.js +0 -2
  73. package/dist/chunk-3MZLTVP3.js.map +0 -1
  74. package/dist/chunk-4JWGUR4O.js +0 -11
  75. package/dist/chunk-5U5WJ465.js +0 -36
  76. package/dist/chunk-5U5WJ465.js.map +0 -1
  77. package/dist/chunk-C5YESWFT.js +0 -2
  78. package/dist/chunk-C5YESWFT.js.map +0 -1
  79. package/dist/chunk-C6ZZ62ET.js +0 -2
  80. package/dist/chunk-C6ZZ62ET.js.map +0 -1
  81. package/dist/chunk-EHLRZULM.js +0 -5
  82. package/dist/chunk-F4RGUDA3.js +0 -2
  83. package/dist/chunk-J2AQNXF2.js +0 -2
  84. package/dist/chunk-NBXF7V5B.js +0 -2
  85. package/dist/chunk-NBXF7V5B.js.map +0 -1
  86. package/dist/chunk-R52YOKI3.js +0 -7
  87. package/dist/chunk-R52YOKI3.js.map +0 -1
  88. package/dist/chunk-UJR6DHXY.js +0 -2
  89. package/dist/chunk-VQXUMFB2.js +0 -21
  90. package/dist/chunk-XH46JNPK.js +0 -2
  91. package/dist/chunk-XH46JNPK.js.map +0 -1
  92. package/dist/chunk-YB6YAG7E.js +0 -35
  93. package/dist/chunk-YB6YAG7E.js.map +0 -1
  94. package/dist/chunk-ZGMTY52O.js +0 -2
package/README.md CHANGED
@@ -836,6 +836,8 @@ A handful of functions exist specifically to feed editor tooling — autocomplet
836
836
 
837
837
  The CLI ships in this package (`scripts/cli.mjs`) and reads/writes stdin/stdout. Run it via `npm run cli` inside a checkout of this repo, or `node scripts/cli.mjs` directly. Called with a subcommand it runs once and exits, same as any Unix tool — fine for scripts and CI:
838
838
 
839
+ On Node 26+ the CLI uses native `Temporal` and needs nothing extra. Below that, it looks for a globally-installed [`temporal-polyfill`](https://github.com/fullcalendar/temporal-polyfill) and exits with an install hint if it can't find one — `temporal-fmt` itself ships with zero dependencies, so this one's on you: `npm install temporal-polyfill`.
840
+
839
841
  ```sh
840
842
  temporal-fmt format "2026-08-04T15:45:30" "yyyy-MM-dd HH:mm:ss"
841
843
  temporal-fmt parse "yyyy-MM-dd" "2026-08-04"
@@ -997,7 +999,172 @@ Neither of these ships as part of this repository — separate packages, install
997
999
 
998
1000
  ## Testing
999
1001
 
1000
- This library is heavily tested. The `node:test` suite (`test/*.test.js`) runs 1200+ cases covering hand-picked scenarios, fuzzing, and adversarial input, alongside a separate `vitest/` suite unit-testing internals directly. On top of that there's a dedicated conformance suite, smoke tests that check the package actually resolves correctly under CJS/ESM/bundler/nodenext, and type tests. If it's mentioned in this README, it's backed by a test — not just a docstring.
1002
+ This library is heavily tested. The `node:test` suite (`test/*.test.js`) runs 1300+ cases covering hand-picked scenarios, fuzzing, and adversarial input, alongside a separate `vitest/` suite unit-testing internals directly. On top of that there's a dedicated conformance suite, smoke tests that check the package actually resolves correctly under CJS/ESM/bundler/nodenext, and type tests. If it's mentioned in this README, it's backed by a test — not just a docstring.
1003
+
1004
+ # Conformance fixtures
1005
+
1006
+ `fixtures.json` is a portable, library-agnostic test-vector set for
1007
+ token-based Temporal formatters. It's written against a different
1008
+ library's token vocabulary, not temporal-fmt's — the fixtures are
1009
+ data, not code. Each case names an `op` (`format` / `parse` /
1010
+ `roundtrip`), an input, a pattern, and an expected result, so any
1011
+ library with a `format`/`parse` pair can be pointed at it.
1012
+
1013
+ `test/conformance.test.js` is the temporal-fmt-specific adapter. It
1014
+ translates fixture patterns into temporal-fmt's actual tokens, runs
1015
+ the cases against `format()`/`parse()`, and checks the result.
1016
+
1017
+ ## Why this is separate from `test/adversarial.test.js` and `test/fuzz.test.js`
1018
+
1019
+ Those two check that temporal-fmt is internally consistent under
1020
+ hostile input — clean throw or correct value, never a crash or a
1021
+ silently wrong one. The reference point there is the library's own
1022
+ logic.
1023
+
1024
+ This folder is different: it checks temporal-fmt against an external,
1025
+ shared set of tricky-but-well-defined cases — DST transitions, leap
1026
+ years, offset rendering, calendar limits — where "correct" comes from
1027
+ the fixture, not from temporal-fmt's own code.
1028
+
1029
+ ## Pattern translation
1030
+
1031
+ Two fixture tokens don't exist in temporal-fmt:
1032
+
1033
+ | Fixture token | temporal-fmt equivalent | Why |
1034
+ |---|---|---|
1035
+ | `ZZ` (always-signed offset, never `Z`) | `xxx` | Only the **uppercase** `X`/`XX`/`XXX` family collapses `+00:00` to `Z` (see `formatOffset()` in `src/tokens.ts`). Lowercase never does, which is exactly `ZZ`'s semantics. Mapping `ZZ` to `XXX` was tried first and is wrong — it fails `offset-ZZ-format-utc-not-Z` and `zone-utc-roundtrip`, both of which expect `+00:00`, not `Z`. |
1036
+ | `VV` (IANA zone id) | `zzz` | temporal-fmt's only zone-identity token. |
1037
+
1038
+ Translation happens in `translatePattern()` and skips anything inside
1039
+ a quoted literal span. Everything else in the fixture set — `yyyy`,
1040
+ `y`, `MM`, `dd`, `HH`, `mm`, `ss`, `S`..`SSSSSSSSS`, `h`, `a`,
1041
+ `X`/`XX`/`XXX` — already matches temporal-fmt's vocabulary directly.
1042
+
1043
+ ## `opinionated` cases
1044
+
1045
+ Some cases are flagged `"opinionated": true` right in the fixture.
1046
+ These encode a design choice of the fixture's source library, not a
1047
+ fact about dates, and temporal-fmt is allowed to disagree with them.
1048
+ The adapter still runs them — if temporal-fmt's behavior differs, it
1049
+ logs a divergence note (printed at the end of the run) instead of
1050
+ failing the suite.
1051
+
1052
+ Two cases currently diverge, both `yy`-pivot ones —
1053
+ `extreme-year-two-digit-pivot-low` and
1054
+ `extreme-year-two-digit-pivot-high`. temporal-fmt refuses `yy` in any
1055
+ format string that isn't a complete date (`yyyy`/`yy` + month + day),
1056
+ so `parse("yy-MM", ...)` throws an incomplete-date error before the
1057
+ question of *which* century a 2-digit year should resolve to ever
1058
+ comes up. The fixture's position — that bare `yy-MM` should resolve
1059
+ via the 00-68/69-99 ECMAScript pivot — is a convention, not a fact
1060
+ about dates; a library is free to pick a different pivot, or, as here,
1061
+ decline to guess a century from `yy` alone at all. Both are documented
1062
+ design choices with their own passing tests
1063
+ (`test/parse.test.js`, `yy pivot: ...`), not something in scope to
1064
+ "fix" by adopting the fixture's convention.
1065
+
1066
+ One other flagged case no longer diverges:
1067
+ **`shape-mixing-H-and-a-rejected`**. `resolveHour()`
1068
+ (`src/parse.ts`) used to cross-check `H` against `a` instead of
1069
+ banning the combination outright, so `13:05 PM` was accepted (13:00 is
1070
+ consistent with PM) and only a genuine contradiction like `01:05 PM`
1071
+ threw. That choice has since been reverted — `H` and `a` are now
1072
+ refused together outright, unconditionally, matching the fixture. The
1073
+ fixture's own `"opinion"` text on that case still describes the old
1074
+ behavior; it's fixture data, not something this adapter edits, so
1075
+ treat the `opinionated` flag there as historical rather than current.
1076
+
1077
+ ## History: divergences that have since been fixed
1078
+
1079
+ Everything below was once tracked in `KNOWN_FAILURES` at the top of
1080
+ `test/conformance.test.js`. That set is currently empty — every
1081
+ previously-found divergence has been resolved, either by fixing a
1082
+ real bug or by deliberately adopting the fixture's convention over a
1083
+ prior design choice. Kept here for context on what changed and why,
1084
+ in case any of it needs revisiting.
1085
+
1086
+ **Fixed — real bug: offset seconds were dropped, not rejected.**
1087
+ `formatOffset()` (`src/tokens.ts`) assumed every offset string was
1088
+ exactly 6 characters — sign, `HH`, `:`, `MM` — and never checked for a
1089
+ seconds component. Verified against a real `Temporal.ZonedDateTime`
1090
+ for a pre-1900 `America/New_York` date: the actual offset is
1091
+ `-04:56:02`, 9 characters, because pre-1883 New York ran on local mean
1092
+ time. The old code read that string's middle two digits as minutes,
1093
+ so `X` silently produced `-0402` (wrong) instead of refusing. Now:
1094
+ `X`/`XX`/`XXX`/`x`/`xx` throw when the offset has a seconds component
1095
+ (none of them have anywhere to put it), and `xxx` — the variant that
1096
+ plays `ZZ`'s "always-signed, never-Z" role — passes the full value
1097
+ through unchanged.
1098
+ - `offset-sub-minute-rejected-by-X`
1099
+ - `offset-sub-minute-passes-through-ZZ`
1100
+
1101
+ **Changed — offset-only `ZonedDateTime` construction, previously
1102
+ supported on purpose, is now refused.** `parse()` used to build a
1103
+ `ZonedDateTime` from an offset token alone, no `zzz` zone required.
1104
+ That was deliberate, not an oversight, but the fixture's position — an
1105
+ offset identifies a moment's distance from UTC, not a time zone, so
1106
+ building a `ZonedDateTime` from one alone papers over that distinction
1107
+ — was adopted instead. A pattern with an offset token and no `zzz` now
1108
+ throws; add `zzz` to the pattern (or parse into a
1109
+ `PlainDateTime`/`PlainDate`/`PlainTime` if a zone genuinely isn't
1110
+ needed).
1111
+ - `zone-required-for-zoneddatetime`
1112
+ - `zone-offset-token-rejected-on-plain-type`
1113
+
1114
+ **Added — `y` token (unpadded, variable-width year).** temporal-fmt
1115
+ previously had only `yyyy` (fixed 4 digits) and `yy` (2-digit,
1116
+ truncated). `y` formats and parses a year at any width, sign preserved
1117
+ for years before ISO year 0 — same semantics as `yyyy` minus the
1118
+ fixed width. It has no bounded fallback the way `yyyy` does when
1119
+ something digit-consuming follows (`yyyy` can fall back to an exact
1120
+ 4-digit fragment in that case; `y` being unpadded is the entire point
1121
+ of the token, so there's no narrower shape that still means the same
1122
+ thing). `buildCapturingPattern()` (`src/parsePattern.ts`) refuses at
1123
+ build time to place `y` directly next to another digit-reading token
1124
+ or a digit-leading literal, rather than trying to estimate an
1125
+ ambiguity cost for an unbounded-width fragment — there's no finite
1126
+ number of "width choices" to charge for "any number of digits."
1127
+ - `extreme-year-max-supported`
1128
+ - `extreme-year-negative`
1129
+ - `extreme-year-past-max-rejected` — previously passed even without a
1130
+ real `y` token, because "no valid pattern matches" for the
1131
+ then-unrecognized token happened to also throw. Now genuinely tests
1132
+ 275761 CE rejection, via the real max-year check on `y`'s parsed
1133
+ value.
1134
+
1135
+ **Fixed — misdiagnosed as a regex gap; the actual cause was `zzz`
1136
+ rejecting valid IANA zone aliases.** `offset-X-parse-accepts-four-digit`
1137
+ expects `X` to parse a 4-digit offset body (`+0530`) alongside a `zzz`
1138
+ zone. This was originally filed as "the capturing regex for `X` in
1139
+ `pattern.ts` doesn't offer the 4-digit shape as an alternative" — that
1140
+ diagnosis was wrong. The `X` regex fragment matches `+0530` correctly
1141
+ in isolation; the actual failure was `isValidTimeZone()`
1142
+ (`src/pattern.ts`) rejecting the fixture's zone name, `Asia/Kolkata`.
1143
+ `isValidTimeZone()` only checked `Intl.supportedValuesOf('timeZone')`,
1144
+ which lists canonical zone ids but not every IANA link/alias name —
1145
+ `Asia/Kolkata` is a legitimate, commonly-used alias for
1146
+ `Asia/Calcutta` that some ICU builds' `supportedValuesOf()` omits.
1147
+ Confirmed against `temporal-polyfill` directly:
1148
+ `Temporal.ZonedDateTime.from()` resolves `Asia/Kolkata` without
1149
+ complaint, so `parse()` was refusing input its own downstream
1150
+ construction step would have accepted. `isValidTimeZone()` now falls
1151
+ back to asking `Temporal` itself (a real `ZonedDateTime.from()` call)
1152
+ when the fast-path `Intl` lookup misses, rather than trusting only the
1153
+ `Intl` list.
1154
+
1155
+ ## Adapter mapping notes
1156
+
1157
+ - `parse(formatStr, input, options)` takes `(formatStr, input)` —
1158
+ reversed from the fixture's `pattern`/`input` field order.
1159
+ - temporal-fmt does have a typed error hierarchy (`TemporalFmtError`
1160
+ and its subclasses in `src/errors.ts`), but the fixture's three
1161
+ `expect.throws` values (`"ParseError"`, `"FormatError"`,
1162
+ `"InvalidPatternError"`) don't map cleanly onto temporal-fmt's
1163
+ dozen-plus subclasses, so the adapter doesn't try — it just checks
1164
+ that something extending `Error` was thrown.
1165
+ - `target` in the fixture is informational only. temporal-fmt's
1166
+ `parse()` infers the result shape from which fields the pattern
1167
+ captures, so the adapter never passes `target` as an input.
1001
1168
 
1002
1169
  ## Contributing
1003
1170
 
@@ -1,5 +1,260 @@
1
- "use strict";var s=Object.defineProperty;var x=Object.getOwnPropertyDescriptor;var F=Object.getOwnPropertyNames;var w=Object.prototype.hasOwnProperty;var V=(e,n)=>{for(var r in n)s(e,r,{get:n[r],enumerable:!0})},W=(e,n,r,t)=>{if(n&&typeof n=="object"||typeof n=="function")for(let a of F(n))!w.call(e,a)&&a!==r&&s(e,a,{get:()=>n[a],enumerable:!(t=x(n,a))||t.enumerable});return e};var Y=e=>W(s({},"__esModule",{value:!0}),e);var H={};V(H,{asDateFieldView:()=>h,dayOfYearHelper:()=>S,daysInMonth:()=>p,daysInYear:()=>E,endOf:()=>P,getMonth:()=>R,getQuarter:()=>L,getWeekday:()=>j,isLeapMonth:()=>_,isLeapYear:()=>M,monthsInYear:()=>I,startOf:()=>C,weekOfYear:()=>A,weekYear:()=>N});module.exports=Y(H);var g=[0,31,59,90,120,151,181,212,243,273,304,334];function d(e){return e%4===0&&e%100!==0||e%400===0}function u(e){return d(e)?366:365}function y(e,n,r){let t=g[n-1]+r;return n>2&&d(e)&&(t+=1),t}var l=2e3;function T(e){let n=0;if(e>=l)for(let t=l;t<e;t++)n+=u(t);else for(let t=e;t<l;t++)n-=u(t);return((5+n)%7+7)%7+1}function c(e,n,r,t){let m=y(e,n,r)+(4-t),i,f;m<1?(i=e-1,f=m+u(i)):m>u(e)?(i=e+1,f=m-u(e)):(i=e,f=m);let k=1+(4-T(i)+7)%7,O=1+Math.floor((f-k)/7);return{isoYear:i,week:O}}function o(e,n){for(let r of n)if(typeof e[r]!="number")throw new Error(`temporal-fmt: calendar helper requires "${String(r)}", which this value doesn't have. Pass a Temporal.PlainDate / PlainDateTime / ZonedDateTime.`)}function p(e){o(e,["year","month"]);let{year:n,month:r}=e,t=[31,28,31,30,31,30,31,31,30,31,30,31];return r===2&&d(n)?29:t[r-1]}function E(e){return o(e,["year"]),d(e.year)?366:365}function I(e){return 12}function M(e){return o(e,["year"]),d(e.year)}function _(e){return!1}function S(e){return o(e,["year","month","day"]),y(e.year,e.month,e.day)}function A(e){return o(e,["year","month","day","dayOfWeek"]),c(e.year,e.month,e.day,e.dayOfWeek).week}function N(e){return o(e,["year","month","day","dayOfWeek"]),c(e.year,e.month,e.day,e.dayOfWeek).isoYear}function U(e){if(!Number.isInteger(e)||e<1||e>12)throw new Error(`temporal-fmt: getQuarter's startMonth must be an integer from 1 to 12 (got ${e}).`)}function L(e,n={}){o(e,["month"]);let r=n.startMonth??1;if(U(r),r===1)return Math.ceil(e.month/3);let t=(e.month-r+12)%12+1;return Math.ceil(t/3)}function R(e){return o(e,["month"]),e.month}function j(e){return o(e,["dayOfWeek"]),e.dayOfWeek}function b(e){return e==="day"||e==="month"||e==="year"}function D(e){if(typeof e.dayOfWeek!="number")return;/* c8 ignore start @preserve -- unreachable: startOf()/endOf() both call
2
- * asDateFieldView() before this, which already throws if year/month/day
3
- * aren't all numbers — so by the time a value with a numeric dayOfWeek
4
- * reaches here, year/month/day are guaranteed present too. */if(typeof e.year!="number"||typeof e.month!="number"||typeof e.day!="number")return;/* c8 ignore stop @preserve */let n=new Date(Date.UTC(e.year,e.month-1,e.day)).getUTCDay();e.dayOfWeek=n===0?7:n}function C(e,n){let t={...h(e)};return n==="year"?(t.month=1,t.day=1):n==="month"&&(t.day=1),D(t),b(n)?(t.hour=0,t.minute=0,t.second=0,t.millisecond=0):n==="hour"?(t.minute=0,t.second=0,t.millisecond=0):n==="minute"?(t.second=0,t.millisecond=0):n==="second"&&(t.millisecond=0),t}function P(e,n){let t={...h(e)};return n==="year"?(t.month=12,t.day=p({year:t.year,month:12})):n==="month"&&(t.day=p({year:t.year,month:t.month})),D(t),b(n)?(t.hour=23,t.minute=59,t.second=59,t.millisecond=999):n==="hour"?(t.minute=59,t.second=59,t.millisecond=999):n==="minute"?(t.second=59,t.millisecond=999):n==="second"&&(t.millisecond=999),t}function h(e){if(typeof e!="object"||e===null)throw new Error(`temporal-fmt: expected a date-carrying Temporal value, got ${String(e)}.`);let n=e,r={};if(typeof n.year=="number"&&(r.year=n.year),typeof n.month=="number"&&(r.month=n.month),typeof n.day=="number"&&(r.day=n.day),typeof n.hour=="number"&&(r.hour=n.hour),typeof n.minute=="number"&&(r.minute=n.minute),typeof n.second=="number"&&(r.second=n.second),typeof n.millisecond=="number"&&(r.millisecond=n.millisecond),typeof n.dayOfWeek=="number"&&(r.dayOfWeek=n.dayOfWeek),typeof n.calendarId=="string"&&(r.calendarId=n.calendarId),r.year===void 0||r.month===void 0||r.day===void 0)throw new Error("temporal-fmt: value is missing year/month/day fields \u2014 pass a Temporal.PlainDate / PlainDateTime / ZonedDateTime.");return r}0&&(module.exports={asDateFieldView,dayOfYearHelper,daysInMonth,daysInYear,endOf,getMonth,getQuarter,getWeekday,isLeapMonth,isLeapYear,monthsInYear,startOf,weekOfYear,weekYear});
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/calendarUtils.ts
21
+ var calendarUtils_exports = {};
22
+ __export(calendarUtils_exports, {
23
+ asDateFieldView: () => asDateFieldView,
24
+ dayOfYearHelper: () => dayOfYearHelper,
25
+ daysInMonth: () => daysInMonth,
26
+ daysInYear: () => daysInYear2,
27
+ endOf: () => endOf,
28
+ getMonth: () => getMonth,
29
+ getQuarter: () => getQuarter,
30
+ getWeekday: () => getWeekday,
31
+ isLeapMonth: () => isLeapMonth,
32
+ isLeapYear: () => isLeapYear,
33
+ monthsInYear: () => monthsInYear,
34
+ startOf: () => startOf,
35
+ weekOfYear: () => weekOfYear,
36
+ weekYear: () => weekYear
37
+ });
38
+ module.exports = __toCommonJS(calendarUtils_exports);
39
+
40
+ // src/isoWeek.ts
41
+ var CUMULATIVE_DAYS_BY_MONTH = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334];
42
+ function isGregorianLeapYear(year) {
43
+ return year % 4 === 0 && year % 100 !== 0 || year % 400 === 0;
44
+ }
45
+ function daysInYear(year) {
46
+ return isGregorianLeapYear(year) ? 366 : 365;
47
+ }
48
+ function dayOfYear(year, month, day) {
49
+ let doy = CUMULATIVE_DAYS_BY_MONTH[month - 1] + day;
50
+ if (month > 2 && isGregorianLeapYear(year)) doy += 1;
51
+ return doy;
52
+ }
53
+ var REFERENCE_YEAR = 2e3;
54
+ function dayOfWeekOfJan1(year) {
55
+ let offset = 0;
56
+ if (year >= REFERENCE_YEAR) {
57
+ for (let y = REFERENCE_YEAR; y < year; y++) offset += daysInYear(y);
58
+ } else {
59
+ for (let y = year; y < REFERENCE_YEAR; y++) offset -= daysInYear(y);
60
+ }
61
+ const zeroIndexed = ((6 - 1 + offset) % 7 + 7) % 7;
62
+ return zeroIndexed + 1;
63
+ }
64
+ function isoWeekYearAndWeek(year, month, day, dayOfWeek) {
65
+ const doy = dayOfYear(year, month, day);
66
+ const thursdayDoyRelative = doy + (4 - dayOfWeek);
67
+ let isoYear;
68
+ let thursdayDoy;
69
+ if (thursdayDoyRelative < 1) {
70
+ isoYear = year - 1;
71
+ thursdayDoy = thursdayDoyRelative + daysInYear(isoYear);
72
+ } else if (thursdayDoyRelative > daysInYear(year)) {
73
+ isoYear = year + 1;
74
+ thursdayDoy = thursdayDoyRelative - daysInYear(year);
75
+ } else {
76
+ isoYear = year;
77
+ thursdayDoy = thursdayDoyRelative;
78
+ }
79
+ const jan1Dow = dayOfWeekOfJan1(isoYear);
80
+ const firstThursdayDoy = 1 + (4 - jan1Dow + 7) % 7;
81
+ const week = 1 + Math.floor((thursdayDoy - firstThursdayDoy) / 7);
82
+ return { isoYear, week };
83
+ }
84
+
85
+ // src/calendarUtils.ts
86
+ function requireFields(view, fields) {
87
+ for (const f of fields) {
88
+ if (typeof view[f] !== "number") {
89
+ throw new Error(
90
+ `temporal-fmt: calendar helper requires "${String(f)}", which this value doesn't have. Pass a Temporal.PlainDate / PlainDateTime / ZonedDateTime.`
91
+ );
92
+ }
93
+ }
94
+ }
95
+ function daysInMonth(view) {
96
+ requireFields(view, ["year", "month"]);
97
+ const { year, month } = view;
98
+ const LENGTHS = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
99
+ if (month === 2 && isGregorianLeapYear(year)) return 29;
100
+ return LENGTHS[month - 1];
101
+ }
102
+ function daysInYear2(view) {
103
+ requireFields(view, ["year"]);
104
+ return isGregorianLeapYear(view.year) ? 366 : 365;
105
+ }
106
+ function monthsInYear(_view) {
107
+ return 12;
108
+ }
109
+ function isLeapYear(view) {
110
+ requireFields(view, ["year"]);
111
+ return isGregorianLeapYear(view.year);
112
+ }
113
+ function isLeapMonth(_view) {
114
+ return false;
115
+ }
116
+ function dayOfYearHelper(view) {
117
+ requireFields(view, ["year", "month", "day"]);
118
+ return dayOfYear(view.year, view.month, view.day);
119
+ }
120
+ function weekOfYear(view) {
121
+ requireFields(view, ["year", "month", "day", "dayOfWeek"]);
122
+ return isoWeekYearAndWeek(view.year, view.month, view.day, view.dayOfWeek).week;
123
+ }
124
+ function weekYear(view) {
125
+ requireFields(view, ["year", "month", "day", "dayOfWeek"]);
126
+ return isoWeekYearAndWeek(view.year, view.month, view.day, view.dayOfWeek).isoYear;
127
+ }
128
+ function validateStartMonth(startMonth) {
129
+ if (!Number.isInteger(startMonth) || startMonth < 1 || startMonth > 12) {
130
+ throw new Error(
131
+ `temporal-fmt: getQuarter's startMonth must be an integer from 1 to 12 (got ${startMonth}).`
132
+ );
133
+ }
134
+ }
135
+ function getQuarter(view, options = {}) {
136
+ requireFields(view, ["month"]);
137
+ const startMonth = options.startMonth ?? 1;
138
+ validateStartMonth(startMonth);
139
+ if (startMonth === 1) {
140
+ return Math.ceil(view.month / 3);
141
+ }
142
+ const shifted = (view.month - startMonth + 12) % 12 + 1;
143
+ return Math.ceil(shifted / 3);
144
+ }
145
+ function getMonth(view) {
146
+ requireFields(view, ["month"]);
147
+ return view.month;
148
+ }
149
+ function getWeekday(view) {
150
+ requireFields(view, ["dayOfWeek"]);
151
+ return view.dayOfWeek;
152
+ }
153
+ function touchesTime(unit) {
154
+ return unit === "day" || unit === "month" || unit === "year";
155
+ }
156
+ function recomputeDayOfWeek(view) {
157
+ if (typeof view.dayOfWeek !== "number") return;
158
+ /* c8 ignore start @preserve -- unreachable: startOf()/endOf() both call
159
+ * asDateFieldView() before this, which already throws if year/month/day
160
+ * aren't all numbers — so by the time a value with a numeric dayOfWeek
161
+ * reaches here, year/month/day are guaranteed present too. */
162
+ if (typeof view.year !== "number" || typeof view.month !== "number" || typeof view.day !== "number") return;
163
+ /* c8 ignore stop @preserve */
164
+ const jsDow = new Date(Date.UTC(view.year, view.month - 1, view.day)).getUTCDay();
165
+ view.dayOfWeek = jsDow === 0 ? 7 : jsDow;
166
+ }
167
+ function startOf(value, unit) {
168
+ const view = asDateFieldView(value);
169
+ const result = { ...view };
170
+ if (unit === "year") {
171
+ result.month = 1;
172
+ result.day = 1;
173
+ } else if (unit === "month") {
174
+ result.day = 1;
175
+ }
176
+ recomputeDayOfWeek(result);
177
+ if (touchesTime(unit)) {
178
+ result.hour = 0;
179
+ result.minute = 0;
180
+ result.second = 0;
181
+ result.millisecond = 0;
182
+ } else if (unit === "hour") {
183
+ result.minute = 0;
184
+ result.second = 0;
185
+ result.millisecond = 0;
186
+ } else if (unit === "minute") {
187
+ result.second = 0;
188
+ result.millisecond = 0;
189
+ } else if (unit === "second") {
190
+ result.millisecond = 0;
191
+ }
192
+ return result;
193
+ }
194
+ function endOf(value, unit) {
195
+ const view = asDateFieldView(value);
196
+ const result = { ...view };
197
+ if (unit === "year") {
198
+ result.month = 12;
199
+ result.day = daysInMonth({ year: result.year, month: 12 });
200
+ } else if (unit === "month") {
201
+ result.day = daysInMonth({ year: result.year, month: result.month });
202
+ }
203
+ recomputeDayOfWeek(result);
204
+ if (touchesTime(unit)) {
205
+ result.hour = 23;
206
+ result.minute = 59;
207
+ result.second = 59;
208
+ result.millisecond = 999;
209
+ } else if (unit === "hour") {
210
+ result.minute = 59;
211
+ result.second = 59;
212
+ result.millisecond = 999;
213
+ } else if (unit === "minute") {
214
+ result.second = 59;
215
+ result.millisecond = 999;
216
+ } else if (unit === "second") {
217
+ result.millisecond = 999;
218
+ }
219
+ return result;
220
+ }
221
+ function asDateFieldView(value) {
222
+ if (typeof value !== "object" || value === null) {
223
+ throw new Error(`temporal-fmt: expected a date-carrying Temporal value, got ${String(value)}.`);
224
+ }
225
+ const v = value;
226
+ const out = {};
227
+ if (typeof v.year === "number") out.year = v.year;
228
+ if (typeof v.month === "number") out.month = v.month;
229
+ if (typeof v.day === "number") out.day = v.day;
230
+ if (typeof v.hour === "number") out.hour = v.hour;
231
+ if (typeof v.minute === "number") out.minute = v.minute;
232
+ if (typeof v.second === "number") out.second = v.second;
233
+ if (typeof v.millisecond === "number") out.millisecond = v.millisecond;
234
+ if (typeof v.dayOfWeek === "number") out.dayOfWeek = v.dayOfWeek;
235
+ if (typeof v.calendarId === "string") out.calendarId = v.calendarId;
236
+ if (out.year === void 0 || out.month === void 0 || out.day === void 0) {
237
+ throw new Error(
238
+ `temporal-fmt: value is missing year/month/day fields \u2014 pass a Temporal.PlainDate / PlainDateTime / ZonedDateTime.`
239
+ );
240
+ }
241
+ return out;
242
+ }
243
+ // Annotate the CommonJS export names for ESM import in node:
244
+ 0 && (module.exports = {
245
+ asDateFieldView,
246
+ dayOfYearHelper,
247
+ daysInMonth,
248
+ daysInYear,
249
+ endOf,
250
+ getMonth,
251
+ getQuarter,
252
+ getWeekday,
253
+ isLeapMonth,
254
+ isLeapYear,
255
+ monthsInYear,
256
+ startOf,
257
+ weekOfYear,
258
+ weekYear
259
+ });
5
260
  //# sourceMappingURL=calendarUtils.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/calendarUtils.ts","../src/isoWeek.ts"],"sourcesContent":["// Calendar utility helpers. These are pure functions\n// over the TemporalLike shape — no Temporal namespace needed, same\n// approach as isoWeek.ts and the field-reading helpers in format.ts.\n// Letting callers compute dayOfYear/weekOfYear/etc. without going\n// through format() means they can build their own derived values\n// without committing to a string format.\n//\n// Calendar-sensitivity: the helpers in this module assume the\n// iso8601 (Gregorian) calendar — that's what TemporalLike fields\n// carry for the overwhelming majority of callers. Non-Gregorian\n// calendars (hebrew, islamic, etc.) need their own helpers; this\n// module doesn't try to be calendar-polymorphic the way Temporal\n// itself is. Documented limitation, not a design choice — see\n// VERIFICATION.md for the rationale.\n\nimport { isGregorianLeapYear, dayOfYear, isoWeekYearAndWeek } from './isoWeek.js';\n\n// A subset of TemporalLike that has the date fields these helpers need,\n// plus optional time fields. PlainTime isn't a DateFieldView (no\n// year/month/day), but PlainDateTime / ZonedDateTime / PlainDate all\n// match. Time fields are optional so callers can pass a PlainDate\n// to startOf(value, 'month') without having to populate hour/minute/etc.\n// Exported so the comparison/arithmetic modules can use the same\n// narrowing.\nexport interface DateFieldView {\n year?: number;\n month?: number;\n day?: number;\n hour?: number;\n minute?: number;\n second?: number;\n millisecond?: number;\n dayOfWeek?: number;\n calendarId?: string;\n}\n\nfunction requireFields(view: DateFieldView, fields: Array<keyof DateFieldView>): void {\n for (const f of fields) {\n if (typeof view[f] !== 'number') {\n throw new Error(\n `temporal-fmt: calendar helper requires \"${String(f)}\", which this value doesn't have. ` +\n `Pass a Temporal.PlainDate / PlainDateTime / ZonedDateTime.`\n );\n }\n }\n}\n\nexport function daysInMonth(view: DateFieldView): number {\n requireFields(view, ['year', 'month']);\n const { year, month } = view;\n // Standard Gregorian month lengths. February's length depends on\n // whether `year` is a leap year — the same isGregorianLeapYear check\n // isoWeek.ts uses for dayOfYear arithmetic.\n const LENGTHS = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];\n if (month === 2 && isGregorianLeapYear(year!)) return 29;\n return LENGTHS[(month! - 1)!]!;\n}\n\nexport function daysInYear(view: DateFieldView): 365 | 366 {\n requireFields(view, ['year']);\n return isGregorianLeapYear(view.year!) ? 366 : 365;\n}\n\nexport function monthsInYear(_view: DateFieldView): 12 {\n // Gregorian always has 12 months. Other calendars (Hebrew leap years\n // have 13) need calendar-aware logic this module doesn't carry — see\n // the file-level comment. The `_view` parameter is kept so the\n // signature mirrors the other helpers and a future calendar-aware\n // implementation can use it without changing call sites.\n return 12;\n}\n\nexport function isLeapYear(view: DateFieldView): boolean {\n requireFields(view, ['year']);\n return isGregorianLeapYear(view.year!);\n}\n\n// `isLeapMonth` would require knowing which month of a leap-year-aware\n// calendar is the leap month — Gregorian doesn't have one, so this\n// returns false unconditionally. Kept here so the public surface\n// matches the module's listing; non-Gregorian calendars need\n// a different implementation.\nexport function isLeapMonth(_view: DateFieldView): boolean {\n return false;\n}\n\nexport function dayOfYearHelper(view: DateFieldView): number {\n requireFields(view, ['year', 'month', 'day']);\n return dayOfYear(view.year!, view.month!, view.day!);\n}\n\n// ISO 8601 week and week-year. Delegates to isoWeek.ts's\n// isoWeekYearAndWeek, which does the full Thursday-of-week\n// computation to handle the year-boundary cases (Dec 29-31 belonging\n// to week 1 of next year, Jan 1-3 belonging to week 52/53 of the\n// previous year).\nexport function weekOfYear(view: DateFieldView): number {\n requireFields(view, ['year', 'month', 'day', 'dayOfWeek']);\n return isoWeekYearAndWeek(view.year!, view.month!, view.day!, view.dayOfWeek!).week;\n}\n\nexport function weekYear(view: DateFieldView): number {\n requireFields(view, ['year', 'month', 'day', 'dayOfWeek']);\n return isoWeekYearAndWeek(view.year!, view.month!, view.day!, view.dayOfWeek!).isoYear;\n}\n\n/**\n * Fiscal-quarter options. `startMonth` is the calendar month (1-12) the\n * fiscal year begins on — e.g. `7` for a fiscal year starting in July.\n * Omitted or `1` gives the calendar-quarter behavior getQuarter() has\n * always had (Jan-Mar = Q1, etc.), so existing callers passing nothing\n * see no change.\n */\nexport interface QuarterOptions {\n startMonth?: number;\n}\n\nfunction validateStartMonth(startMonth: number): void {\n if (!Number.isInteger(startMonth) || startMonth < 1 || startMonth > 12) {\n throw new Error(\n `temporal-fmt: getQuarter's startMonth must be an integer from 1 to 12 (got ${startMonth}).`\n );\n }\n}\n\nexport function getQuarter(view: DateFieldView, options: QuarterOptions = {}): number {\n requireFields(view, ['month']);\n const startMonth = options.startMonth ?? 1;\n validateStartMonth(startMonth);\n if (startMonth === 1) {\n // Mirrors the Q token: months 1-3 → Q1, 4-6 → Q2, 7-9 → Q3, 10-12 → Q4.\n return Math.ceil(view.month! / 3);\n }\n // Fiscal case: shift the month so startMonth becomes month 1 of the\n // fiscal year (mod 12, 1-indexed), then apply the same ceil(/3) rule.\n // E.g. startMonth=7 (fiscal year starts July): July→1, Aug→2, ...,\n // Dec→6, Jan→7, ..., June→12. Then Q1 = fiscal months 1-3 (Jul-Sep),\n // matching the common \"FY starts in July\" convention where Q1 is the\n // first quarter of the fiscal year, not a quarter numbered by which\n // calendar quarter it falls in.\n const shifted = ((view.month! - startMonth + 12) % 12) + 1;\n return Math.ceil(shifted / 3);\n}\n\n// `getMonth` / `getWeekday` look trivial (just read the field) but\n// they're kept as real functions for API surface consistency with the\n// rest of this module. They also normalize: getWeekday returns 1-7\n// (Mon-Sun, matching Temporal's spec) regardless of what numbering the\n// caller's underlying value uses.\nexport function getMonth(view: DateFieldView): number {\n requireFields(view, ['month']);\n return view.month!;\n}\n\nexport function getWeekday(view: DateFieldView): number {\n requireFields(view, ['dayOfWeek']);\n return view.dayOfWeek!;\n}\n\n// startOf / endOf return new field bags (not Temporal objects — this\n// module is polyfill-free) with the relevant fields zeroed/extended.\n// Callers can pass the result to a Temporal constructor if they want\n// a typed value.\nexport type StartOfUnit = 'day' | 'month' | 'year' | 'hour' | 'minute' | 'second';\n\n// Returns true if the given unit (when used with startOf/endOf) should\n// also touch the time fields. 'day', 'month', 'year' all imply a\n// resolution coarser than an hour, so startOf zeroes the time fields\n// and endOf maxes them. Sub-hour units (hour/minute/second) only touch\n// the fields finer than themselves.\nfunction touchesTime(unit: StartOfUnit): boolean {\n return unit === 'day' || unit === 'month' || unit === 'year';\n}\n\n// startOf/endOf reassign year/month/day, which invalidates any\n// dayOfWeek carried over from the input — a plain { ...view } spread\n// leaves the old value sitting there unchanged. Same failure mode\n// businessCalendar.ts's isBusinessDay() works around for add(); we\n// recompute here rather than trust the copied field.\nfunction recomputeDayOfWeek(view: DateFieldView): void {\n if (typeof view.dayOfWeek !== 'number') return;\n /* c8 ignore start @preserve -- unreachable: startOf()/endOf() both call\n * asDateFieldView() before this, which already throws if year/month/day\n * aren't all numbers — so by the time a value with a numeric dayOfWeek\n * reaches here, year/month/day are guaranteed present too. */\n if (typeof view.year !== 'number' || typeof view.month !== 'number' || typeof view.day !== 'number') return;\n /* c8 ignore stop @preserve */\n const jsDow = new Date(Date.UTC(view.year, view.month - 1, view.day)).getUTCDay(); // 0=Sun..6=Sat\n view.dayOfWeek = jsDow === 0 ? 7 : jsDow; // 1=Mon..7=Sun\n}\n\nexport function startOf(value: unknown, unit: StartOfUnit): DateFieldView {\n const view = asDateFieldView(value);\n const result: DateFieldView = { ...view };\n if (unit === 'year') {\n result.month = 1;\n result.day = 1;\n } else if (unit === 'month') {\n result.day = 1;\n }\n recomputeDayOfWeek(result);\n if (touchesTime(unit)) {\n result.hour = 0;\n result.minute = 0;\n result.second = 0;\n result.millisecond = 0;\n } else if (unit === 'hour') {\n result.minute = 0;\n result.second = 0;\n result.millisecond = 0;\n } else if (unit === 'minute') {\n result.second = 0;\n result.millisecond = 0;\n } else if (unit === 'second') {\n result.millisecond = 0;\n }\n return result;\n}\n\nexport function endOf(value: unknown, unit: StartOfUnit): DateFieldView {\n const view = asDateFieldView(value);\n const result: DateFieldView = { ...view };\n if (unit === 'year') {\n result.month = 12;\n result.day = daysInMonth({ year: result.year!, month: 12 });\n } else if (unit === 'month') {\n result.day = daysInMonth({ year: result.year!, month: result.month! });\n }\n recomputeDayOfWeek(result);\n if (touchesTime(unit)) {\n result.hour = 23;\n result.minute = 59;\n result.second = 59;\n result.millisecond = 999;\n } else if (unit === 'hour') {\n result.minute = 59;\n result.second = 59;\n result.millisecond = 999;\n } else if (unit === 'minute') {\n result.second = 59;\n result.millisecond = 999;\n } else if (unit === 'second') {\n result.millisecond = 999;\n }\n return result;\n}\n\n// Type-narrowing helpers used by the comparison/arithmetic modules.\n// Lets them accept any of the four date-carrying Temporal types without\n// importing Temporal itself.\nexport function asDateFieldView(value: unknown): DateFieldView {\n if (typeof value !== 'object' || value === null) {\n throw new Error(`temporal-fmt: expected a date-carrying Temporal value, got ${String(value)}.`);\n }\n // Temporal instances expose year/month/day/etc. as prototype getters,\n // not own enumerable properties — so `{ ...value }` would lose them.\n // Read them explicitly. Only the fields actually present on this\n // value type end up in the returned view.\n const v = value as Record<string, unknown>;\n const out: DateFieldView = {};\n if (typeof v.year === 'number') out.year = v.year;\n if (typeof v.month === 'number') out.month = v.month;\n if (typeof v.day === 'number') out.day = v.day;\n if (typeof v.hour === 'number') out.hour = v.hour;\n if (typeof v.minute === 'number') out.minute = v.minute;\n if (typeof v.second === 'number') out.second = v.second;\n if (typeof v.millisecond === 'number') out.millisecond = v.millisecond;\n if (typeof v.dayOfWeek === 'number') out.dayOfWeek = v.dayOfWeek;\n if (typeof v.calendarId === 'string') out.calendarId = v.calendarId;\n if (out.year === undefined || out.month === undefined || out.day === undefined) {\n throw new Error(\n `temporal-fmt: value is missing year/month/day fields — pass a Temporal.PlainDate / PlainDateTime / ZonedDateTime.`\n );\n }\n return out;\n}\n\n// Re-export the TemporalType alias so callers can import everything\n// from one place.\nexport type { TemporalType } from './tokenMetadata.js';\n// Re-export TemporalLike for the same reason.\nexport type { TemporalLike } from './tokens.js';","// ISO 8601 week numbering: a week runs Monday–Sunday, and week 1 of a year\n// is the week containing the year's first Thursday (equivalently, the week\n// containing January 4). This means late-December dates can fall in week 1\n// of the *next* year, and early-January dates can fall in week 52 or 53 of\n// the *previous* year. The \"ISO week-numbering year\" (what `RRRR` formats)\n// is that adjacent year, not the calendar year.\n//\n// Computed here from the date's own year/month/day plus its ISO dayOfWeek\n// (1=Mon..7=Sun, matching Temporal's numbering) using plain Gregorian\n// arithmetic — no Temporal factory needed. format() only has the fields\n// the caller already put on the object, and requiring a Temporal\n// implementation just for ISO week would be a regression for callers who\n// use format() without setTemporal() on a non-26 Node.\n\nconst DAYS_IN_MONTH = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];\nconst CUMULATIVE_DAYS_BY_MONTH = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334];\n\nexport function isGregorianLeapYear(year: number): boolean {\n // Gregorian rule: divisible by 4, except centuries which must also be\n // divisible by 400. Temporal's iso8601 calendar is proleptic Gregorian\n // (no Julian cutover), so this applies for every year, including BCE.\n return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;\n}\n\nfunction daysInYear(year: number): 365 | 366 {\n return isGregorianLeapYear(year) ? 366 : 365;\n}\n\nexport function dayOfYear(year: number, month: number, day: number): number {\n let doy = CUMULATIVE_DAYS_BY_MONTH[month - 1]! + day;\n if (month > 2 && isGregorianLeapYear(year)) doy += 1;\n return doy;\n}\n\n// Jan 1, 2000 was a Saturday — ISO dayOfWeek 6. Anchoring day-of-week\n// computations to a known reference date is simpler and cheaper than\n// pulling in Zeller's congruence, and the reference never changes.\nconst REFERENCE_YEAR = 2000;\nconst REFERENCE_JAN1_DAY_OF_WEEK = 6;\n\nfunction dayOfWeekOfJan1(year: number): number {\n // Sum full-year deltas from the 2000 anchor rather than recomputing from\n // scratch each call — the per-call work is a single mod this way, and\n // the loop rarely runs far (a typical caller passes a current-era year).\n let offset = 0;\n if (year >= REFERENCE_YEAR) {\n for (let y = REFERENCE_YEAR; y < year; y++) offset += daysInYear(y);\n } else {\n for (let y = year; y < REFERENCE_YEAR; y++) offset -= daysInYear(y);\n }\n // Convert \"days since Jan 1, 2000\" into an ISO day-of-week (1=Mon..7=Sun).\n // Jan 1, 2000 was ISO 6 (Sat), so zero-indexed dow = (6-1 + offset) mod 7.\n const zeroIndexed = (((6 - 1 + offset) % 7) + 7) % 7;\n return zeroIndexed + 1;\n}\n\nexport interface IsoWeekDate {\n isoYear: number;\n week: number; // 1..53\n}\n\nexport function isoWeekYearAndWeek(year: number, month: number, day: number, dayOfWeek: number): IsoWeekDate {\n // Step 1: find the Thursday of the current ISO week. The ISO week-numbering\n // year is whichever calendar year that Thursday falls in. Computing it via\n // day-of-year offsets (rather than constructing a Temporal.PlainDate and\n // adding days) keeps this function pure-numeric.\n const doy = dayOfYear(year, month, day);\n const thursdayDoyRelative = doy + (4 - dayOfWeek); // may be <1 or >daysInYear\n\n let isoYear: number;\n let thursdayDoy: number;\n if (thursdayDoyRelative < 1) {\n isoYear = year - 1;\n thursdayDoy = thursdayDoyRelative + daysInYear(isoYear);\n } else if (thursdayDoyRelative > daysInYear(year)) {\n isoYear = year + 1;\n thursdayDoy = thursdayDoyRelative - daysInYear(year);\n } else {\n isoYear = year;\n thursdayDoy = thursdayDoyRelative;\n }\n\n // Step 2: locate the first Thursday of isoYear — its week is week 1. The\n // first Thursday's day-of-year depends on what weekday Jan 1 of isoYear is.\n const jan1Dow = dayOfWeekOfJan1(isoYear);\n const firstThursdayDoy = 1 + ((4 - jan1Dow + 7) % 7); // 1..7\n\n // Step 3: count full weeks between the two Thursdays.\n const week = 1 + Math.floor((thursdayDoy - firstThursdayDoy) / 7);\n return { isoYear, week };\n}\n"],"mappings":"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","names":["calendarUtils_exports","__export","asDateFieldView","dayOfYearHelper","daysInMonth","daysInYear","endOf","getMonth","getQuarter","getWeekday","isLeapMonth","isLeapYear","monthsInYear","startOf","weekOfYear","weekYear","__toCommonJS","CUMULATIVE_DAYS_BY_MONTH","isGregorianLeapYear","year","daysInYear","dayOfYear","month","day","doy","REFERENCE_YEAR","dayOfWeekOfJan1","year","offset","REFERENCE_YEAR","y","daysInYear","isoWeekYearAndWeek","month","day","dayOfWeek","thursdayDoyRelative","dayOfYear","isoYear","thursdayDoy","firstThursdayDoy","week","requireFields","view","fields","f","daysInMonth","year","month","LENGTHS","isGregorianLeapYear","daysInYear","monthsInYear","_view","isLeapYear","isLeapMonth","dayOfYearHelper","dayOfYear","weekOfYear","isoWeekYearAndWeek","weekYear","validateStartMonth","startMonth","getQuarter","options","shifted","getMonth","getWeekday","touchesTime","unit","recomputeDayOfWeek","jsDow","startOf","value","result","asDateFieldView","endOf","v","out"]}
1
+ {"version":3,"sources":["../src/calendarUtils.ts","../src/isoWeek.ts"],"sourcesContent":["// Calendar utility helpers. These are pure functions\n// over the TemporalLike shape — no Temporal namespace needed, same\n// approach as isoWeek.ts and the field-reading helpers in format.ts.\n// Letting callers compute dayOfYear/weekOfYear/etc. without going\n// through format() means they can build their own derived values\n// without committing to a string format.\n//\n// Calendar-sensitivity: the helpers in this module assume the\n// iso8601 (Gregorian) calendar — that's what TemporalLike fields\n// carry for the overwhelming majority of callers. Non-Gregorian\n// calendars (hebrew, islamic, etc.) need their own helpers; this\n// module doesn't try to be calendar-polymorphic the way Temporal\n// itself is. Documented limitation, not a design choice — see\n// VERIFICATION.md for the rationale.\n\nimport { isGregorianLeapYear, dayOfYear, isoWeekYearAndWeek } from './isoWeek.js';\n\n// A subset of TemporalLike that has the date fields these helpers need,\n// plus optional time fields. PlainTime isn't a DateFieldView (no\n// year/month/day), but PlainDateTime / ZonedDateTime / PlainDate all\n// match. Time fields are optional so callers can pass a PlainDate\n// to startOf(value, 'month') without having to populate hour/minute/etc.\n// Exported so the comparison/arithmetic modules can use the same\n// narrowing.\nexport interface DateFieldView {\n year?: number;\n month?: number;\n day?: number;\n hour?: number;\n minute?: number;\n second?: number;\n millisecond?: number;\n dayOfWeek?: number;\n calendarId?: string;\n}\n\nfunction requireFields(view: DateFieldView, fields: Array<keyof DateFieldView>): void {\n for (const f of fields) {\n if (typeof view[f] !== 'number') {\n throw new Error(\n `temporal-fmt: calendar helper requires \"${String(f)}\", which this value doesn't have. ` +\n `Pass a Temporal.PlainDate / PlainDateTime / ZonedDateTime.`\n );\n }\n }\n}\n\nexport function daysInMonth(view: DateFieldView): number {\n requireFields(view, ['year', 'month']);\n const { year, month } = view;\n // Standard Gregorian month lengths. February's length depends on\n // whether `year` is a leap year — the same isGregorianLeapYear check\n // isoWeek.ts uses for dayOfYear arithmetic.\n const LENGTHS = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];\n if (month === 2 && isGregorianLeapYear(year!)) return 29;\n return LENGTHS[(month! - 1)!]!;\n}\n\nexport function daysInYear(view: DateFieldView): 365 | 366 {\n requireFields(view, ['year']);\n return isGregorianLeapYear(view.year!) ? 366 : 365;\n}\n\nexport function monthsInYear(_view: DateFieldView): 12 {\n // Gregorian always has 12 months. Other calendars (Hebrew leap years\n // have 13) need calendar-aware logic this module doesn't carry — see\n // the file-level comment. The `_view` parameter is kept so the\n // signature mirrors the other helpers and a future calendar-aware\n // implementation can use it without changing call sites.\n return 12;\n}\n\nexport function isLeapYear(view: DateFieldView): boolean {\n requireFields(view, ['year']);\n return isGregorianLeapYear(view.year!);\n}\n\n// `isLeapMonth` would require knowing which month of a leap-year-aware\n// calendar is the leap month — Gregorian doesn't have one, so this\n// returns false unconditionally. Kept here so the public surface\n// matches the module's listing; non-Gregorian calendars need\n// a different implementation.\nexport function isLeapMonth(_view: DateFieldView): boolean {\n return false;\n}\n\nexport function dayOfYearHelper(view: DateFieldView): number {\n requireFields(view, ['year', 'month', 'day']);\n return dayOfYear(view.year!, view.month!, view.day!);\n}\n\n// ISO 8601 week and week-year. Delegates to isoWeek.ts's\n// isoWeekYearAndWeek, which does the full Thursday-of-week\n// computation to handle the year-boundary cases (Dec 29-31 belonging\n// to week 1 of next year, Jan 1-3 belonging to week 52/53 of the\n// previous year).\nexport function weekOfYear(view: DateFieldView): number {\n requireFields(view, ['year', 'month', 'day', 'dayOfWeek']);\n return isoWeekYearAndWeek(view.year!, view.month!, view.day!, view.dayOfWeek!).week;\n}\n\nexport function weekYear(view: DateFieldView): number {\n requireFields(view, ['year', 'month', 'day', 'dayOfWeek']);\n return isoWeekYearAndWeek(view.year!, view.month!, view.day!, view.dayOfWeek!).isoYear;\n}\n\n/**\n * Fiscal-quarter options. `startMonth` is the calendar month (1-12) the\n * fiscal year begins on — e.g. `7` for a fiscal year starting in July.\n * Omitted or `1` gives the calendar-quarter behavior getQuarter() has\n * always had (Jan-Mar = Q1, etc.), so existing callers passing nothing\n * see no change.\n */\nexport interface QuarterOptions {\n startMonth?: number;\n}\n\nfunction validateStartMonth(startMonth: number): void {\n if (!Number.isInteger(startMonth) || startMonth < 1 || startMonth > 12) {\n throw new Error(\n `temporal-fmt: getQuarter's startMonth must be an integer from 1 to 12 (got ${startMonth}).`\n );\n }\n}\n\nexport function getQuarter(view: DateFieldView, options: QuarterOptions = {}): number {\n requireFields(view, ['month']);\n const startMonth = options.startMonth ?? 1;\n validateStartMonth(startMonth);\n if (startMonth === 1) {\n // Mirrors the Q token: months 1-3 → Q1, 4-6 → Q2, 7-9 → Q3, 10-12 → Q4.\n return Math.ceil(view.month! / 3);\n }\n // Fiscal case: shift the month so startMonth becomes month 1 of the\n // fiscal year (mod 12, 1-indexed), then apply the same ceil(/3) rule.\n // E.g. startMonth=7 (fiscal year starts July): July→1, Aug→2, ...,\n // Dec→6, Jan→7, ..., June→12. Then Q1 = fiscal months 1-3 (Jul-Sep),\n // matching the common \"FY starts in July\" convention where Q1 is the\n // first quarter of the fiscal year, not a quarter numbered by which\n // calendar quarter it falls in.\n const shifted = ((view.month! - startMonth + 12) % 12) + 1;\n return Math.ceil(shifted / 3);\n}\n\n// `getMonth` / `getWeekday` look trivial (just read the field) but\n// they're kept as real functions for API surface consistency with the\n// rest of this module. They also normalize: getWeekday returns 1-7\n// (Mon-Sun, matching Temporal's spec) regardless of what numbering the\n// caller's underlying value uses.\nexport function getMonth(view: DateFieldView): number {\n requireFields(view, ['month']);\n return view.month!;\n}\n\nexport function getWeekday(view: DateFieldView): number {\n requireFields(view, ['dayOfWeek']);\n return view.dayOfWeek!;\n}\n\n// startOf / endOf return new field bags (not Temporal objects — this\n// module is polyfill-free) with the relevant fields zeroed/extended.\n// Callers can pass the result to a Temporal constructor if they want\n// a typed value.\nexport type StartOfUnit = 'day' | 'month' | 'year' | 'hour' | 'minute' | 'second';\n\n// Returns true if the given unit (when used with startOf/endOf) should\n// also touch the time fields. 'day', 'month', 'year' all imply a\n// resolution coarser than an hour, so startOf zeroes the time fields\n// and endOf maxes them. Sub-hour units (hour/minute/second) only touch\n// the fields finer than themselves.\nfunction touchesTime(unit: StartOfUnit): boolean {\n return unit === 'day' || unit === 'month' || unit === 'year';\n}\n\n// startOf/endOf reassign year/month/day, which invalidates any\n// dayOfWeek carried over from the input — a plain { ...view } spread\n// leaves the old value sitting there unchanged. Same failure mode\n// businessCalendar.ts's isBusinessDay() works around for add(); we\n// recompute here rather than trust the copied field.\nfunction recomputeDayOfWeek(view: DateFieldView): void {\n if (typeof view.dayOfWeek !== 'number') return;\n /* c8 ignore start @preserve -- unreachable: startOf()/endOf() both call\n * asDateFieldView() before this, which already throws if year/month/day\n * aren't all numbers — so by the time a value with a numeric dayOfWeek\n * reaches here, year/month/day are guaranteed present too. */\n if (typeof view.year !== 'number' || typeof view.month !== 'number' || typeof view.day !== 'number') return;\n /* c8 ignore stop @preserve */\n const jsDow = new Date(Date.UTC(view.year, view.month - 1, view.day)).getUTCDay(); // 0=Sun..6=Sat\n view.dayOfWeek = jsDow === 0 ? 7 : jsDow; // 1=Mon..7=Sun\n}\n\nexport function startOf(value: unknown, unit: StartOfUnit): DateFieldView {\n const view = asDateFieldView(value);\n const result: DateFieldView = { ...view };\n if (unit === 'year') {\n result.month = 1;\n result.day = 1;\n } else if (unit === 'month') {\n result.day = 1;\n }\n recomputeDayOfWeek(result);\n if (touchesTime(unit)) {\n result.hour = 0;\n result.minute = 0;\n result.second = 0;\n result.millisecond = 0;\n } else if (unit === 'hour') {\n result.minute = 0;\n result.second = 0;\n result.millisecond = 0;\n } else if (unit === 'minute') {\n result.second = 0;\n result.millisecond = 0;\n } else if (unit === 'second') {\n result.millisecond = 0;\n }\n return result;\n}\n\nexport function endOf(value: unknown, unit: StartOfUnit): DateFieldView {\n const view = asDateFieldView(value);\n const result: DateFieldView = { ...view };\n if (unit === 'year') {\n result.month = 12;\n result.day = daysInMonth({ year: result.year!, month: 12 });\n } else if (unit === 'month') {\n result.day = daysInMonth({ year: result.year!, month: result.month! });\n }\n recomputeDayOfWeek(result);\n if (touchesTime(unit)) {\n result.hour = 23;\n result.minute = 59;\n result.second = 59;\n result.millisecond = 999;\n } else if (unit === 'hour') {\n result.minute = 59;\n result.second = 59;\n result.millisecond = 999;\n } else if (unit === 'minute') {\n result.second = 59;\n result.millisecond = 999;\n } else if (unit === 'second') {\n result.millisecond = 999;\n }\n return result;\n}\n\n// Type-narrowing helpers used by the comparison/arithmetic modules.\n// Lets them accept any of the four date-carrying Temporal types without\n// importing Temporal itself.\nexport function asDateFieldView(value: unknown): DateFieldView {\n if (typeof value !== 'object' || value === null) {\n throw new Error(`temporal-fmt: expected a date-carrying Temporal value, got ${String(value)}.`);\n }\n // Temporal instances expose year/month/day/etc. as prototype getters,\n // not own enumerable properties — so `{ ...value }` would lose them.\n // Read them explicitly. Only the fields actually present on this\n // value type end up in the returned view.\n const v = value as Record<string, unknown>;\n const out: DateFieldView = {};\n if (typeof v.year === 'number') out.year = v.year;\n if (typeof v.month === 'number') out.month = v.month;\n if (typeof v.day === 'number') out.day = v.day;\n if (typeof v.hour === 'number') out.hour = v.hour;\n if (typeof v.minute === 'number') out.minute = v.minute;\n if (typeof v.second === 'number') out.second = v.second;\n if (typeof v.millisecond === 'number') out.millisecond = v.millisecond;\n if (typeof v.dayOfWeek === 'number') out.dayOfWeek = v.dayOfWeek;\n if (typeof v.calendarId === 'string') out.calendarId = v.calendarId;\n if (out.year === undefined || out.month === undefined || out.day === undefined) {\n throw new Error(\n `temporal-fmt: value is missing year/month/day fields — pass a Temporal.PlainDate / PlainDateTime / ZonedDateTime.`\n );\n }\n return out;\n}\n\n// Re-export the TemporalType alias so callers can import everything\n// from one place.\nexport type { TemporalType } from './tokenMetadata.js';\n// Re-export TemporalLike for the same reason.\nexport type { TemporalLike } from './tokens.js';","// ISO week stuff. week runs Mon-Sun, and week 1 is whichever week has\n// the year's first Thursday in it (same as saying \"the week with Jan 4\").\n// took me a minute to wrap my head around this but the upshot is late-Dec\n// dates can land in week 1 of NEXT year, and early-Jan dates can land in\n// week 52/53 of the PREVIOUS year. that adjacent year is what RRRR prints,\n// not the plain calendar year.\n//\n// doing this with plain year/month/day + dayOfWeek math instead of asking\n// Temporal for it, since format() only has whatever fields got handed in\n// and dragging in a whole Temporal implementation just for week numbers\n// felt like overkill (also breaks for people not on setTemporal()).\n\nconst DAYS_IN_MONTH = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];\nconst CUMULATIVE_DAYS_BY_MONTH = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334];\n\nexport function isGregorianLeapYear(year: number): boolean {\n // divisible by 4, unless it's a century, then it also needs /400.\n // Temporal's iso8601 calendar never switches to Julian, so this rule\n // just applies all the way back, even for BCE years.\n return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;\n}\n\nfunction daysInYear(year: number): 365 | 366 {\n return isGregorianLeapYear(year) ? 366 : 365;\n}\n\nexport function dayOfYear(year: number, month: number, day: number): number {\n let doy = CUMULATIVE_DAYS_BY_MONTH[month - 1]! + day;\n if (month > 2 && isGregorianLeapYear(year)) doy += 1;\n return doy;\n}\n\n// Jan 1 2000 was a Saturday (ISO dow 6). picked this as an anchor point\n// since it's easier than reaching for Zeller's congruence every time,\n// and obviously this fact about Jan 1 2000 isn't going to change on us\nconst REFERENCE_YEAR = 2000;\nconst REFERENCE_JAN1_DAY_OF_WEEK = 6;\n\nfunction dayOfWeekOfJan1(year: number): number {\n // walk year by year from 2000 instead of doing this from scratch every\n // time — most callers pass a current-era year so this loop is short anyway\n let offset = 0;\n if (year >= REFERENCE_YEAR) {\n for (let y = REFERENCE_YEAR; y < year; y++) offset += daysInYear(y);\n } else {\n for (let y = year; y < REFERENCE_YEAR; y++) offset -= daysInYear(y);\n }\n // now turn \"days since Jan 1 2000\" into an ISO weekday. Jan 1 2000 was a 6\n // (Sat), so: zero-indexed = (6-1 + offset) mod 7, then bump back to 1-indexed\n const zeroIndexed = (((6 - 1 + offset) % 7) + 7) % 7;\n return zeroIndexed + 1;\n}\n\nexport interface IsoWeekDate {\n isoYear: number;\n week: number; // goes 1 to 53\n}\n\nexport function isoWeekYearAndWeek(year: number, month: number, day: number, dayOfWeek: number): IsoWeekDate {\n // step 1: figure out the Thursday of this week — whatever calendar year\n // that Thursday's in IS the ISO week-numbering year. doing this with\n // day-of-year offsets so we don't need an actual Temporal.PlainDate here\n const doy = dayOfYear(year, month, day);\n const thursdayDoyRelative = doy + (4 - dayOfWeek); // can go negative or past daysInYear, that's fine\n\n let isoYear: number;\n let thursdayDoy: number;\n if (thursdayDoyRelative < 1) {\n isoYear = year - 1;\n thursdayDoy = thursdayDoyRelative + daysInYear(isoYear);\n } else if (thursdayDoyRelative > daysInYear(year)) {\n isoYear = year + 1;\n thursdayDoy = thursdayDoyRelative - daysInYear(year);\n } else {\n isoYear = year;\n thursdayDoy = thursdayDoyRelative;\n }\n\n // step 2: find the first Thursday of isoYear, that one's week 1 by definition.\n // depends entirely on which weekday Jan 1 lands on\n const jan1Dow = dayOfWeekOfJan1(isoYear);\n const firstThursdayDoy = 1 + ((4 - jan1Dow + 7) % 7); // always lands somewhere 1-7\n\n // step 3: just count how many full weeks between the two Thursdays\n const week = 1 + Math.floor((thursdayDoy - firstThursdayDoy) / 7);\n return { isoYear, week };\n}\n"],"mappings":"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","names":["daysInYear","daysInYear"]}
@@ -1,2 +1,34 @@
1
- import{a,b,c,d,e,f,g,h,i,j,k,l,m,n}from"./chunk-EHLRZULM.js";import"./chunk-C6ZZ62ET.js";export{n as asDateFieldView,f as dayOfYearHelper,a as daysInMonth,b as daysInYear,m as endOf,j as getMonth,i as getQuarter,k as getWeekday,e as isLeapMonth,d as isLeapYear,c as monthsInYear,l as startOf,g as weekOfYear,h as weekYear};
1
+ import {
2
+ asDateFieldView,
3
+ dayOfYearHelper,
4
+ daysInMonth,
5
+ daysInYear,
6
+ endOf,
7
+ getMonth,
8
+ getQuarter,
9
+ getWeekday,
10
+ isLeapMonth,
11
+ isLeapYear,
12
+ monthsInYear,
13
+ startOf,
14
+ weekOfYear,
15
+ weekYear
16
+ } from "./chunk-HXY2GUUM.js";
17
+ import "./chunk-MKPDLQKD.js";
18
+ export {
19
+ asDateFieldView,
20
+ dayOfYearHelper,
21
+ daysInMonth,
22
+ daysInYear,
23
+ endOf,
24
+ getMonth,
25
+ getQuarter,
26
+ getWeekday,
27
+ isLeapMonth,
28
+ isLeapYear,
29
+ monthsInYear,
30
+ startOf,
31
+ weekOfYear,
32
+ weekYear
33
+ };
2
34
  //# sourceMappingURL=calendarUtils.js.map