temporal-fmt 0.9.71 → 0.9.80

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 (59) hide show
  1. package/README.md +13 -49
  2. package/dist/{chunk-MXXTJ5CW.js → chunk-5KP5H7X2.js} +3 -3
  3. package/dist/{chunk-ZT3UXRAA.js → chunk-DMJWJTFY.js} +3 -8
  4. package/dist/chunk-DMJWJTFY.js.map +1 -0
  5. package/dist/{chunk-CQJYB7E7.js → chunk-HGRT37LT.js} +4 -10
  6. package/dist/chunk-HGRT37LT.js.map +1 -0
  7. package/dist/{chunk-3FMUGYE2.js → chunk-KV6GMX4D.js} +4 -159
  8. package/dist/chunk-KV6GMX4D.js.map +1 -0
  9. package/dist/chunk-UGYSEYUQ.js +168 -0
  10. package/dist/chunk-UGYSEYUQ.js.map +1 -0
  11. package/dist/{chunk-3SC5AVSY.js → chunk-WEVNKOQA.js} +2 -2
  12. package/dist/chunk-WP7TBCTT.js +241 -0
  13. package/dist/chunk-WP7TBCTT.js.map +1 -0
  14. package/dist/{chunk-BQET6RUR.js → chunk-Z245NHDT.js} +2 -19
  15. package/dist/{chunk-BQET6RUR.js.map → chunk-Z245NHDT.js.map} +1 -1
  16. package/dist/format.cjs +0 -17
  17. package/dist/format.cjs.map +1 -1
  18. package/dist/format.js +3 -3
  19. package/dist/index.cjs +3832 -4705
  20. package/dist/index.cjs.map +1 -1
  21. package/dist/index.d.cts +20 -103
  22. package/dist/index.d.ts +20 -103
  23. package/dist/index.js +2406 -846
  24. package/dist/index.js.map +1 -1
  25. package/dist/interval.cjs +106 -4773
  26. package/dist/interval.cjs.map +1 -1
  27. package/dist/interval.js +5 -10
  28. package/dist/parse.cjs +0 -13
  29. package/dist/parse.cjs.map +1 -1
  30. package/dist/parse.js +3 -3
  31. package/dist/recurrence.js +2 -1
  32. package/dist/relativeTime.cjs.map +1 -1
  33. package/dist/relativeTime.js +2 -2
  34. package/dist/tokens.d.cts +0 -4
  35. package/dist/tokens.d.ts +0 -4
  36. package/package.json +5 -21
  37. package/MODS.md +0 -148
  38. package/dist/chunk-3FMUGYE2.js.map +0 -1
  39. package/dist/chunk-CQJYB7E7.js.map +0 -1
  40. package/dist/chunk-ZT3UXRAA.js.map +0 -1
  41. package/dist/chunk-ZTIF4ZHX.js +0 -3041
  42. package/dist/chunk-ZTIF4ZHX.js.map +0 -1
  43. package/dist/modApi.d.cts +0 -147
  44. package/dist/modApi.d.ts +0 -147
  45. package/dist/runtime.d.cts +0 -206
  46. package/dist/runtime.d.ts +0 -206
  47. package/scripts/cli.mjs +0 -324
  48. package/scripts/loadMods.d.mts +0 -37
  49. package/scripts/loadMods.mjs +0 -801
  50. package/scripts/managePermissions.mjs +0 -126
  51. package/scripts/modConfig.mjs +0 -96
  52. package/scripts/modSandbox.d.mts +0 -13
  53. package/scripts/modSandbox.mjs +0 -855
  54. package/scripts/modWire.mjs +0 -241
  55. package/scripts/modWorker.mjs +0 -574
  56. package/scripts/semverRange.mjs +0 -157
  57. package/scripts/versions.json +0 -42
  58. /package/dist/{chunk-MXXTJ5CW.js.map → chunk-5KP5H7X2.js.map} +0 -0
  59. /package/dist/{chunk-3SC5AVSY.js.map → chunk-WEVNKOQA.js.map} +0 -0
package/README.md CHANGED
@@ -23,7 +23,7 @@ on 26+, or falling back to the Temporal implementation's own
23
23
  > **Just want `format`/`parse`, nothing else, ever?** Check out
