temporal-fmt 0.9.70 → 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.
- package/README.md +28 -59
- package/dist/{chunk-6ZIYGBFN.js → chunk-5KP5H7X2.js} +4 -4
- package/dist/{chunk-N4SYRZIP.js → chunk-DMJWJTFY.js} +35 -12
- package/dist/chunk-DMJWJTFY.js.map +1 -0
- package/dist/{chunk-CGI4FL6H.js → chunk-FLMOMOAQ.js} +2 -2
- package/dist/{chunk-UU2XN6N2.js → chunk-HGRT37LT.js} +5 -11
- package/dist/chunk-HGRT37LT.js.map +1 -0
- package/dist/{chunk-3FMUGYE2.js → chunk-KV6GMX4D.js} +4 -159
- package/dist/chunk-KV6GMX4D.js.map +1 -0
- package/dist/{chunk-BZPNSQHI.js → chunk-LHR5PCP4.js} +2 -2
- package/dist/{chunk-OLNU4LON.js → chunk-QRLQ7RR7.js} +2 -1
- package/dist/chunk-QRLQ7RR7.js.map +1 -0
- package/dist/chunk-UGYSEYUQ.js +168 -0
- package/dist/chunk-UGYSEYUQ.js.map +1 -0
- package/dist/{chunk-VXG3VD3D.js → chunk-WEVNKOQA.js} +3 -3
- package/dist/chunk-WP7TBCTT.js +241 -0
- package/dist/chunk-WP7TBCTT.js.map +1 -0
- package/dist/{chunk-BQDOTFGC.js → chunk-Z245NHDT.js} +21 -21
- package/dist/chunk-Z245NHDT.js.map +1 -0
- package/dist/config.d.cts +2 -2
- package/dist/config.d.ts +2 -2
- package/dist/duration.cjs.map +1 -1
- package/dist/duration.js +2 -2
- package/dist/format.cjs +71 -18
- package/dist/format.cjs.map +1 -1
- package/dist/format.js +4 -4
- package/dist/index.cjs +3875 -4707
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +21 -104
- package/dist/index.d.ts +21 -104
- package/dist/index.js +2409 -849
- package/dist/index.js.map +1 -1
- package/dist/interval.cjs +131 -4757
- package/dist/interval.cjs.map +1 -1
- package/dist/interval.js +6 -11
- package/dist/localeRegistry.cjs.map +1 -1
- package/dist/localeRegistry.js +2 -2
- package/dist/localeVocab.d.cts +4 -3
- package/dist/localeVocab.d.ts +4 -3
- package/dist/numbering.d.cts +4 -2
- package/dist/numbering.d.ts +4 -2
- package/dist/parse.cjs +42 -14
- package/dist/parse.cjs.map +1 -1
- package/dist/parse.js +4 -4
- package/dist/recurrence.js +2 -1
- package/dist/relativeTime.cjs.map +1 -1
- package/dist/relativeTime.js +3 -3
- package/dist/tokens.d.cts +0 -4
- package/dist/tokens.d.ts +0 -4
- package/package.json +5 -21
- package/MODS.md +0 -148
- package/dist/chunk-3FMUGYE2.js.map +0 -1
- package/dist/chunk-6WKSAP4Y.js +0 -3041
- package/dist/chunk-6WKSAP4Y.js.map +0 -1
- package/dist/chunk-BQDOTFGC.js.map +0 -1
- package/dist/chunk-N4SYRZIP.js.map +0 -1
- package/dist/chunk-OLNU4LON.js.map +0 -1
- package/dist/chunk-UU2XN6N2.js.map +0 -1
- package/dist/modApi.d.cts +0 -147
- package/dist/modApi.d.ts +0 -147
- package/dist/runtime.d.cts +0 -206
- package/dist/runtime.d.ts +0 -206
- package/scripts/cli.mjs +0 -324
- package/scripts/loadMods.d.mts +0 -37
- package/scripts/loadMods.mjs +0 -801
- package/scripts/managePermissions.mjs +0 -126
- package/scripts/modConfig.mjs +0 -96
- package/scripts/modSandbox.d.mts +0 -13
- package/scripts/modSandbox.mjs +0 -855
- package/scripts/modWire.mjs +0 -241
- package/scripts/modWorker.mjs +0 -574
- package/scripts/semverRange.mjs +0 -157
- package/scripts/versions.json +0 -37
- /package/dist/{chunk-6ZIYGBFN.js.map → chunk-5KP5H7X2.js.map} +0 -0
- /package/dist/{chunk-CGI4FL6H.js.map → chunk-FLMOMOAQ.js.map} +0 -0
- /package/dist/{chunk-BZPNSQHI.js.map → chunk-LHR5PCP4.js.map} +0 -0
- /package/dist/{chunk-VXG3VD3D.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
|
|
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
|
|
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,11 +75,9 @@ 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
|
-
- [
|
|
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
|
-
- [Known limitations](#known-limitations)
|
|
83
81
|
- [Related tools](#related-tools)
|
|
84
82
|
- [Testing](#testing)
|
|
85
83
|
- [Contributing](#contributing)
|
|
@@ -199,6 +197,8 @@ parse('yyyy-Md', '2026-121', { lenient: true }).toString() // '2026-12-01'
|
|
|
199
197
|
|
|
200
198
|
**Heuristic**: if one of the tokens in the ambiguous run is `d` (day), prefer the split where the day value is ≤ 12. The reasoning: someone who glues a run like `"121"` into an `Md` format is more likely to mean Dec 1 (M=12, d=1) than Jan 21 (M=1, d=21) — if they meant Jan 21, they'd more often write it with a separator or padding (`"1/21"`, `"01/21"`). It's not a guarantee, which is exactly why it's opt-in. When the heuristic doesn't narrow it down (both splits have day ≤ 12), or when there's no `d` token in the run at all (e.g. `Hm`), it falls back to the first valid split — deterministic, but arbitrary. Default behavior (lenient unset or `false`) is unchanged either way.
|
|
201
199
|
|
|
200
|
+
Ambiguity is also easy to avoid outright — the same fixes the heuristic's reasoning hints at: zero-pad the tokens (`MM`/`dd`) or put a literal separator between them (`M/d`), and there's only ever one split to find. Unambiguous glued runs parse fine in strict mode: `"85"` against `yyyy-Md` has exactly one valid split (month 8, day 5), so only genuinely ambiguous input throws. One structural case always throws regardless of ambiguity: a glued run in a format string with no `yyyy` at all (`Md`, `dM`, `Hm` on their own) can never produce a date, since `parse()` needs year, month, and day together to build one (the incomplete-date error in [Typed errors](#typed-errors)).
|
|
201
|
+
|
|
202
202
|
### Offset tokens (`X`/`XX`/`XXX`/`x`/`xx`/`xxx`)
|
|
203
203
|
|
|
204
204
|
The six offset tokens only work on `ZonedDateTime`. On a `PlainDate`/`PlainTime`/`PlainDateTime` they throw the same "requires offset, which this Temporal object doesn't have" error every other field-typed token throws when its field is missing.
|
|
@@ -207,6 +207,8 @@ Uppercase (`X`/`XX`/`XXX`) collapses `+00:00` to `Z` for UTC. Lowercase (`x`/`xx
|
|
|
207
207
|
|
|
208
208
|
On parse, an offset token needs a full date and time to anchor the instant — same rule `zzz` enforces. With an offset token and no `zzz`, the resulting `ZonedDateTime`'s `timeZoneId` is the offset string itself (e.g. `"+09:00"`). With **both** `zzz` and an offset token, it's a cross-check: `zzz` wins for the result's `timeZoneId` (the IANA name is the meaningful label), and the offset token's value must match that zone's actual offset at the parsed instant. Disagreement throws rather than silently picking one — `parse('yyyy-MM-dd HH:mm zzz XXX', '2026-08-04 15:45 America/New_York +09:00')` throws, because August in New York is `-04:00`, not `+09:00`.
|
|
209
209
|
|
|
210
|
+
Sub-minute historical offsets: the tokens read `ZonedDateTime`'s `offset` field, which is `±HH:MM` for any modern date but carries a seconds component for pre-railway local-mean-time zones (Europe/London before 1847 surfaces `-00:01:15`). `xxx` is the one variant with a slot for that — it passes a sub-minute offset through verbatim — while the other five throw a descriptive error recommending `xxx` rather than silently truncating the seconds away. Parse-side offset shapes have no seconds group, so a sub-minute offset can't be parsed back through a token; construct the `ZonedDateTime` directly if you need to round-trip one of those.
|
|
211
|
+
|
|
210
212
|
Range: `-12:00` to `+14:00`, the IANA-supported range. Out-of-range values throw a descriptive error naming the bound.
|
|
211
213
|
|
|
212
214
|
## Locales
|
|
@@ -832,6 +834,17 @@ parse('yyyy-MM-dd', '٢٠٢٦-٠٨-٠٤', { parseNumberingSystem: 'arab' }).toSt
|
|
|
832
834
|
|
|
833
835
|
The two option names are deliberately different (`numberingSystem` vs. `parseNumberingSystem`), not a naming inconsistency — the two directions aren't always symmetric. You might want Arabic-Indic digits in your UI output without expecting Arabic-Indic digits back on input, or the reverse, so a caller mixing `format()` and `parse()` options in one config object can set each independently. `formatToParts()` applies numbering per-part rather than once at the end, so a caller styling individual token parts (one `<span>` per token, say) still gets correctly-transliterated digits in each part instead of plain ASCII. An unsupported system name throws immediately rather than silently falling back to `'latn'`.
|
|
834
836
|
|
|
837
|
+
Rather than hardcoding a system code, pass `'auto'` to use whichever numeral system the call's locale itself uses, resolved through `Intl.NumberFormat(locale).resolvedOptions().numberingSystem`:
|
|
838
|
+
|
|
839
|
+
```js
|
|
840
|
+
format(date, 'yyyy-MM-dd', { numberingSystem: 'auto', locale: 'ar-EG' }); // "٢٠٢٦-٠٨-٠٤" — ar-EG's native digits
|
|
841
|
+
format(date, 'yyyy-MM-dd', { numberingSystem: 'auto', locale: 'bn-BD' }); // "২০২৬-০৮-০৪"
|
|
842
|
+
format(date, 'yyyy-MM-dd', { numberingSystem: 'auto' }); // "2026-08-04" — en-US defaults to latn
|
|
843
|
+
parse('yyyy-MM-dd', '٢٠٢٦-٠٨-٠٤', { parseNumberingSystem: 'auto', locale: 'ar-EG' }); // same resolution, input direction
|
|
844
|
+
```
|
|
845
|
+
|
|
846
|
+
`'auto'` works the same on both sides and through `createConfig()` (a config's `numberingSystem: 'auto'` resolves per call, against whichever locale that call uses). When the locale's native system isn't one this library can transliterate (Thai digits, Lao, Myanmar, ...), `'auto'` falls back to `'latn'` rather than throwing — only a malformed locale tag throws. Unset still means `'latn'` everywhere; `'auto'` is an opt-in, not a default change.
|
|
847
|
+
|
|
835
848
|
If you'd rather convert digits yourself instead of going through `format()`/`parse()`'s options — say, transliterating a string that came from somewhere else entirely — the underlying conversion is available directly:
|
|
836
849
|
|
|
837
850
|
```js
|
|
@@ -859,51 +872,18 @@ A handful of functions exist specifically to feed editor tooling — autocomplet
|
|
|
859
872
|
- `getInlineDiagnostics(formatStr)` — diagnostics with position and suggested fixes for a given format string.
|
|
860
873
|
- `previewFormat(formatStr, sample?)` — a live preview string for a format, given an optional sample value.
|
|
861
874
|
- `getDocUrl(tokenName)` — a documentation URL for a token, currently pointing at this README's [Tokens](#tokens) section.
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
## CLI
|
|
865
|
-
|
|
866
|
-
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:
|
|
875
|
+
`DAYJS_TO_TEMPORAL_FMT` / `DATE_FNS_TO_TEMPORAL_FMT` expose token conversion hints for migration tooling.
|
|
867
876
|
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
```sh
|
|
871
|
-
temporal-fmt format "2026-08-04T15:45:30" "yyyy-MM-dd HH:mm:ss"
|
|
872
|
-
temporal-fmt parse "yyyy-MM-dd" "2026-08-04"
|
|
873
|
-
temporal-fmt inspect "MMMM d, yyyy 'at' h:mm a"
|
|
874
|
-
temporal-fmt validate "yyyy-MM-dd HH:mm:ss"
|
|
875
|
-
temporal-fmt translate dayjs "YYYY-MM-DD HH:mm:ss"
|
|
876
|
-
```
|
|
877
|
+
## Legacy mod documentation
|
|
877
878
|
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
| `parse <format-string> <input> [--locale=LOCALE] [--lenient]` | Parses input against a format string, prints the resulting ISO value. |
|
|
882
|
-
| `inspect <format-string>` | Prints `explainFormat`'s report on a format string. |
|
|
883
|
-
| `validate <format-string>` | Prints `valid` or `invalid`. |
|
|
884
|
-
| `translate <source-lib> <format-string>` | Translates a Day.js or date-fns format string to `temporal-fmt` tokens. |
|
|
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.
|
|
885
882
|
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
Run `temporal-fmt` with no arguments to start a REPL:
|
|
891
|
-
|
|
892
|
-
```
|
|
893
|
-
$ temporal-fmt
|
|
894
|
-
temporal-fmt interactive mode. Type a subcommand, "help", or "exit".
|
|
895
|
-
temporal-fmt> format
|
|
896
|
-
ISO input: 2026-08-04T15:45:30
|
|
897
|
-
Format string: yyyy-MM-dd HH:mm:ss
|
|
898
|
-
2026-08-04 15:45:30
|
|
899
|
-
temporal-fmt> exit
|
|
900
|
-
```
|
|
901
|
-
|
|
902
|
-
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.
|
|
903
|
-
|
|
904
|
-
## Mods (advanced, optional)
|
|
905
|
-
|
|
906
|
-
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.
|
|
907
887
|
|
|
908
888
|
## Subpath imports
|
|
909
889
|
|
|
@@ -923,8 +903,6 @@ import { registerLocale } from 'temporal-fmt/locale';
|
|
|
923
903
|
|
|
924
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.
|
|
925
905
|
|
|
926
|
-
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.
|
|
927
|
-
|
|
928
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.
|
|
929
907
|
|
|
930
908
|
## Migrating from Day.js or date-fns
|
|
@@ -1018,21 +996,12 @@ function formatDate(date, formatStr, opts) {
|
|
|
1018
996
|
|
|
1019
997
|
Migrate file by file, dropping the wrapper once nothing calls the old path anymore.
|
|
1020
998
|
|
|
1021
|
-
## Known limitations
|
|
1022
|
-
|
|
1023
|
-
- **Numerals default to Western digits** in numeric tokens, regardless of locale, unless you opt into `{ numberingSystem }` / `{ parseNumberingSystem }` — see [Numbering systems](#numbering-systems).
|
|
1024
|
-
- **Locale-aware tokens need Node 20+**, native or polyfilled. Untested below that.
|
|
1025
|
-
- **You must provide a Temporal implementation** on anything below Node 26 — see [Providing `Temporal`](#providing-temporal).
|
|
1026
|
-
- **Pre-1582 dates and locale-aware tokens don't mix well on native Temporal (Node 26+).** `MMMM`/`MMM`/`EEEE`/`EEE` can render the wrong month or weekday for dates before roughly 1582 CE. This is an ICU limitation, not a bug here: ICU's default Gregorian calendar cutover is October 15, 1582, so `Intl.DateTimeFormat.formatToParts()` silently reinterprets earlier dates under the Julian calendar even though `Temporal` itself uses a proleptic Gregorian calendar throughout — see [tc39/ecma402#1003](https://github.com/tc39/ecma402/issues/1003). Numeric tokens never touch `Intl` and aren't affected.
|
|
1027
|
-
- **Gluing two unpadded numeric tokens with no separator is ambiguous for some inputs**, and `parse()` throws rather than guessing (`Md`, `dM`, `Hm` against certain input). `"121"` against `yyyy-Md` could mean month 1/day 21 or month 12/day 1 — both valid, no single correct reading. Unambiguous inputs against the same format string parse fine (`"85"` against `yyyy-Md` has only one valid split). Fix it by zero-padding (`MM`/`dd`), adding a separator, or opting into `{ lenient: true }`. Note that `Md` (or `dM`/`Hm`) with no `yyyy` present always throws regardless of ambiguity — `parse()` needs year, month, and day together to build a date at all.
|
|
1028
|
-
- **Offset tokens can't express sub-minute historical offsets.** They read `ZonedDateTime.prototype.offset`, which Temporal exposes as `+HH:MM` for any modern date. Historical LMT offsets with seconds (Europe/London before 1847 was `+00:01:15`) aren't reachable through that field, and the offset tokens' regex shapes don't include a seconds group either. Construct the `ZonedDateTime` directly if you need to round-trip one of those. Offset range is bounded to `-12:00` through `+14:00` (Baker Island to Kiritimati) — `+14:01`/`-12:01` throw even though each digit is individually plausible, since no real zone uses an offset past that range.
|
|
1029
|
-
|
|
1030
999
|
## Related tools
|
|
1031
1000
|
|
|
1032
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:
|
|
1033
1002
|
|
|
1034
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.
|
|
1035
|
-
- [`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.
|
|
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.
|
|
1036
1005
|
|
|
1037
1006
|
## Testing
|
|
1038
1007
|
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
applyParseNumbering,
|
|
3
3
|
tokenize
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-DMJWJTFY.js";
|
|
5
5
|
import {
|
|
6
6
|
DEFAULT_LOCALE
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-Z245NHDT.js";
|
|
8
8
|
import {
|
|
9
9
|
getTemporal
|
|
10
10
|
} from "./chunk-DFWTCRS3.js";
|
|
@@ -26,7 +26,7 @@ import {
|
|
|
26
26
|
getLocaleVocab,
|
|
27
27
|
subscribeToVocabChanges,
|
|
28
28
|
wrapUntypedError
|
|
29
|
-
} from "./chunk-
|
|
29
|
+
} from "./chunk-QRLQ7RR7.js";
|
|
30
30
|
|
|
31
31
|
// src/pattern.ts
|
|
32
32
|
function escapeRegExp(literal) {
|
|
@@ -981,4 +981,4 @@ export {
|
|
|
981
981
|
parseToParts,
|
|
982
982
|
compileParser
|
|
983
983
|
};
|
|
984
|
-
//# sourceMappingURL=chunk-
|
|
984
|
+
//# sourceMappingURL=chunk-5KP5H7X2.js.map
|
|
@@ -1,19 +1,17 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
} from "./chunk-BQDOTFGC.js";
|
|
2
|
+
DEFAULT_LOCALE,
|
|
3
|
+
TOKENS
|
|
4
|
+
} from "./chunk-Z245NHDT.js";
|
|
6
5
|
import {
|
|
7
6
|
FormatSyntaxError,
|
|
8
7
|
InvalidLocaleError,
|
|
9
|
-
UnknownTokenError
|
|
10
|
-
|
|
8
|
+
UnknownTokenError,
|
|
9
|
+
canonicalCacheKey,
|
|
10
|
+
normalizeLocaleTag
|
|
11
|
+
} from "./chunk-QRLQ7RR7.js";
|
|
11
12
|
|
|
12
13
|
// src/tokenize.ts
|
|
13
14
|
var SORTED_TOKEN_STRINGS = TOKENS.map(([tok]) => tok).sort((a, b) => b.length - a.length);
|
|
14
|
-
onTokenTableChange(() => {
|
|
15
|
-
SORTED_TOKEN_STRINGS = getEffectiveTokens().map(([tok]) => tok).sort((a, b) => b.length - a.length);
|
|
16
|
-
});
|
|
17
15
|
function tokenizeWithSpans(format) {
|
|
18
16
|
const pieces = [];
|
|
19
17
|
let i = 0;
|
|
@@ -103,6 +101,31 @@ var SUPPORTED_NUMBERING_SYSTEMS = /* @__PURE__ */ new Set([
|
|
|
103
101
|
"hanidec"
|
|
104
102
|
]);
|
|
105
103
|
var digitMapCache = /* @__PURE__ */ new Map();
|
|
104
|
+
var autoNumberingCache = /* @__PURE__ */ new Map();
|
|
105
|
+
var MAX_AUTO_NUMBERING_CACHE = 500;
|
|
106
|
+
function resolveAutoNumberingSystem(locale) {
|
|
107
|
+
const key = canonicalCacheKey(locale);
|
|
108
|
+
const cached = autoNumberingCache.get(key);
|
|
109
|
+
if (cached !== void 0) return cached;
|
|
110
|
+
let resolved;
|
|
111
|
+
try {
|
|
112
|
+
resolved = new Intl.NumberFormat(normalizeLocaleTag(locale)).resolvedOptions().numberingSystem;
|
|
113
|
+
} catch (err) {
|
|
114
|
+
throw new InvalidLocaleError({ actual: locale, reason: err.message });
|
|
115
|
+
}
|
|
116
|
+
const system = SUPPORTED_NUMBERING_SYSTEMS.has(resolved) ? resolved : "latn";
|
|
117
|
+
if (autoNumberingCache.size >= MAX_AUTO_NUMBERING_CACHE) {
|
|
118
|
+
const oldestKey = autoNumberingCache.keys().next().value;
|
|
119
|
+
if (oldestKey !== void 0) autoNumberingCache.delete(oldestKey);
|
|
120
|
+
}
|
|
121
|
+
autoNumberingCache.set(key, system);
|
|
122
|
+
return system;
|
|
123
|
+
}
|
|
124
|
+
function resolveRequestedSystem(requested, locale) {
|
|
125
|
+
if (requested === void 0) return "latn";
|
|
126
|
+
if (requested === "auto") return resolveAutoNumberingSystem(locale ?? DEFAULT_LOCALE);
|
|
127
|
+
return requested;
|
|
128
|
+
}
|
|
106
129
|
function getDigitMap(system) {
|
|
107
130
|
let map = digitMapCache.get(system);
|
|
108
131
|
if (map) return map;
|
|
@@ -157,12 +180,12 @@ function convertDigitsToAscii(s, system) {
|
|
|
157
180
|
return result;
|
|
158
181
|
}
|
|
159
182
|
function applyNumbering(s, options) {
|
|
160
|
-
const system = options.numberingSystem
|
|
183
|
+
const system = resolveRequestedSystem(options.numberingSystem, options.locale);
|
|
161
184
|
if (system === "latn") return s;
|
|
162
185
|
return convertDigits(s, system);
|
|
163
186
|
}
|
|
164
187
|
function applyParseNumbering(s, options) {
|
|
165
|
-
const system = options.parseNumberingSystem
|
|
188
|
+
const system = resolveRequestedSystem(options.parseNumberingSystem, options.locale);
|
|
166
189
|
if (system === "latn") return s;
|
|
167
190
|
return convertDigitsToAscii(s, system);
|
|
168
191
|
}
|
|
@@ -176,4 +199,4 @@ export {
|
|
|
176
199
|
applyNumbering,
|
|
177
200
|
applyParseNumbering
|
|
178
201
|
};
|
|
179
|
-
//# sourceMappingURL=chunk-
|
|
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":[]}
|
|
@@ -2,7 +2,7 @@ import {
|
|
|
2
2
|
InvalidLocaleError,
|
|
3
3
|
getLocaleVocab,
|
|
4
4
|
registerLocaleVocab
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-QRLQ7RR7.js";
|
|
6
6
|
|
|
7
7
|
// src/localeRegistry.ts
|
|
8
8
|
var extendedVocabs = /* @__PURE__ */ new Map();
|
|
@@ -72,4 +72,4 @@ export {
|
|
|
72
72
|
getLocale,
|
|
73
73
|
hasLocale
|
|
74
74
|
};
|
|
75
|
-
//# sourceMappingURL=chunk-
|
|
75
|
+
//# sourceMappingURL=chunk-FLMOMOAQ.js.map
|
|
@@ -1,29 +1,23 @@
|
|
|
1
1
|
import {
|
|
2
2
|
applyNumbering,
|
|
3
3
|
tokenize
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-DMJWJTFY.js";
|
|
5
5
|
import {
|
|
6
6
|
DEFAULT_LOCALE,
|
|
7
|
-
TOKENS
|
|
8
|
-
|
|
9
|
-
onTokenTableChange
|
|
10
|
-
} from "./chunk-BQDOTFGC.js";
|
|
7
|
+
TOKENS
|
|
8
|
+
} from "./chunk-Z245NHDT.js";
|
|
11
9
|
import {
|
|
12
10
|
MAX_FORMAT_LENGTH
|
|
13
11
|
} from "./chunk-SOPTTXUI.js";
|
|
14
12
|
import {
|
|
15
13
|
FormatSyntaxError,
|
|
16
14
|
UnknownTokenError
|
|
17
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-QRLQ7RR7.js";
|
|
18
16
|
|
|
19
17
|
// src/format.ts
|
|
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-
|
|
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-
|
|
408
|
+
//# sourceMappingURL=chunk-KV6GMX4D.js.map
|