24
24
  > [`temporal-fmt-lite`](https://github.com/DirazCoder/temporal-fmt-lite) —
25
25
  > a frozen snapshot of this library's original v0.8.2 surface: the same two
26
- > functions, the same locale support, no CLI/mods/recurrence/business-calendar
26
+ > functions, the same locale support, no recurrence/business-calendar
27
27
  > weight to opt out of via subpaths, and no new tokens or exports getting
28
28
  > added later. Security and correctness fixes only, no LTS promise. If you
29
29
  > outgrow it, migrating to the full `temporal-fmt` is additive, not a rewrite.
@@ -50,7 +50,7 @@ That's the whole library for most use cases — `format(temporal, formatStr)` in
50
50
 
51
51
  Below Node 26, `Temporal` isn't global yet, so you'll need a polyfill first — see [Providing `Temporal`](#providing-temporal). On Node 26+ the snippet above just works.
52
52
 
53
- The package looks large on npm — locales, recurrence, business calendars, timezone disambiguation, an analyzer, config layers, a CLI — but none of that is required reading or required bundle weight. It's there behind its own subpaths for when you need it; see [Subpath imports](#subpath-imports) for the full list and [Formatting](#formatting)/[Parsing](#parsing) for the details on the two functions above.
53
+ The package looks large on npm — locales, recurrence, business calendars, timezone disambiguation, an analyzer, config layers — but none of that is required reading or required bundle weight. It's there behind its own subpaths for when you need it; see [Subpath imports](#subpath-imports) for the full list and [Formatting](#formatting)/[Parsing](#parsing) for the details on the two functions above.
54
54
 
55
55
  ## Contents
56
56
 
@@ -75,8 +75,7 @@ The package looks large on npm — locales, recurrence, business calendars, time
75
75
  - [Config](#config)
76
76
  - [Extending with custom tokens](#extending-with-custom-tokens)
77
77
  - [IDE tooling data](#ide-tooling-data)
78
- - [CLI](#cli)
79
- - [Mods (advanced, optional)](#mods-advanced-optional) — full guide in [MODS.md](./MODS.md)
78
+ - [Legacy mod documentation](#legacy-mod-documentation)
80
79
  - [Subpath imports](#subpath-imports)
81
80
  - [Migrating from Day.js or date-fns](#migrating-from-dayjs-or-date-fns)
82
81
  - [Related tools](#related-tools)
@@ -873,51 +872,18 @@ A handful of functions exist specifically to feed editor tooling — autocomplet
873
872
  - `getInlineDiagnostics(formatStr)` — diagnostics with position and suggested fixes for a given format string.
874
873
  - `previewFormat(formatStr, sample?)` — a live preview string for a format, given an optional sample value.
875
874
  - `getDocUrl(tokenName)` — a documentation URL for a token, currently pointing at this README's [Tokens](#tokens) section.
876
- - `DAYJS_TO_TEMPORAL_FMT` / `DATE_FNS_TO_TEMPORAL_FMT` — token conversion hint tables, the same ones the CLI's `translate` subcommand and any migration tooling draw from.
875
+ `DAYJS_TO_TEMPORAL_FMT` / `DATE_FNS_TO_TEMPORAL_FMT` expose token conversion hints for migration tooling.
877
876
 
878
- ## CLI
877
+ ## Legacy mod documentation
879
878
 
880
- 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:
879
+ Starting with version **0.9.80**, mod support is entirely removed. The package
880
+ does not discover, load, sandbox, or execute mods, and no mod loader or
881
+ permission-management CLI is included. This also removes the package CLI.
881
882
 
882
- 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`.
883
-
884
- ```sh
885
- temporal-fmt format "2026-08-04T15:45:30" "yyyy-MM-dd HH:mm:ss"
886
- temporal-fmt parse "yyyy-MM-dd" "2026-08-04"
887
- temporal-fmt inspect "MMMM d, yyyy 'at' h:mm a"
888
- temporal-fmt validate "yyyy-MM-dd HH:mm:ss"
889
- temporal-fmt translate dayjs "YYYY-MM-DD HH:mm:ss"
890
- ```
891
-
892
- | Subcommand | What it does |
893
- |------------|---------------|
894
- | `format <iso-input> <format-string> [--locale=LOCALE]` | Formats an ISO date/time input against the given format string. |
895
- | `parse <format-string> <input> [--locale=LOCALE] [--lenient]` | Parses input against a format string, prints the resulting ISO value. |
896
- | `inspect <format-string>` | Prints `explainFormat`'s report on a format string. |
897
- | `validate <format-string>` | Prints `valid` or `invalid`. |
898
- | `translate <source-lib> <format-string>` | Translates a Day.js or date-fns format string to `temporal-fmt` tokens. |
899
-
900
- `translate` is implemented in-repo (`src/codemod.ts`) against the same token tables the IDE tooling data uses — no external package, no runtime dependency beyond this library itself. It throws on tokens with no `temporal-fmt` equivalent (`Do`/`P`/etc. — see the [migration table](#token-mapping)) rather than guessing.
901
-
902
- ### Interactive mode
903
-
904
- Run `temporal-fmt` with no arguments to start a REPL:
905
-
906
- ```
907
- $ temporal-fmt
908
- temporal-fmt interactive mode. Type a subcommand, "help", or "exit".
909
- temporal-fmt> format
910
- ISO input: 2026-08-04T15:45:30
911
- Format string: yyyy-MM-dd HH:mm:ss
912
- 2026-08-04 15:45:30
913
- temporal-fmt> exit
914
- ```
915
-
916
- Type a subcommand with all its arguments inline (`validate yyyy-MM-dd`) or just the subcommand name — the REPL prompts for whatever's missing, one field at a time. Errors print and the session keeps going; `exit`, `quit`, or Ctrl+D ends it. This is the same subcommand logic as one-shot mode, just wrapped in a loop that asks instead of exiting on a missing argument — one-shot stays there for scripting, and doesn't touch the REPL machinery.
917
-
918
- ## Mods (advanced, optional)
919
-
920
- Mod support (0.9.4+) lets you extend or patch behavior without forking. MODS.md covers installing and running mods someone else wrote — the sandbox from the user side, permissions, load order, and what happens when a mod is broken. Writing your own mod is a separate guide, split by API level: API_DOCS/LEVEL_1.md (0.9.4–0.9.5), API_DOCS/LEVEL_2.md (0.9.6-0.9.61), and API_DOCS/LEVEL_3.md (0.9.70+).
883
+ Historical documentation for prior versions is retained under
884
+ [`LEGACY_VERSION_MODS/`](./LEGACY_VERSION_MODS/). It describes interfaces and
885
+ behavior that are not available in 0.9.80 and later; do not use it as current
886
+ usage guidance.
921
887
 
922
888
  ## Subpath imports
923
889
 
@@ -937,8 +903,6 @@ import { registerLocale } from 'temporal-fmt/locale';
937
903
 
938
904
  The rest of the API (arithmetic, comparison, rounding, intervals-adjacent helpers not listed above, business calendars, holidays, serialization, config, type guards, typed errors, the analyzer, and IDE tooling data) is only available from the main `temporal-fmt` entry point — there's no dedicated subpath for those yet.
939
905
 
940
- Two Node-only script subpaths exist outside the library API proper: `scripts/loadMods.mjs` (the mod loader, with its sandbox) and `scripts/modSandbox.mjs` (subprocess lifecycle for mods that installed runtime overrides). They ship in the package but aren't in the `exports` map — the loader is ESM-only and has no honest CommonJS twin, so you import them by path rather than by bare specifier. See [MODS.md](./MODS.md); most installs will never touch either.
941
-
942
906
  `sideEffects: false` is set in `package.json`, so a bundler with tree-shaking enabled genuinely drops what you don't import. Measured with esbuild: `import { format } from 'temporal-fmt/format'` bundles to ~27KB, versus ~68KB for the same single function pulled from the bare `temporal-fmt` entry — the main entry point re-exports everything, so anything imported from it drags the whole graph along regardless of what you actually call. If bundle size matters for your use case, import from the subpath, not the package root.
943
907
 
944
908
  ## Migrating from Day.js or date-fns
@@ -1037,7 +1001,7 @@ Migrate file by file, dropping the wrapper once nothing calls the old path anymo
1037
1001
  Neither of these ships as part of this repository — separate packages. **Both are now deprecated and archived** — single-maintainer bandwidth, no further updates — but the last published versions still work if you want them:
1038
1002
 
1039
1003
  - [`eslint-plugin-temporal-fmt`](https://www.npmjs.com/package/eslint-plugin-temporal-fmt) *(deprecated, archived)* — lints format strings for common mistakes (e.g. `hh` without `a`). This is what backed the `analyzeFormat(formatStr).warnings` check mentioned in [Introspection and the analyzer](#introspection-and-the-analyzer) — same underlying metadata, surfaced as a lint diagnostic instead of a runtime call.
1040
- - [`temporal-fmt-codemod`](https://github.com/DirazCoder/temporal-fmt-codemod) *(deprecated, archived)* — a jscodeshift AST codemod that rewrote `dayjs(x).format(...)`/date-fns `format(...)` *call sites* across a codebase, not just format-string literals. A different job from the CLI's `translate` subcommand (see [CLI](#cli)), which only translates a format string you hand it and doesn't touch call sites; this was the option for migrating an entire codebase and rewriting the calls themselves.
1004
+ - [`temporal-fmt-codemod`](https://github.com/DirazCoder/temporal-fmt-codemod) *(deprecated, archived)* — a jscodeshift AST codemod that rewrote `dayjs(x).format(...)`/date-fns `format(...)` *call sites* across a codebase, not just format-string literals. It migrated an entire codebase and rewrote call sites rather than only translating format strings.
1041
1005
 
1042
1006
  ## Testing
1043
1007
 
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  applyParseNumbering,
3
3
  tokenize
4
- } from "./chunk-ZT3UXRAA.js";
4
+ } from "./chunk-DMJWJTFY.js";
5
5
  import {
6
6
  DEFAULT_LOCALE
7
- } from "./chunk-BQET6RUR.js";
7
+ } from "./chunk-Z245NHDT.js";
8
8
  import {
9
9
  getTemporal
10
10
  } from "./chunk-DFWTCRS3.js";
@@ -981,4 +981,4 @@ export {
981
981
  parseToParts,
982
982
  compileParser
983
983
  };
984
- //# sourceMappingURL=chunk-MXXTJ5CW.js.map
984
+ //# sourceMappingURL=chunk-5KP5H7X2.js.map
@@ -1,9 +1,7 @@
1
1
  import {
2
2
  DEFAULT_LOCALE,
3
- TOKENS,
4
- getEffectiveTokens,
5
- onTokenTableChange
6
- } from "./chunk-BQET6RUR.js";
3
+ TOKENS
4
+ } from "./chunk-Z245NHDT.js";
7
5
  import {
8
6
  FormatSyntaxError,
9
7
  InvalidLocaleError,
@@ -14,9 +12,6 @@ import {
14
12
 
15
13
  // src/tokenize.ts
16
14
  var SORTED_TOKEN_STRINGS = TOKENS.map(([tok]) => tok).sort((a, b) => b.length - a.length);
17
- onTokenTableChange(() => {
18
- SORTED_TOKEN_STRINGS = getEffectiveTokens().map(([tok]) => tok).sort((a, b) => b.length - a.length);
19
- });
20
15
  function tokenizeWithSpans(format) {
21
16
  const pieces = [];
22
17
  let i = 0;
@@ -204,4 +199,4 @@ export {
204
199
  applyNumbering,
205
200
  applyParseNumbering
206
201
  };
207
- //# sourceMappingURL=chunk-ZT3UXRAA.js.map
202
+ //# sourceMappingURL=chunk-DMJWJTFY.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/tokenize.ts","../src/numbering.ts"],"sourcesContent":["/*\n * Copyright 2026 DirazCoder\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { TOKENS } from './tokens.js';\nimport { FormatSyntaxError, UnknownTokenError } from './errors.js';\n\nexport type Piece =\n | { kind: 'token'; value: string }\n | { kind: 'literal'; value: string };\n\n// A piece plus its exact source span in the ORIGINAL format string.\n// `start`/`end` are UTF-16 code-unit indices, half-open [start, end).\n// Decoded literals can be shorter than their span ('' is 2 source\n// chars → 1 decoded quote), which is exactly why positions computed\n// from decoded lengths (the old approach in analyze.ts) drifted after\n// any quoted text.\nexport interface SpannedPiece {\n kind: 'token' | 'literal';\n value: string;\n start: number;\n end: number;\n}\n\n// gotta sort longest-first or the greedy scan grabs \"M\" when \"MMMM\" was actually there.\nconst SORTED_TOKEN_STRINGS = TOKENS.map(([tok]) => tok).sort((a, b) => b.length - a.length);\n\n/**\n * Splits a format string like `\"yyyy-MM-dd 'at' HH:mm\"` into token/literal\n * pieces, each with its exact source span. Text in single quotes is always\n * literal (e.g. write 'rd' in \"3rd\" so it's not read as the day token). A\n * doubled quote ('') means a literal quote character, both inside a quoted\n * span and standalone.\n */\nexport function tokenizeWithSpans(format: string): SpannedPiece[] {\n const pieces: SpannedPiece[] = [];\n let i = 0;\n\n while (i < format.length) {\n const ch = format[i];\n\n if (ch === \"'\") {\n // gotta check the doubled-quote case first, otherwise \"''best''\" parses wrong\n if (format[i + 1] === \"'\") {\n appendLiteral(pieces, \"'\", i, i + 2);\n i += 2;\n continue;\n }\n\n let j = i + 1;\n let literal = '';\n let closed = false;\n while (j < format.length) {\n if (format[j] === \"'\") {\n if (format[j + 1] === \"'\") {\n literal += \"'\";\n j += 2;\n continue;\n }\n closed = true;\n j += 1;\n break;\n }\n literal += format[j];\n j += 1;\n }\n\n if (!closed) {\n throw new FormatSyntaxError({\n format,\n message: `temporal-fmt: unterminated quote in format string \"${format}\"`,\n });\n }\n\n appendLiteral(pieces, literal, i, j);\n i = j;\n continue;\n }\n\n const match = SORTED_TOKEN_STRINGS.find((tok) => format.startsWith(tok, i));\n if (match) {\n // this is already the longest token that starts here, so if there's\n // one more of the same char after it, that's not a real token — it'd\n // just silently fall through to whatever handles that next char and\n // get glued onto this one. found this the hard way: \"zzzz\" was\n // parsing as zzz + literal \"z\", \"MMMMM\" as MMMM + the M token. so now\n // we just treat the whole overlong run as one bad token instead\n const runChar = match[match.length - 1];\n if (format[i + match.length] === runChar) {\n let end = i + match.length;\n while (format[end] === runChar) end += 1;\n // UnknownTokenError, not FormatSyntaxError: wrapUntypedError's\n // classifier already treats this exact message (\"isn't a\n // recognized token\") as UNKNOWN_TOKEN (see errors.test.js's\n // \"overlong token run classifies as UnknownTokenError\"), and a\n // direct throw here needs to agree with what safeParse's fallback\n // path would have classified it as.\n throw new UnknownTokenError({\n format,\n token: format.slice(i, end),\n message:\n `temporal-fmt: \"${format.slice(i, end)}\" in format string \"${format}\" isn't a recognized token — ` +\n `did you mean \"${match}\"?`,\n });\n }\n pieces.push({ kind: 'token', value: match, start: i, end: i + match.length });\n i += match.length;\n continue;\n }\n\n // not a token or quote — pass through as-is\n appendLiteral(pieces, ch, i, i + 1);\n i += 1;\n }\n\n return pieces;\n}\n\n/**\n * Splits a format string like `\"yyyy-MM-dd 'at' HH:mm\"` into token/literal\n * pieces. Text in single quotes is always literal (e.g. write 'rd' in\n * \"3rd\" so it's not read as the day token). A doubled quote ('') means a\n * literal quote character, both inside a quoted span and standalone.\n */\nexport function tokenize(format: string): Piece[] {\n return tokenizeWithSpans(format).map(({ kind, value }) => ({ kind, value }));\n}\n\n// merges into the previous piece if it's also a literal, so \"---\" is one\n// piece instead of three (spans track the full merged range)\nfunction appendLiteral(pieces: SpannedPiece[], value: string, start: number, end: number): void {\n const last = pieces[pieces.length - 1];\n if (last && last.kind === 'literal' && last.end === start) {\n last.value += value;\n last.end = end;\n } else {\n pieces.push({ kind: 'literal', value, start, end });\n }\n}\n","/*\n * Copyright 2026 DirazCoder\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// numbering systems. latn (ASCII digits) is the default, but arab, deva,\n// beng, etc are all options, configurable per format call.\n//\n// the tokens in this lib always spit out ASCII digits normally (check\n// tokens.ts's pad() — just String(n), which is ASCII). this module bolts\n// on the ability to convert that output to a locale's native digits via\n// Intl.NumberFormat, since that's the standard way JS does digit\n// transliteration anyway.\n//\n// parse side is stricter: parse() only accepts ASCII digits, matches how\n// NUMERIC_FRAGMENTS is already built. parseNumberingSystem converts\n// input digits to ASCII first — an explicit opt-in per call, never\n// silently accepting any numeral system that shows up.\n//\n// both directions also accept 'auto': instead of requiring the caller to\n// know the ICU numbering-system code for their locale, ask\n// Intl.NumberFormat(locale).resolvedOptions().numberingSystem what the\n// locale itself uses and transliterate with that.\n\nimport { DEFAULT_LOCALE, type FormatOptions } from './tokens.js';\nimport { InvalidLocaleError } from './errors.js';\nimport { canonicalCacheKey, normalizeLocaleTag } from './localeVocab.js';\n\nexport type NumberingSystem = 'latn' | 'arab' | 'deva' | 'beng' | 'guru' | 'gujr' | 'orya' | 'tamldec' | 'telu' | 'knda' | 'mlym' | 'fullwide' | 'hanidec';\n\n// What a caller may pass as numberingSystem / parseNumberingSystem: an\n// explicit system name, or 'auto' to derive the system from the call's\n// locale. The `(string & {})` keeps arbitrary strings compiling (the\n// runtime still validates them against SUPPORTED_NUMBERING_SYSTEMS and\n// throws on unknown names, unchanged) while editors still offer the\n// known literals — 'latn', 'arab', ..., 'auto' — as completions.\nexport type NumberingSystemOption = NumberingSystem | 'auto' | (string & {});\n\n// every NumberingSystem value we support. latn's the default and\n// what the rest of this lib naturally produces\nexport const SUPPORTED_NUMBERING_SYSTEMS: ReadonlySet<string> = new Set([\n 'latn', 'arab', 'deva', 'beng', 'guru', 'gujr', 'orya', 'tamldec',\n 'telu', 'knda', 'mlym', 'fullwide', 'hanidec',\n]);\n\nconst digitMapCache = new Map<string, Record<string, string>>();\n\n// 'auto' resolutions, keyed by canonical locale tag (same key discipline\n// as every other locale-keyed cache in this library — 'ar_EG' and 'ar-EG'\n// fold together instead of costing two entries). Intl.NumberFormat\n// construction isn't free and format() runs in loops (a table of dates, one\n// row per record), so resolve each locale once. Bounded, evicting the\n// oldest insertion, mirroring formatterCache in tokens.ts.\nconst autoNumberingCache = new Map<string, string>();\nconst MAX_AUTO_NUMBERING_CACHE = 500;\n\n// Turns 'auto' into a concrete system by asking Intl what the locale\n// itself defaults to (ar-EG -> arab, bn-BD -> beng, en-US -> latn). When\n// the locale's native system isn't one this library can transliterate\n// (thai, laoo, mymr, ... — anything outside SUPPORTED_NUMBERING_SYSTEMS),\n// fall back to 'latn' rather than throwing: 'auto' means \"use whatever\n// this locale naturally uses, if you can\", not \"throw on locales with\n// numerals this library doesn't cover\". A malformed locale tag DOES\n// throw — typed InvalidLocaleError, matching getFormatter() in tokens.ts —\n// since asking to derive from a locale that doesn't exist is a caller bug,\n// not a data condition to paper over.\nfunction resolveAutoNumberingSystem(locale: string): string {\n const key = canonicalCacheKey(locale);\n const cached = autoNumberingCache.get(key);\n if (cached !== undefined) return cached;\n let resolved: string;\n try {\n resolved = new Intl.NumberFormat(normalizeLocaleTag(locale)).resolvedOptions().numberingSystem;\n } catch (err) {\n throw new InvalidLocaleError({ actual: locale, reason: (err as Error).message });\n }\n const system = SUPPORTED_NUMBERING_SYSTEMS.has(resolved) ? resolved : 'latn';\n if (autoNumberingCache.size >= MAX_AUTO_NUMBERING_CACHE) {\n // not real LRU, just evicts oldest insertion — fine for this key space\n const oldestKey = autoNumberingCache.keys().next().value;\n if (oldestKey !== undefined) autoNumberingCache.delete(oldestKey);\n }\n autoNumberingCache.set(key, system);\n return system;\n}\n\n// Shared front door for both option names. undefined stays 'latn' — the\n// default is unchanged; 'auto' is an opt-in, not a new default. Anything\n// else passes through untouched for convertDigits/convertDigitsToAscii\n// to validate against the supported set (they throw on unknown names).\nfunction resolveRequestedSystem(requested: string | undefined, locale: string | undefined): string {\n if (requested === undefined) return 'latn';\n if (requested === 'auto') return resolveAutoNumberingSystem(locale ?? DEFAULT_LOCALE);\n return requested;\n}\n\n// builds a digit-transliteration map per numbering system. renders 0-9\n// through Intl.NumberFormat in the target system, then builds the lookup\n// table from that. caching it since spinning up a formatter isn't free\n// and we reuse the same map for every digit we convert\nfunction getDigitMap(system: string): Record<string, string> {\n let map = digitMapCache.get(system);\n if (map) return map;\n /* c8 ignore start @preserve -- this branch is dead by construction, not\n just untested: both callers (convertDigits, convertDigitsToAscii)\n already bail out early on system === 'latn' before ever calling\n getDigitMap, so it never actually gets invoked with 'latn'. keeping\n it anyway as a defensive fallback rather than betting that stays\n true forever */\n if (system === 'latn') {\n map = {};\n for (let i = 0; i < 10; i++) map[String(i)] = String(i);\n } else {\n /* c8 ignore stop @preserve */\n const fmt = new Intl.NumberFormat('en-US-u-nu-' + system, { useGrouping: false });\n map = {};\n for (let i = 0; i < 10; i++) {\n map[String(i)] = fmt.format(i);\n }\n }\n digitMapCache.set(system, map);\n return map;\n}\n\n// swaps every ASCII digit in `s` for its equivalent in the target\n// numbering system. anything that's not a digit passes through untouched\nexport function convertDigits(s: string, system: string): string {\n if (system === 'latn') return s;\n if (!SUPPORTED_NUMBERING_SYSTEMS.has(system)) {\n throw new InvalidLocaleError({ actual: system, reason: `numbering system \"${system}\" is not supported. Supported: ${[...SUPPORTED_NUMBERING_SYSTEMS].join(', ')}.` });\n }\n const map = getDigitMap(system);\n let result = '';\n for (const ch of s) {\n if (ch >= '0' && ch <= '9') {\n // map[ch] is always populated for 0-9 — getDigitMap builds all ten\n // keys for every system we support, and ch is already range-checked\n // above. the ?? ch is really just there to satisfy TS about Record's\n // implicit undefined, not because this path is actually reachable\n /* c8 ignore next */\n result += map[ch] ?? ch;\n } else {\n result += ch;\n }\n }\n return result;\n}\n\n// inverse of convertDigits — takes non-ASCII digits back to ASCII. used\n// by parse() when someone passes an explicit numberingSystem option.\n// throws on anything unsupported\nexport function convertDigitsToAscii(s: string, system: string): string {\n // same dead-by-construction thing as the 'latn' guard above —\n // applyParseNumbering (the only caller) already returns early on\n // 'latn' before this ever gets called, so system's never actually\n // 'latn' here in practice. leaving the guard anyway in case someone\n // calls this directly someday without going through that guard\n /* c8 ignore next */\n if (system === 'latn') return s;\n if (!SUPPORTED_NUMBERING_SYSTEMS.has(system)) {\n throw new InvalidLocaleError({ actual: system, reason: `numbering system \"${system}\" is not supported.` });\n }\n const map = getDigitMap(system);\n // just flip the map around\n const reverse: Record<string, string> = {};\n for (const k of Object.keys(map)) reverse[map[k]!] = k;\n let result = '';\n for (const ch of s) {\n result += reverse[ch] ?? ch;\n }\n return result;\n}\n\n// FormatOptions plus a numberingSystem field. pass { numberingSystem: 'arab' }\n// to format() to get Arabic-Indic digits out, or { numberingSystem: 'auto' }\n// to use whichever numeral system the call's locale itself defaults to\n// (ar-EG -> arab, bn-BD -> beng, en-US -> latn), resolved via\n// Intl.NumberFormat(locale).resolvedOptions().numberingSystem with a\n// 'latn' fallback for locales whose native system isn't supported here.\n// Unset still means 'latn' — 'auto' is an opt-in, not a default change.\nexport interface NumberingFormatOptions extends FormatOptions {\n numberingSystem?: NumberingSystemOption;\n}\n\n// same idea but for the parse side. called parseNumberingSystem instead of\n// just numberingSystem so someone mixing format() and parse() options in\n// one config object can set both independently — the two directions\n// aren't always symmetric (you might want native digits out without\n// wanting to accept them back in, or vice versa). 'auto' works here too,\n// resolving from the parse call's own locale the same way the format\n// side does.\nexport interface NumberingParseOptions extends FormatOptions {\n parseNumberingSystem?: NumberingSystemOption;\n}\n\n// format-path helper: takes format()'s ASCII output and converts digits\n// if numberingSystem was asked for. lives here so format.ts doesn't need\n// to know anything about numbering systems\nexport function applyNumbering(s: string, options: NumberingFormatOptions): string {\n const system = resolveRequestedSystem(options.numberingSystem, options.locale);\n if (system === 'latn') return s;\n return convertDigits(s, system);\n}\n\n// parse-path helper: converts input digits to ASCII before matching, if\n// parseNumberingSystem got set. kept as a separate option name from the\n// format side so callers can be explicit about which direction they\n// actually want transliterated\nexport function applyParseNumbering(s: string, options: { parseNumberingSystem?: string; locale?: string }): string {\n // both call sites for this (both in parse.ts) already guard with\n // `if (options.parseNumberingSystem)` before calling, so an undefined\n // parseNumberingSystem never actually reaches here from parse() itself —\n // the undefined handling inside resolveRequestedSystem is dead by\n // construction from that direction. leaving it in as a safety net\n // rather than betting every future caller replicates the same guard\n // (this is a public export; direct calls don't come pre-guarded).\n const system = resolveRequestedSystem(options.parseNumberingSystem, options.locale);\n if (system === 'latn') return s;\n return convertDigitsToAscii(s, system);\n}"],"mappings":"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","names":[]}
@@ -1,13 +1,11 @@
1
1
  import {
2
2
  applyNumbering,
3
3
  tokenize
4
- } from "./chunk-ZT3UXRAA.js";
4
+ } from "./chunk-DMJWJTFY.js";
5
5
  import {
6
6
  DEFAULT_LOCALE,
7
- TOKENS,
8
- getEffectiveTokens,
9
- onTokenTableChange
10
- } from "./chunk-BQET6RUR.js";
7
+ TOKENS
8
+ } from "./chunk-Z245NHDT.js";
11
9
  import {
12
10
  MAX_FORMAT_LENGTH
13
11
  } from "./chunk-SOPTTXUI.js";
@@ -20,10 +18,6 @@ import {
20
18
  var HANDLER_BY_TOKEN = new Map(TOKENS.map(([tok, fn, field]) => [tok, { fn, field }]));
21
19
  var tokenizeCache = /* @__PURE__ */ new Map();
22
20
  var MAX_TOKENIZE_CACHE_SIZE = 500;
23
- onTokenTableChange(() => {
24
- HANDLER_BY_TOKEN = new Map(getEffectiveTokens().map(([tok, fn, field]) => [tok, { fn, field }]));
25
- tokenizeCache.clear();
26
- });
27
21
  function getPieces(formatStr) {
28
22
  let pieces = tokenizeCache.get(formatStr);
29
23
  if (pieces) return pieces;
@@ -195,4 +189,4 @@ export {
195
189
  _getPieces,
196
190
  _handlerFor
197
191
  };
198
- //# sourceMappingURL=chunk-CQJYB7E7.js.map
192
+ //# sourceMappingURL=chunk-HGRT37LT.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/format.ts"],"sourcesContent":["/*\n * Copyright 2026 DirazCoder\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { TOKENS, DEFAULT_LOCALE, type TemporalLike, type FormatOptions } from './tokens.js';\nimport { tokenize, type Piece } from './tokenize.js';\nimport { MAX_FORMAT_LENGTH } from './constants.js';\nimport { applyNumbering, type NumberingFormatOptions } from './numbering.js';\nimport { FormatSyntaxError, UnknownTokenError } from './errors.js';\n\nconst HANDLER_BY_TOKEN = new Map(TOKENS.map(([tok, fn, field]) => [tok, { fn, field }]));\n\n// Pre-tokenized format strings, keyed by (formatStr) — locale doesn't\n// change the tokenization step, only the per-token rendering, so the\n// piece list is shared across locales. Same eviction shape as the\n// other caches in this library.\nconst tokenizeCache = new Map<string, Piece[]>();\nconst MAX_TOKENIZE_CACHE_SIZE = 500;\n\nfunction getPieces(formatStr: string): Piece[] {\n let pieces = tokenizeCache.get(formatStr);\n if (pieces) return pieces;\n if (tokenizeCache.size >= MAX_TOKENIZE_CACHE_SIZE) {\n const oldestKey = tokenizeCache.keys().next().value;\n if (oldestKey !== undefined) tokenizeCache.delete(oldestKey);\n }\n pieces = tokenize(formatStr);\n // Deep-freeze before caching: the SAME array instance is handed out\n // by compileFormat().pieces and _getPieces() (documented as readonly\n // inspection surface). Read-only at the type level alone didn't stop\n // runtime mutation — a caller writing pieces[0].value used to poison\n // the shared cache and corrupt every subsequent format() for that\n // string process-wide. Frozen objects make the mutation attempt\n // throw (ESM is strict mode) instead of silently corrupting state.\n for (const piece of pieces) Object.freeze(piece);\n Object.freeze(pieces);\n tokenizeCache.set(formatStr, pieces);\n return pieces;\n}\n\n/**\n * Format a Temporal.PlainDate, PlainTime, PlainDateTime, or ZonedDateTime\n * using a date-fns-style token string.\n *\n * @example\n * format(Temporal.Now.plainDateISO(), 'yyyy-MM-dd') // \"2026-08-04\"\n * format(zdt, \"MMM d, yyyy 'at' h:mm a\") // \"Aug 4, 2026 at 3:45 PM\"\n * format(zdt, 'MMMM d, yyyy', { locale: 'fr-FR' }) // \"août 4, 2026\"\n *\n * Throws on a token the input type doesn't support (e.g. 'HH' on a PlainDate).\n */\nexport function format(temporal: TemporalLike, formatStr: string, options: NumberingFormatOptions = {}): string {\n if (formatStr.length > MAX_FORMAT_LENGTH) {\n throw new FormatSyntaxError({\n format: formatStr,\n message:\n `temporal-fmt: format string exceeds maximum length of ${MAX_FORMAT_LENGTH} characters ` +\n `(got ${formatStr.length}).`,\n });\n }\n // Null/undefined input used to leak a raw V8 TypeError from the\n // first field read (\"Cannot read properties of null (reading 'year')\")\n // — every other entry point validates its input with a typed error,\n // and null/undefined is by far the most common wrong input. A null\n // `options` object hits the same class (the default parameter only\n // covers undefined).\n if (temporal === null || temporal === undefined) {\n throw new FormatSyntaxError({\n input: String(temporal),\n reason: `format() expected a Temporal.PlainDate / PlainTime / PlainDateTime / ZonedDateTime (or a TemporalLike field bag), got ${String(temporal)}`,\n });\n }\n const opts = options ?? {};\n\n const locale = opts.locale ?? DEFAULT_LOCALE;\n const pieces = getPieces(formatStr);\n let result = '';\n\n for (const piece of pieces) {\n if (piece.kind === 'literal') {\n result += piece.value;\n continue;\n }\n\n const handler = HANDLER_BY_TOKEN.get(piece.value);\n /* c8 ignore start @preserve -- unreachable: tokenize() only ever emits\n tokens present in TOKENS, and HANDLER_BY_TOKEN is built from that\n same list, so every token piece.value can hold already has an entry\n here. Same defensive-guard category as the twin checks in\n analyze.ts/formatDuration.ts. */\n if (!handler) {\n throw new UnknownTokenError({ token: piece.value, format: formatStr, message: `temporal-fmt: unknown token \"${piece.value}\"` });\n }\n /* c8 ignore stop @preserve */\n\n if (temporal[handler.field] === undefined) {\n throw new FormatSyntaxError({\n format: formatStr,\n token: piece.value,\n message:\n `temporal-fmt: token \"${piece.value}\" requires \"${handler.field}\", ` +\n `which this Temporal object doesn't have. ` +\n `(e.g. PlainDate has no time fields, PlainTime has no date fields)`,\n });\n }\n\n result += handler.fn(temporal, locale);\n }\n\n // Numeral transliteration happens last and only on request — every\n // upstream token handler still emits plain ASCII digits, so this is\n // the single place output digits can diverge from that default.\n return applyNumbering(result, opts);\n}\n\n// Shape mirrors Intl.DateTimeFormat.formatToParts: each entry is either\n// a literal (carrying no token info) or a token piece (carrying the\n// token string and the formatted value). Letting callers iterate parts\n// means they can build custom output — strip a token, swap a separator,\n// render each token to its own DOM node — without re-implementing the\n// tokenizer or the field-check logic.\nexport interface FormattedPart {\n type: 'literal' | 'token';\n value: string;\n // Present when `type === 'token'`. Carries the token string (e.g. \"yyyy\")\n // so a caller can look up its metadata via tokenInfo() from analyze.ts.\n token?: string;\n}\n\nexport function formatToParts(temporal: TemporalLike, formatStr: string, options: NumberingFormatOptions = {}): FormattedPart[] {\n if (formatStr.length > MAX_FORMAT_LENGTH) {\n throw new FormatSyntaxError({\n format: formatStr,\n message:\n `temporal-fmt: format string exceeds maximum length of ${MAX_FORMAT_LENGTH} characters ` +\n `(got ${formatStr.length}).`,\n });\n }\n const locale = options.locale ?? DEFAULT_LOCALE;\n const pieces = getPieces(formatStr);\n const result: FormattedPart[] = [];\n for (const piece of pieces) {\n if (piece.kind === 'literal') {\n // Collapse adjacent literals so formatToParts stays consistent with\n // how format() walks pieces — a multi-char literal \"at \" is one\n // entry, not one per character. Same merge logic as appendLiteral\n // in tokenize.ts.\n const last = result[result.length - 1];\n /* c8 ignore start @preserve -- unreachable through tokenize()'s own\n output: appendLiteral() in tokenize.ts already merges every\n adjacent literal character before pieces ever reaches here, so\n two 'literal' pieces never sit back-to-back in the array this\n loop walks. Kept as defense-in-depth in case that invariant ever\n changes upstream. */\n if (last && last.type === 'literal') {\n last.value += piece.value;\n } else {\n /* c8 ignore stop @preserve */\n result.push({ type: 'literal', value: piece.value });\n }\n continue;\n }\n const handler = HANDLER_BY_TOKEN.get(piece.value);\n /* c8 ignore start @preserve -- unreachable, see the note in format() above */\n if (!handler) {\n throw new UnknownTokenError({ token: piece.value, format: formatStr, message: `temporal-fmt: unknown token \"${piece.value}\"` });\n }\n /* c8 ignore stop @preserve */\n if (temporal[handler.field] === undefined) {\n throw new FormatSyntaxError({\n format: formatStr,\n token: piece.value,\n message:\n `temporal-fmt: token \"${piece.value}\" requires \"${handler.field}\", ` +\n `which this Temporal object doesn't have. ` +\n `(e.g. PlainDate has no time fields, PlainTime has no date fields)`,\n });\n }\n // Numeral transliteration applies per-token-part here, rather than\n // once at the end like format() does, so a caller styling individual\n // parts (e.g. one <span> per token) still gets correctly-transliterated\n // digits in each part instead of plain ASCII.\n result.push({ type: 'token', value: applyNumbering(handler.fn(temporal, locale), options), token: piece.value });\n }\n return result;\n}\n\n// Pre-compiles a format string into an object whose format()/formatToParts()\n// methods skip the tokenization step on every call. The tokenizeCache in\n// this module means a plain format(temporal, fmt) call already pays only\n// a Map lookup for tokenization after the first call, so compileFormat()\n// is mostly a typing/ergonomics affordance — useful for callers who want\n// to hold onto a compiled form explicitly (e.g. to inspect the pieces\n// via the .pieces property, or to pass the compiled object around\n// instead of the string).\nexport interface CompiledFormat {\n format(temporal: TemporalLike, options?: NumberingFormatOptions): string;\n formatToParts(temporal: TemporalLike, options?: NumberingFormatOptions): FormattedPart[];\n readonly pieces: ReadonlyArray<Piece>;\n readonly formatStr: string;\n}\n\nexport function compileFormat(formatStr: string): CompiledFormat {\n if (formatStr.length > MAX_FORMAT_LENGTH) {\n throw new FormatSyntaxError({\n format: formatStr,\n message:\n `temporal-fmt: format string exceeds maximum length of ${MAX_FORMAT_LENGTH} characters ` +\n `(got ${formatStr.length}).`,\n });\n }\n // Pre-tokenize once. Validation (unknown tokens, unterminated quotes)\n // happens here, not lazily on first format() call — surfaces a bad\n // format string at compile time rather than at first use, which is\n // the point of compiling up front.\n const pieces = getPieces(formatStr);\n return {\n formatStr,\n pieces,\n format(temporal: TemporalLike, options: NumberingFormatOptions = {}) {\n const locale = options.locale ?? DEFAULT_LOCALE;\n let result = '';\n for (const piece of pieces) {\n if (piece.kind === 'literal') {\n result += piece.value;\n continue;\n }\n const handler = HANDLER_BY_TOKEN.get(piece.value);\n /* c8 ignore next -- unreachable, see the note in format() above */\n if (!handler) throw new UnknownTokenError({ token: piece.value, format: formatStr, message: `temporal-fmt: unknown token \"${piece.value}\"` });\n if (temporal[handler.field] === undefined) {\n throw new FormatSyntaxError({\n format: formatStr,\n token: piece.value,\n message:\n `temporal-fmt: token \"${piece.value}\" requires \"${handler.field}\", ` +\n `which this Temporal object doesn't have. ` +\n `(e.g. PlainDate has no time fields, PlainTime has no date fields)`,\n });\n }\n result += handler.fn(temporal, locale);\n }\n return applyNumbering(result, options);\n },\n formatToParts(temporal: TemporalLike, options: NumberingFormatOptions = {}) {\n const locale = options.locale ?? DEFAULT_LOCALE;\n const out: FormattedPart[] = [];\n for (const piece of pieces) {\n if (piece.kind === 'literal') {\n const last = out[out.length - 1];\n /* c8 ignore next -- unreachable, see the note in formatToParts() above */\n if (last && last.type === 'literal') last.value += piece.value;\n else out.push({ type: 'literal', value: piece.value });\n continue;\n }\n const handler = HANDLER_BY_TOKEN.get(piece.value);\n /* c8 ignore next -- unreachable, see the note in format() above */\n if (!handler) throw new UnknownTokenError({ token: piece.value, format: formatStr, message: `temporal-fmt: unknown token \"${piece.value}\"` });\n if (temporal[handler.field] === undefined) {\n throw new FormatSyntaxError({\n format: formatStr,\n token: piece.value,\n message:\n `temporal-fmt: token \"${piece.value}\" requires \"${handler.field}\", ` +\n `which this Temporal object doesn't have. ` +\n `(e.g. PlainDate has no time fields, PlainTime has no date fields)`,\n });\n }\n out.push({ type: 'token', value: applyNumbering(handler.fn(temporal, locale), options), token: piece.value });\n }\n return out;\n },\n };\n}\n\n// Exported so analyze.ts can reuse the same tokenization cache rather\n// than re-tokenizing when a caller asks for both a format() and an\n// analyzeFormat() on the same string.\nexport function _getPieces(formatStr: string): Piece[] {\n return getPieces(formatStr);\n}\n\n// Exported for analyze.ts — same reason as above. The handler map is\n// the source of truth for \"what field does this token need\" — analyze\n// consumes it to compute requiredFields and compatibleTypes.\nexport function _handlerFor(token: string): { fn: (t: TemporalLike, locale: string) => string; field: keyof TemporalLike } | undefined {\n return HANDLER_BY_TOKEN.get(token);\n}"],"mappings":"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","names":[]}
@@ -1,153 +1,16 @@
1
1
  import {
2
2
  add
3
3
  } from "./chunk-SC6ENLEB.js";
4
+ import {
5
+ compare
6
+ } from "./chunk-UGYSEYUQ.js";
4
7
  import {
5
8
  asDateFieldView
6
9
  } from "./chunk-KEPIXQK7.js";
7
10
  import {
8
- dayOfWeekFromCivil,
9
11
  daysFromCivil
10
12
  } from "./chunk-BLZDBPSV.js";
11
13
 
12
- // src/comparison.ts
13
- function toComparableMs(view) {
14
- const days = daysFromCivil(view.year, view.month, view.day);
15
- const MS_PER_HOUR = 36e5;
16
- const MS_PER_MINUTE = 6e4;
17
- const MS_PER_SECOND = 1e3;
18
- return days * 864e5 + (view.hour ?? 0) * MS_PER_HOUR + (view.minute ?? 0) * MS_PER_MINUTE + (view.second ?? 0) * MS_PER_SECOND + (view.millisecond ?? 0);
19
- }
20
- function asDateTimeFieldView(value) {
21
- const v = asDateFieldView(value);
22
- return v;
23
- }
24
- function compare(a, b) {
25
- const av = asDateTimeFieldView(a);
26
- const bv = asDateTimeFieldView(b);
27
- const am = toComparableMs(av);
28
- const bm = toComparableMs(bv);
29
- if (am < bm) return -1;
30
- if (am > bm) return 1;
31
- return 0;
32
- }
33
- function isEqual(a, b) {
34
- return compare(a, b) === 0;
35
- }
36
- function isBefore(a, b) {
37
- return compare(a, b) < 0;
38
- }
39
- function isAfter(a, b) {
40
- return compare(a, b) > 0;
41
- }
42
- function min(values) {
43
- if (!Array.isArray(values) || values.length === 0) {
44
- throw new Error("temporal-fmt: min() requires a non-empty array of values.");
45
- }
46
- let best = values[0];
47
- for (let i = 1; i < values.length; i++) {
48
- if (compare(values[i], best) < 0) best = values[i];
49
- }
50
- return best;
51
- }
52
- function max(values) {
53
- if (!Array.isArray(values) || values.length === 0) {
54
- throw new Error("temporal-fmt: max() requires a non-empty array of values.");
55
- }
56
- let best = values[0];
57
- for (let i = 1; i < values.length; i++) {
58
- if (compare(values[i], best) > 0) best = values[i];
59
- }
60
- return best;
61
- }
62
- function clamp(value, lo, hi) {
63
- if (compare(value, lo) < 0) return lo;
64
- if (compare(value, hi) > 0) return hi;
65
- return value;
66
- }
67
- function isBetween(value, lo, hi) {
68
- return compare(value, lo) >= 0 && compare(value, hi) <= 0;
69
- }
70
- function readYearMonthDay(value) {
71
- const v = asDateFieldView(value);
72
- return { year: v.year, month: v.month, day: v.day };
73
- }
74
- function todayYearMonthDay() {
75
- const now = /* @__PURE__ */ new Date();
76
- return { year: now.getFullYear(), month: now.getMonth() + 1, day: now.getDate() };
77
- }
78
- function dateEqualYearMonthDay(a, b) {
79
- return a.year === b.year && a.month === b.month && a.day === b.day;
80
- }
81
- function isSameDay(value, reference) {
82
- return dateEqualYearMonthDay(readYearMonthDay(value), readYearMonthDay(reference));
83
- }
84
- function isToday(value) {
85
- return dateEqualYearMonthDay(readYearMonthDay(value), todayYearMonthDay());
86
- }
87
- function tomorrowYearMonthDay() {
88
- const d = /* @__PURE__ */ new Date();
89
- d.setDate(d.getDate() + 1);
90
- return { year: d.getFullYear(), month: d.getMonth() + 1, day: d.getDate() };
91
- }
92
- function yesterdayYearMonthDay() {
93
- const d = /* @__PURE__ */ new Date();
94
- d.setDate(d.getDate() - 1);
95
- return { year: d.getFullYear(), month: d.getMonth() + 1, day: d.getDate() };
96
- }
97
- function isTomorrow(value) {
98
- return dateEqualYearMonthDay(readYearMonthDay(value), tomorrowYearMonthDay());
99
- }
100
- function isYesterday(value) {
101
- return dateEqualYearMonthDay(readYearMonthDay(value), yesterdayYearMonthDay());
102
- }
103
- function sameWeek(yearA, monthA, dayA, yearB, monthB, dayB) {
104
- const daysSince = (y, m, d) => {
105
- return daysFromCivil(y, m, d);
106
- };
107
- const a = daysSince(yearA, monthA, dayA);
108
- const b = daysSince(yearB, monthB, dayB);
109
- if (Math.abs(a - b) >= 7) return false;
110
- const weekdayOf2 = (y, m, d) => {
111
- return dayOfWeekFromCivil(y, m, d);
112
- };
113
- const wa = a - (weekdayOf2(yearA, monthA, dayA) - 1);
114
- const wb = b - (weekdayOf2(yearB, monthB, dayB) - 1);
115
- return wa === wb;
116
- }
117
- function isSameWeek(value, reference) {
118
- const a = readYearMonthDay(value);
119
- const b = readYearMonthDay(reference);
120
- return sameWeek(a.year, a.month, a.day, b.year, b.month, b.day);
121
- }
122
- function isSameMonth(value, reference) {
123
- const a = readYearMonthDay(value);
124
- const b = readYearMonthDay(reference);
125
- return a.year === b.year && a.month === b.month;
126
- }
127
- function isSameQuarter(value, reference) {
128
- const a = readYearMonthDay(value);
129
- const b = readYearMonthDay(reference);
130
- return a.year === b.year && Math.ceil(a.month / 3) === Math.ceil(b.month / 3);
131
- }
132
- function isSameYear(value, reference) {
133
- return readYearMonthDay(value).year === readYearMonthDay(reference).year;
134
- }
135
- function isWeekend(value) {
136
- if (typeof value !== "object" || value === null) {
137
- throw new Error(`temporal-fmt: isWeekend() expected a Temporal value, got ${String(value)}.`);
138
- }
139
- const v = value;
140
- if (typeof v.dayOfWeek !== "number") {
141
- throw new Error(
142
- `temporal-fmt: isWeekend() needs a value with a dayOfWeek field (PlainDate / PlainDateTime / ZonedDateTime).`
143
- );
144
- }
145
- return v.dayOfWeek === 6 || v.dayOfWeek === 7;
146
- }
147
- function isWeekday(value) {
148
- return !isWeekend(value);
149
- }
150
-
151
14
  // src/recurrence.ts
152
15
  function daysInMonthOf(year, month) {
153
16
  const LENGTHS = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
@@ -535,24 +398,6 @@ function formatRRule(rule) {
535
398
  }
536
399
 
537
400
  export {
538
- compare,
539
- isEqual,
540
- isBefore,
541
- isAfter,
542
- min,
543
- max,
544
- clamp,
545
- isBetween,
546
- isSameDay,
547
- isToday,
548
- isTomorrow,
549
- isYesterday,
550
- isSameWeek,
551
- isSameMonth,
552
- isSameQuarter,
553
- isSameYear,
554
- isWeekend,
555
- isWeekday,
556
401
  recurrence,
557
402
  take,
558
403
  skip,
@@ -560,4 +405,4 @@ export {
560
405
  parseRRule,
561
406
  formatRRule
562
407
  };
563
- //# sourceMappingURL=chunk-3FMUGYE2.js.map
408
+ //# sourceMappingURL=chunk-KV6GMX4D.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/recurrence.ts"],"sourcesContent":["/*\n * Copyright 2026 DirazCoder\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// Recurrence engine. Deterministic RRULE-like\n// recurrence without pulling in a runtime dependency. Supports\n// secondly/minutely/hourly/daily/weekly/monthly/yearly frequencies,\n// interval, count, until, weekdays (incl. ordinal BYDAY like 2MO/-1FR\n// for monthly/yearly), monthDays (incl. negative end-of-month values),\n// exclusions, inclusions.\n//\n// RRULE interop: parseRRule() / formatRRule() convert to/from the\n// standard iCalendar RRULE string format (RFC 5545). Implemented\n// without depending on a RRULE library — the grammar is small enough\n// to handle inline. Not enforced (documented limitation, same as\n// before): BYSETPOS, BYHOUR, BYMINUTE, BYSECOND, WKST.\n\nimport { add } from './arithmetic.js';\nimport { compare } from './comparison.js';\nimport { daysFromCivil } from './isoWeek.js';\nimport { asDateFieldView } from './calendarUtils.js';\n\nexport type RecurrenceFrequency = 'secondly' | 'minutely' | 'hourly' | 'daily' | 'weekly' | 'monthly' | 'yearly';\n\nexport interface WeekdayOrdinal {\n // 2 = \"2nd such weekday of the month\", -1 = \"last such weekday\".\n // Only meaningful with monthly/yearly frequency (RFC 5545 3.3.10:\n // the numeric prefix is invalid with WEEKLY, where it's ignored).\n ordinal: number;\n weekday: number; // ISO 1=Mon..7=Sun\n}\n\nexport interface RecurrenceRule {\n frequency: RecurrenceFrequency;\n interval: number; // default 1 — omitted/null now means 1 (used to mean NaN)\n count?: number; // max occurrences\n until?: unknown; // Temporal value (or RRULE UNTIL date form) — recurrence ends after this\n // For weekly: ISO weekdays to include (1=Mon..7=Sun). Empty = all.\n byWeekday?: number[];\n // Ordinal BYDAY parts (2MO, -1FR) parsed from RRULE strings — the nth\n // weekday of each month/year. Matched in addition to plain byWeekday.\n byWeekdayOrdinal?: WeekdayOrdinal[];\n // For monthly/yearly: days of month to include (1-31, or negative\n // for end-of-month: -1 = last day).\n byMonthDay?: number[];\n // For yearly: months to include (1-12).\n byMonth?: number[];\n // For weekly: which week of the year (1-53).\n byWeek?: number[];\n // Exclusions: dates to skip even if they match the rule.\n exDates?: unknown[];\n // Inclusions: dates to add even if they don't match the rule.\n rDates?: unknown[];\n}\n\nexport interface RecurrenceIterator {\n next(): { value: unknown; done: boolean } | { value: undefined; done: true };\n previous(): { value: unknown; done: boolean } | { value: undefined; done: true };\n}\n\nfunction daysInMonthOf(year: number, month: number): number {\n const LENGTHS = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];\n const leap = (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;\n return month === 2 && leap ? 29 : LENGTHS[month - 1]!;\n}\n\n// Day of week (ISO, 1=Mon..7=Sun) for a proleptic Gregorian y/m/d in\n// O(1): 1970-01-01 was a Thursday (dow 4), so dow = ((days + 3) mod 7) + 1.\nfunction weekdayOf(year: number, month: number, day: number): number {\n const days = daysFromCivil(year, month, day);\n return (((days + 3) % 7) + 7) % 7 + 1;\n}\n\n// Day-of-month of the `ordinal`-th `weekday` in the given month\n// (ordinal < 0 counts from the end, -1 = last). Returns null when no\n// such day exists in this month (e.g. 5th Monday of a 4-Monday month).\nfunction nthWeekdayDay(year: number, month: number, weekday: number, ordinal: number): number | null {\n const last = daysInMonthOf(year, month);\n if (ordinal > 0) {\n const firstDow = weekdayOf(year, month, 1);\n const first = 1 + ((weekday - firstDow + 7) % 7);\n const day = first + (ordinal - 1) * 7;\n return day <= last ? day : null;\n }\n const lastDow = weekdayOf(year, month, last);\n const lastOccurrence = last - ((lastDow - weekday + 7) % 7);\n const day = lastOccurrence + (ordinal + 1) * 7;\n return day >= 1 ? day : null;\n}\n\n// Creates a recurrence iterator starting from `start`. The first call\n// to next() returns `start` itself (if it matches the rule); subsequent\n// calls return the next matching occurrence.\nexport function recurrence(start: unknown, rule: RecurrenceRule): RecurrenceIterator {\n if (rule.count !== undefined && (!Number.isSafeInteger(rule.count) || rule.count < 1)) {\n throw new RangeError(`temporal-fmt: recurrence rule count must be a positive safe integer (got ${String(rule.count)}).`);\n }\n // The interface documents interval as \"default 1\" — normalize it here\n // so a rule object omitting it (or passing undefined) behaves as 1\n // instead of poisoning every candidate with NaN arithmetic.\n const interval = rule.interval !== undefined && rule.interval !== null\n ? rule.interval\n : 1;\n if (!Number.isSafeInteger(interval) || interval < 1) {\n throw new RangeError(`temporal-fmt: recurrence rule interval must be a positive safe integer (got ${String(rule.interval)}).`);\n }\n // Programmatic rules skip parseRRule's validation, so re-check the\n // BY* lists here — garbage used to fall through to `?? 0` fallbacks\n // in matches() and quietly match nothing (a rule like byMonthDay:[0]\n // produced an empty sequence with no hint why). Real rules from\n // parseRRule always pass.\n if (rule.byMonthDay && rule.byMonthDay.some((d) => !Number.isSafeInteger(d) || d === 0 || d < -31 || d > 31)) {\n throw new RangeError('temporal-fmt: recurrence rule byMonthDay entries must be nonzero integers in [-31, 31].');\n }\n if (rule.byWeekday && rule.byWeekday.some((d) => !Number.isSafeInteger(d) || d < 1 || d > 7)) {\n throw new RangeError('temporal-fmt: recurrence rule byWeekday entries must be ISO weekdays 1 (Mon) through 7 (Sun).');\n }\n if (rule.byMonth && rule.byMonth.some((m) => !Number.isSafeInteger(m) || m < 1 || m > 12)) {\n throw new RangeError('temporal-fmt: recurrence rule byMonth entries must be months 1 through 12.');\n }\n if (rule.byWeekdayOrdinal && rule.byWeekdayOrdinal.some((o) =>\n !Number.isSafeInteger(o.ordinal) || o.ordinal === 0 || o.ordinal < -53 || o.ordinal > 53 ||\n !Number.isSafeInteger(o.weekday) || o.weekday < 1 || o.weekday > 7)) {\n throw new RangeError('temporal-fmt: recurrence rule byWeekdayOrdinal entries need a nonzero ordinal in [-53, 53] and an ISO weekday 1-7.');\n }\n // Track past values for previous() — keeps a ring buffer of the last\n // N occurrences so previous() can walk back without recomputing.\n const history: unknown[] = [];\n const MAX_HISTORY = 10_000;\n let atEnd = false;\n let atStart = true;\n\n // Anchor: the ORIGINAL start, as a field view. Monthly/yearly\n // candidates are derived from this (not from the previous occurrence)\n // so the day-of-month never drifts: advancing Jan 31 by months used\n // to go 01-31 → 02-28 → 03-28 → 04-28 …, permanently stuck on day 28\n // after the first February clamp. Deriving each period's candidates\n // from the anchor keeps the original day and skips (rather than\n // clamps) periods that lack it — RFC 5545 semantics.\n const anchor = asDateFieldView(start);\n const originDayCount = daysFromCivil(anchor.year!, anchor.month!, anchor.day!);\n\n // A plain field-bag start without dayOfWeek used to make every\n // byWeekday-dependent rule silently produce nothing: matches()'s\n // `?? 0` never equals a real weekday, and add()'s dayOfWeek\n // recompute only refreshes a field that already exists — so the\n // chain never picks one up (a Temporal start works because its\n // dayOfWeek getter seeds it). Derive it once here so bag starts and\n // Temporal starts behave identically for BYDAY rules.\n let current: unknown = start;\n if (rule.byWeekday && rule.byWeekday.length > 0 && typeof (start as { dayOfWeek?: unknown }).dayOfWeek !== 'number') {\n current = { ...anchor, dayOfWeek: weekdayOf(anchor.year!, anchor.month!, anchor.day!) };\n }\n let count = 0;\n\n function matches(value: unknown): boolean {\n // The `?? 0` / `?? []` fallbacks on the lines flagged below are\n // defensive only and unreachable through the public API: recurrence()\n // rejects starts missing year/month/day, every generated candidate is\n // a full field view (fillNextPeriod sets dayOfWeek; add()'s\n // recomputeDayOfWeek refreshes one seeded at the top), and the\n // byWeekday gate above runs before plainOk is ever evaluated — so\n // dayOfWeek is defined whenever `?? 0` would fire, and year/month/day\n // are always present. The lines they sit on are otherwise live and\n // test-covered; only the fallback arms need the ignore.\n const v = value as { dayOfWeek?: number; year?: number; month?: number; day?: number; hour?: number; minute?: number; second?: number };\n if (rule.byWeekday && rule.byWeekday.length > 0) {\n // `?? 0` is defensive only since the dayOfWeek seeding above: every\n // value a byWeekday rule tests (seeded start, generated candidates)\n // carries a number.\n /* c8 ignore next @preserve */\n if (!rule.byWeekday.includes(v.dayOfWeek ?? 0)) return false;\n }\n if (rule.byWeekdayOrdinal && rule.byWeekdayOrdinal.length > 0\n && (rule.frequency === 'monthly' || rule.frequency === 'yearly')) {\n // Union with the plain-byWeekday check above per RFC: any BYDAY\n // entry matching is enough. (Monthly/yearly candidates are\n // generated from the ordinal itself, so this re-check is about\n // rules that mix plain and ordinal entries.)\n /* c8 ignore next @preserve */\n const plainOk = !!rule.byWeekday && rule.byWeekday.length > 0 && rule.byWeekday.includes(v.dayOfWeek ?? 0);\n /* c8 ignore next @preserve */\n const ordinalOk = (rule.byWeekdayOrdinal ?? []).some((o) =>\n /* c8 ignore next @preserve */\n nthWeekdayDay(v.year ?? 0, v.month ?? 0, o.weekday, o.ordinal) === (v.day ?? 0));\n if (!plainOk && !ordinalOk) return false;\n }\n if (rule.byMonthDay && rule.byMonthDay.length > 0) {\n // Negative values count back from the end of the month\n // (-1 = last day). They used to be accepted by parseRRule but\n // never matched anything — a day-of-month is never negative.\n /* c8 ignore next @preserve */\n const day = v.day ?? 0;\n /* c8 ignore next @preserve */\n const last = daysInMonthOf(v.year ?? 0, v.month ?? 0);\n // `?? []` is defensive only: the enclosing if guards byMonthDay\n // non-empty.\n /* c8 ignore next @preserve */\n const hit = (rule.byMonthDay ?? []).some((d) => (d > 0 ? d === day : d + last + 1 === day));\n if (!hit) return false;\n }\n if (rule.byMonth && rule.byMonth.length > 0) {\n /* c8 ignore next @preserve */\n if (!rule.byMonth.includes(v.month ?? 0)) return false;\n }\n if (rule.exDates && rule.exDates.some((d) => compare(d, value) === 0)) return false;\n return true;\n }\n\n // ---- Monthly/yearly candidate generation ------------------------------\n // Each interval step yields one period (a month, or a year with its\n // byMonth months). The period's candidate days are:\n // - byMonthDay values, if present (negatives resolved to real days);\n // - else ordinal BYDAY days, if present;\n // - else the anchor's day (periods lacking it yield nothing).\n // Candidates carry the anchor's time-of-day and a recomputed\n // dayOfWeek, and are yielded in chronological order.\n let pending: unknown[] = [];\n // Starts at 0: the start's OWN period gets generated first (RFC —\n // FREQ=MONTHLY;BYMONTHDAY=15 from Jan 1 yields Jan 15 before Feb 15),\n // with candidates at or before `start` skipped by nextMatch.\n let periodIndex = 0;\n\n function fillNextPeriod(): void {\n // k starts at 0 (the start's own period) and increments AFTER use,\n // so every period from the anchor's onward is generated exactly once.\n const k = periodIndex;\n periodIndex++;\n let year: number;\n let months: number[];\n if (rule.frequency === 'monthly') {\n const totalMonths = anchor.year! * 12 + anchor.month! - 1 + k * interval;\n year = Math.floor(totalMonths / 12);\n months = [totalMonths - year * 12 + 1];\n } else {\n year = anchor.year! + k * interval;\n months = rule.byMonth && rule.byMonth.length > 0\n ? [...rule.byMonth].sort((a, b) => a - b)\n : [anchor.month!];\n }\n const days: Array<{ month: number; day: number }> = [];\n for (const m of months) {\n const last = daysInMonthOf(year, m);\n if (rule.byMonthDay && rule.byMonthDay.length > 0) {\n for (const d of rule.byMonthDay) {\n const real = d > 0 ? d : last + 1 + d;\n if (real >= 1 && real <= last) days.push({ month: m, day: real });\n }\n } else if (rule.byWeekdayOrdinal && rule.byWeekdayOrdinal.length > 0) {\n for (const o of rule.byWeekdayOrdinal) {\n const real = nthWeekdayDay(year, m, o.weekday, o.ordinal);\n if (real !== null) days.push({ month: m, day: real });\n }\n } else {\n if (anchor.day! <= last) days.push({ month: m, day: anchor.day! });\n }\n }\n days.sort((a, b) => a.month - b.month || a.day - b.day);\n pending = days.map(({ month, day }) => ({\n ...anchor,\n year,\n month,\n day,\n dayOfWeek: weekdayOf(year, month, day),\n }));\n }\n\n // Steps to the next candidate after `value`, by the frequency's own\n // granularity:\n // - monthly/yearly: pop the period queue (refilling as needed);\n // - weekly + BYDAY: one day at a time (stepping whole weeks preserves\n // the weekday, so a start not already on a BYDAY weekday could\n // never match — FREQ=WEEKLY;BYDAY=SA from a Monday used to produce\n // an empty sequence), gated to the interval's week windows;\n // - everything else: one `interval` step from the current value, the\n // same way the original implementation did.\n function nextMatch(value: unknown): { value: unknown; found: boolean } {\n let candidate = value;\n let safetyCounter = 0;\n const daywise = rule.frequency === 'weekly' && !!rule.byWeekday && rule.byWeekday.length > 0;\n const periodwise = rule.frequency === 'monthly' || rule.frequency === 'yearly';\n const unit = rule.frequency === 'secondly' ? 'seconds'\n : rule.frequency === 'minutely' ? 'minutes'\n : rule.frequency === 'hourly' ? 'hours'\n : rule.frequency === 'daily' ? 'days'\n : 'weeks';\n\n while (true) {\n // Count EVERY iteration, including the two `continue` paths below\n // (empty periods, candidates at/before the start): those used to\n // skip the increment, so a rule whose every period is empty — e.g.\n // monthly BYDAY=-6MO, which no month can ever satisfy — spun the\n // loop forever. The cap is a total-work bound per next() call, not\n // a candidates-produced bound.\n safetyCounter++;\n if (safetyCounter > 2000) {\n // Defensive — if the rule is so restrictive no match exists\n // within 2000 steps, give up rather than spin forever. The\n // caller must treat this as \"no more occurrences\", not as a\n // real match — returning the unmatched candidate here used to\n // get handed back to next()'s caller as if it were valid.\n // (1000 previously; doubled because day-granular stepping for\n // weekly BYDAY rules legitimately needs up to 7 steps per\n // occurrence, and monthly skips can chain across short months.)\n atEnd = true;\n return { value: candidate, found: false };\n }\n if (daywise) {\n candidate = add(candidate, 1, 'days');\n // INTERVAL > 1 on a weekly BYDAY rule: only weeks that are a\n // whole multiple of `interval` away from the anchor's week emit\n // occurrences (WKST unsupported — the window anchors on the\n // start's own day).\n if (interval > 1) {\n const v = asDateFieldView(candidate);\n const dayCount = daysFromCivil(v.year!, v.month!, v.day!);\n if (Math.floor((dayCount - originDayCount) / 7) % interval !== 0) continue;\n }\n } else if (periodwise) {\n if (pending.length === 0) {\n fillNextPeriod();\n if (pending.length === 0) continue; // period had no valid days\n }\n candidate = pending.shift()!;\n // The start's own period can contain candidate days at or before\n // the start itself (BYMONTHDAY=15 generating Jan 15 for a Jan 1\n // start, where Jan 1..14 precede it) — those aren't occurrences.\n if (compare(candidate, value) <= 0) continue;\n } else {\n candidate = add(candidate, interval, unit as Parameters<typeof add>[2]);\n }\n if (rule.until && compare(candidate, rule.until) > 0) {\n atEnd = true;\n return { value: candidate, found: false };\n }\n if (matches(candidate)) break;\n }\n return { value: candidate, found: true };\n }\n\n function recordHistory(value: unknown): void {\n history.push(value);\n if (history.length > MAX_HISTORY) history.shift();\n }\n\n return {\n next() {\n if (atEnd) return { value: undefined, done: true };\n if (atStart) {\n atStart = false;\n if (matches(current)) {\n count++;\n if (rule.count !== undefined && count >= rule.count) atEnd = true;\n if (rule.until && compare(current, rule.until) > 0) {\n atEnd = true;\n return { value: undefined, done: true };\n }\n // Include rDates that match the current value.\n recordHistory(current);\n return { value: current, done: false };\n }\n // If start doesn't match, advance to first match.\n const advanced = nextMatch(current);\n if (!advanced.found) return { value: undefined, done: true };\n current = advanced.value;\n count++;\n if (rule.count !== undefined && count >= rule.count) atEnd = true;\n recordHistory(current);\n return { value: current, done: false };\n }\n const advanced = nextMatch(current);\n if (!advanced.found) return { value: undefined, done: true };\n current = advanced.value;\n count++;\n if (rule.count !== undefined && count >= rule.count) atEnd = true;\n recordHistory(current);\n return { value: current, done: false };\n },\n previous() {\n if (history.length === 0) return { value: undefined, done: true };\n const v = history.pop()!;\n return { value: v, done: false };\n },\n };\n}\n\n// Take N occurrences from a recurrence iterator.\nexport function take(iter: RecurrenceIterator, n: number): unknown[] {\n const result: unknown[] = [];\n for (let i = 0; i < n; i++) {\n const r = iter.next();\n if (r.done) break;\n result.push(r.value);\n }\n return result;\n}\n\n// Upper bound on how many occurrences skip() will collect after its\n// skip phase. skip() used to call take(iter, Number.MAX_SAFE_INTEGER),\n// and an iterator over an unbounded rule (no count, no until — e.g.\n// { frequency: 'daily', interval: 1 }) never returns done: next() always\n// has another day. The call then looped forever, pushing into a result\n// array until the process OOM'd. Same cap style as businessCalendar.ts.\nconst MAX_SKIP_COLLECTION = 100_000;\n// Upper bound on skip()'s skip phase too: skip(iter, 1e9) on an\n// unbounded rule used to burn a billion next() calls before the\n// collection cap was ever reached.\nconst MAX_SKIP_STEPS = 1_000_000;\n\n// Skip N occurrences from a recurrence iterator, returning the ones\n// that follow. Throws a RangeError when the iterator is still producing\n// occurrences after MAX_SKIP_COLLECTION — that means the rule is\n// unbounded (no count/until) and \"everything after the skip\" has no\n// finite answer.\nexport function skip(iter: RecurrenceIterator, n: number): unknown[] {\n for (let i = 0; i < n; i++) {\n if (i >= MAX_SKIP_STEPS) {\n throw new RangeError(\n `temporal-fmt: skip() was asked to skip more than ${MAX_SKIP_STEPS} occurrences — that's not a bounded operation; use take() on a count/until-bounded rule instead.`\n );\n }\n const r = iter.next();\n if (r.done) break;\n }\n const result: unknown[] = [];\n while (result.length < MAX_SKIP_COLLECTION) {\n const r = iter.next();\n if (r.done) return result;\n result.push(r.value);\n }\n throw new RangeError(\n `temporal-fmt: skip() collected ${MAX_SKIP_COLLECTION} occurrences and the rule is still ` +\n `producing — add a count or until to the rule, or use take(iter, n) for a bounded read.`\n );\n}\n\n// All occurrences between two dates (inclusive of start, exclusive of end).\nexport function between(start: unknown, rule: RecurrenceRule, rangeStart: unknown, rangeEnd: unknown): unknown[] {\n const iter = recurrence(start, rule);\n const result: unknown[] = [];\n // Traversal cap: a fine-grained rule anchored far before rangeEnd\n // (e.g. daily from year -2000) walks every occurrence since `start`\n // — bounded work instead of an unbounded loop, same style as skip().\n const MAX_TRAVERSAL = 1_000_000;\n let traversed = 0;\n while (true) {\n if (traversed++ > MAX_TRAVERSAL) {\n throw new RangeError(\n `temporal-fmt: between() walked more than ${MAX_TRAVERSAL} occurrences without reaching rangeEnd — ` +\n `pass a start closer to the range, or bound the rule with count/until.`\n );\n }\n const r = iter.next();\n if (r.done) break;\n if (compare(r.value, rangeEnd) >= 0) break;\n if (compare(r.value, rangeStart) >= 0) result.push(r.value);\n }\n return result;\n}\n\n// Parses an RRULE UNTIL value (RFC 5545 date \"20240110\" or UTC\n// date-time \"20240110T153000Z\") into a comparable field bag. Stored raw\n// previously, so iterating a parsed rule threw \"expected a date-carrying\n// Temporal value\" on the first next() — a parsed RRULE was unusable\n// with UNTIL, the single most common termination form.\nfunction parseUntil(value: string): { year: number; month: number; day: number; hour?: number; minute?: number; second?: number } {\n const m = /^(\\d{4})(\\d{2})(\\d{2})(?:T(\\d{2})(\\d{2})(\\d{2})Z?)?$/.exec(value.trim());\n if (!m) {\n throw new RangeError(\n `temporal-fmt: RRULE UNTIL must be a date (\"20240110\") or UTC date-time (\"20240110T153000Z\") — got \"${value}\".`\n );\n }\n const year = Number(m[1]);\n const month = Number(m[2]);\n const day = Number(m[3]);\n if (month < 1 || month > 12 || day < 1 || day > 31) {\n throw new RangeError(`temporal-fmt: RRULE UNTIL carries an out-of-range date \"${value}\".`);\n }\n const hour = m[4] !== undefined ? Number(m[4]) : undefined;\n const minute = m[5] !== undefined ? Number(m[5]) : undefined;\n const second = m[6] !== undefined ? Number(m[6]) : undefined;\n if (hour !== undefined && (hour > 23 || minute! > 59 || second! > 59)) {\n throw new RangeError(`temporal-fmt: RRULE UNTIL carries an out-of-range time \"${value}\".`);\n }\n return { year, month, day, hour, minute, second };\n}\n\n// Parses an RRULE string like \"FREQ=DAILY;INTERVAL=2;COUNT=5\" into a\n// RecurrenceRule. Doesn't support every RRULE feature (BYSETPOS,\n// BYHOUR, BYMINUTE, BYSECOND, WKST are not enforced by the recurrence\n// iterator above), but covers the common cases.\nexport function parseRRule(input: string): RecurrenceRule {\n const parts = input.trim().toUpperCase().replace(/^RRULE:/, '').split(';');\n const rule: RecurrenceRule = { frequency: 'daily', interval: 1 };\n const frequencies: ReadonlySet<string> = new Set([\n 'SECONDLY', 'MINUTELY', 'HOURLY', 'DAILY', 'WEEKLY', 'MONTHLY', 'YEARLY',\n ]);\n for (const part of parts) {\n if (!part) continue;\n const eq = part.indexOf('=');\n if (eq < 0) continue;\n const key = part.slice(0, eq);\n const value = part.slice(eq + 1);\n switch (key) {\n case 'FREQ':\n if (!frequencies.has(value)) {\n throw new RangeError(`temporal-fmt: unsupported RRULE frequency \"${value}\".`);\n }\n rule.frequency = value.toLowerCase() as RecurrenceFrequency;\n break;\n case 'INTERVAL': {\n const interval = Number(value);\n if (!Number.isSafeInteger(interval) || interval < 1) {\n throw new RangeError(`temporal-fmt: RRULE INTERVAL must be a positive safe integer (got \"${value}\").`);\n }\n rule.interval = interval;\n break;\n }\n case 'COUNT': {\n const count = Number(value);\n if (!Number.isSafeInteger(count) || count < 1) {\n throw new RangeError(`temporal-fmt: RRULE COUNT must be a positive safe integer (got \"${value}\").`);\n }\n rule.count = count;\n break;\n }\n case 'UNTIL':\n rule.until = parseUntil(value);\n break;\n case 'BYDAY': {\n const WEEKDAY_MAP: Record<string, number> = { MO: 1, TU: 2, WE: 3, TH: 4, FR: 5, SA: 6, SU: 7 };\n const plain: number[] = [];\n const ordinals: WeekdayOrdinal[] = [];\n for (const d of value.split(',')) {\n // RFC 5545 ordinals run -53..53 — two digits are legal, so the\n // pattern must accept them (it used to cap at one digit, which\n // made valid rules like BYDAY=53MO throw \"invalid weekday\" and\n // pushed out-of-range ordinals past the range check below\n // instead of reaching it).\n const m = d.match(/^([+-]?\\d{1,2})?([A-Z]{2})$/);\n // Unknown tokens used to map to 0 silently — a typo'd BYDAY\n // entry quietly matched nothing instead of failing loudly.\n if (!m || WEEKDAY_MAP[m[2]!] === undefined) {\n throw new RangeError(`temporal-fmt: RRULE BYDAY contains an invalid weekday \"${d}\".`);\n }\n const weekday = WEEKDAY_MAP[m[2]!]!;\n if (m[1] !== undefined) {\n const ordinal = Number(m[1]);\n if (!Number.isSafeInteger(ordinal) || ordinal === 0 || ordinal < -53 || ordinal > 53) {\n throw new RangeError(`temporal-fmt: RRULE BYDAY ordinal out of range in \"${d}\".`);\n }\n ordinals.push({ ordinal, weekday });\n } else {\n plain.push(weekday);\n }\n }\n rule.byWeekday = plain;\n if (ordinals.length > 0) rule.byWeekdayOrdinal = ordinals;\n break;\n }\n case 'BYMONTHDAY': {\n const values = value.split(',').map(Number);\n if (values.some((day) => !Number.isInteger(day) || day === 0 || day < -31 || day > 31)) {\n throw new RangeError(`temporal-fmt: RRULE BYMONTHDAY contains an out-of-range value.`);\n }\n rule.byMonthDay = values;\n break;\n }\n case 'BYMONTH': {\n const values = value.split(',').map(Number);\n if (values.some((month) => !Number.isInteger(month) || month < 1 || month > 12)) {\n throw new RangeError(`temporal-fmt: RRULE BYMONTH contains an out-of-range value.`);\n }\n rule.byMonth = values;\n break;\n }\n }\n }\n return rule;\n}\n\nexport function formatRRule(rule: RecurrenceRule): string {\n const parts: string[] = [`FREQ=${rule.frequency.toUpperCase()}`];\n // Programmatic rules may omit interval (documented default: 1) — emitting\n // \"INTERVAL=undefined\" for them produced output that parseRRule itself\n // rejects, breaking the round-trip. Omit it unless it's an explicit != 1.\n if (rule.interval !== undefined && rule.interval !== 1) parts.push(`INTERVAL=${rule.interval}`);\n if (rule.count !== undefined) parts.push(`COUNT=${rule.count}`);\n if (rule.until !== undefined) parts.push(`UNTIL=${String(rule.until)}`);\n const WEEKDAY_NAMES: Record<number, string> = { 1: 'MO', 2: 'TU', 3: 'WE', 4: 'TH', 5: 'FR', 6: 'SA', 7: 'SU' };\n if (rule.byWeekday && rule.byWeekday.length > 0) {\n parts.push(`BYDAY=${rule.byWeekday.map((d) => WEEKDAY_NAMES[d] ?? String(d)).join(',')}`);\n }\n if (rule.byWeekdayOrdinal && rule.byWeekdayOrdinal.length > 0) {\n const rendered = rule.byWeekdayOrdinal.map((o) => `${o.ordinal}${WEEKDAY_NAMES[o.weekday] ?? String(o.weekday)}`);\n // Merge into the plain BYDAY part when both exist (RRULE carries\n // one combined list).\n if (rule.byWeekday && rule.byWeekday.length > 0) {\n parts[parts.length - 1] = `BYDAY=${[...rule.byWeekday.map((d) => WEEKDAY_NAMES[d] ?? String(d)), ...rendered].join(',')}`;\n } else {\n parts.push(`BYDAY=${rendered.join(',')}`);\n }\n }\n if (rule.byMonthDay && rule.byMonthDay.length > 0) parts.push(`BYMONTHDAY=${rule.byMonthDay.join(',')}`);\n if (rule.byMonth && rule.byMonth.length > 0) parts.push(`BYMONTH=${rule.byMonth.join(',')}`);\n return parts.join(';');\n}\n"],"mappings":"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","names":["day","advanced"]}