temporal-fmt 0.7.8 → 0.7.95

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 CHANGED
@@ -1,4 +1,4 @@
1
- # temporal-fmt
1
+ # temporal-fmt 🥶🔥
2
2
 
3
3
  Format `Temporal.PlainDate` / `PlainTime` / `PlainDateTime` / `ZonedDateTime` objects
4
4
  using date-fns-style token strings.
@@ -202,19 +202,30 @@ sitting in your output waiting to confuse someone in three weeks.
202
202
  tokens (`yyyy`/`MM`/`dd`) never go through `Intl` and aren't affected.
203
203
  - Gluing two unpadded numeric tokens with no separator between them (e.g.
204
204
  `Md`, `dM`, `Hm`) is ambiguous for some inputs, and `parse()` throws rather
205
- than guessing. `"121"` against `Md` could mean month 1/day 21 or month
205
+ than guessing. `"121"` against `yyyy-Md` could mean month 1/day 21 or month
206
206
  12/day 1 — both are valid, so there's no single correct reading to fall
207
207
  back to. Unambiguous inputs against the same format string still parse
208
- normally (`"85"` against `Md` only has one valid split). If you need glued
209
- numeric fields, either zero-pad them (`MM`/`dd`) or put a separator between
210
- them; that removes the ambiguity entirely.
208
+ normally (`"85"` against `yyyy-Md` only has one valid split). If you need
209
+ glued numeric fields, either zero-pad them (`MM`/`dd`) or put a separator
210
+ between them; that removes the ambiguity entirely.
211
+
212
+ Note: `Md` (or `dM`/`Hm`) alone, with no `yyyy`, always throws —
213
+ `parse()` requires year, month, and day together to build a date, so a
214
+ bare `Md` format string is incomplete regardless of ambiguity. The
215
+ examples above use `yyyy-Md` for exactly this reason.
211
216
 
212
217
  ## Dev notes
213
218
 
214
- `tsconfig.json` sets `ignoreDeprecations: "6.0"` to work around a tsup bug
215
- ([tsup#1388](https://github.com/egoist/tsup/issues/1388)/[#1389](https://github.com/egoist/tsup/issues/1389)). tsup's dts build step quietly injects a deprecated
216
- `baseUrl`, and TypeScript 6+ hard-errors on it. Workaround, not a fix — drop
217
- it the moment tsup ships a real one upstream.
219
+ Building requires TypeScript 7.0.2+ but `.d.ts` generation runs as a separate
220
+ `tsc` pass, not through tsup. tsup's dts step bundles types via
221
+ `rollup-plugin-dts`, which calls into TypeScript's compiler API — the API
222
+ isn't stable yet on 7.x ([targeted for
223
+ 7.1](https://devblogs.microsoft.com/typescript/announcing-typescript-7-0/)),
224
+ so it crashes on 7.0.2. `tsup.config.ts` sets `dts: false` and `build` runs
225
+ `tsup && tsc --declaration --emitDeclarationOnly` instead. One side effect:
226
+ `dist/` now has one `.d.ts` per source file instead of a single rolled-up
227
+ `index.d.ts` — same exported API, different file layout. Revert to `dts:
228
+ true` once tsup/rollup-plugin-dts catch up.
218
229
 
219
230
  Tests pull from `temporal-polyfill/full`, not the slim `temporal-polyfill` —
220
231
  the Hebrew-calendar test needs the full build's calendar data, and the slim
@@ -227,4 +238,4 @@ through `Intl.DateTimeFormat` directly; on the polyfill, it falls back to
227
238
 
228
239
  ## License
229
240
 
230
- MIT
241
+ MIT
@@ -0,0 +1 @@
1
+ export declare const MAX_FORMAT_LENGTH = 1000;
@@ -0,0 +1,13 @@
1
+ import { type TemporalLike, type FormatOptions } from './tokens.js';
2
+ /**
3
+ * Format a Temporal.PlainDate, PlainTime, PlainDateTime, or ZonedDateTime
4
+ * using a date-fns-style token string.
5
+ *
6
+ * @example
7
+ * format(Temporal.Now.plainDateISO(), 'yyyy-MM-dd') // "2026-08-04"
8
+ * format(zdt, "MMM d, yyyy 'at' h:mm a") // "Aug 4, 2026 at 3:45 PM"
9
+ * format(zdt, 'MMMM d, yyyy', { locale: 'fr-FR' }) // "août 4, 2026"
10
+ *
11
+ * Throws on a token the input type doesn't support (e.g. 'HH' on a PlainDate).
12
+ */
13
+ export declare function format(temporal: TemporalLike, formatStr: string, options?: FormatOptions): string;
package/dist/index.d.ts CHANGED
@@ -1,83 +1,5 @@
1
- interface TemporalLike {
2
- year?: number;
3
- month?: number;
4
- day?: number;
5
- hour?: number;
6
- minute?: number;
7
- second?: number;
8
- millisecond?: number;
9
- timeZoneId?: string;
10
- dayOfWeek?: number;
11
- calendarId?: string;
12
- toInstant?: () => unknown;
13
- toLocaleString?: (locale: string, options: Intl.DateTimeFormatOptions) => string;
14
- }
15
- interface FormatOptions {
16
- /** BCP 47 locale tag, e.g. 'en-US', 'fr-FR', 'ar-EG'. Defaults to 'en-US'. */
17
- locale?: string;
18
- }
19
-
20
- /**
21
- * Format a Temporal.PlainDate, PlainTime, PlainDateTime, or ZonedDateTime
22
- * using a date-fns-style token string.
23
- *
24
- * @example
25
- * format(Temporal.Now.plainDateISO(), 'yyyy-MM-dd') // "2026-08-04"
26
- * format(zdt, "MMM d, yyyy 'at' h:mm a") // "Aug 4, 2026 at 3:45 PM"
27
- * format(zdt, 'MMMM d, yyyy', { locale: 'fr-FR' }) // "août 4, 2026"
28
- *
29
- * Throws on a token the input type doesn't support (e.g. 'HH' on a PlainDate).
30
- */
31
- declare function format(temporal: TemporalLike, formatStr: string, options?: FormatOptions): string;
32
-
33
- /**
34
- * Parses `input` against `formatStr` and builds the real Temporal value it
35
- * describes: a `Temporal.PlainDate`, `PlainTime`, `PlainDateTime`, or
36
- * `ZonedDateTime` depending on which tokens are present.
37
- *
38
- * Returns `unknown` — this package has no ambient `Temporal` types to return
39
- * a real one against.
40
- *
41
- * `options.locale` picks the calendar the result is built in. Pass a locale
42
- * tag with a `-u-ca-` extension (e.g. `'en-u-ca-hebrew'`) to parse into a
43
- * non-Gregorian calendar.
44
- *
45
- * @throws if `input` doesn't match `formatStr`'s shape at all
46
- * @throws if it matches the shape but describes an impossible date (e.g. Feb
47
- * 30) or self-contradictory data (e.g. a weekday name that doesn't match the
48
- * actual date)
49
- *
50
- * @example
51
- * parse('yyyy-MM-dd HH:mm', '2026-08-04 15:45') // Temporal.PlainDateTime
52
- * parse('yyyy-MM', '2026-08-04T15:45:30') // throws — shape doesn't match
53
- * parse('yyyy-MM-dd', '2026-02-30') // throws — not a real date
54
- */
55
- declare function parse(formatStr: string, input: string, options?: FormatOptions): unknown | undefined;
56
-
57
- interface TemporalFactory {
58
- from(fields: Record<string, number | string | undefined>, options?: {
59
- overflow?: 'constrain' | 'reject';
60
- }): unknown;
61
- }
62
- interface TemporalNamespace {
63
- PlainDate: TemporalFactory;
64
- PlainTime: TemporalFactory;
65
- PlainDateTime: TemporalFactory;
66
- ZonedDateTime: TemporalFactory;
67
- }
68
- /**
69
- * Explicitly hand temporal-fmt the Temporal implementation to use, instead
70
- * of relying on a global `Temporal`. Call this once, before your first
71
- * `format()`/`parse()`
72
- *
73
- * Call with no argument (or `undefined`) to clear the override and fall
74
- * back to `globalThis.Temporal` again.
75
- *
76
- * @example
77
- * import { Temporal } from 'temporal-polyfill';
78
- * import { setTemporal } from 'temporal-fmt';
79
- * setTemporal(Temporal);
80
- */
81
- declare function setTemporal(temporal?: TemporalNamespace): void;
82
-
83
- export { type FormatOptions, type TemporalLike, type TemporalNamespace, format, parse, setTemporal };
1
+ export { format } from './format.js';
2
+ export { parse } from './parse.js';
3
+ export { setTemporal } from './temporalProvider.js';
4
+ export type { TemporalLike, FormatOptions } from './tokens.js';
5
+ export type { TemporalNamespace } from './temporalProvider.js';
@@ -0,0 +1,8 @@
1
+ export interface LocaleVocab {
2
+ monthLong: string[];
3
+ monthShort: string[];
4
+ weekdayLong: string[];
5
+ weekdayShort: string[];
6
+ dayPeriod: string[];
7
+ }
8
+ export declare function getLocaleVocab(locale: string): LocaleVocab;
@@ -0,0 +1,24 @@
1
+ import { type FormatOptions } from './tokens.js';
2
+ /**
3
+ * Parses `input` against `formatStr` and builds the real Temporal value it
4
+ * describes: a `Temporal.PlainDate`, `PlainTime`, `PlainDateTime`, or
5
+ * `ZonedDateTime` depending on which tokens are present.
6
+ *
7
+ * Returns `unknown` — this package has no ambient `Temporal` types to return
8
+ * a real one against.
9
+ *
10
+ * `options.locale` picks the calendar the result is built in. Pass a locale
11
+ * tag with a `-u-ca-` extension (e.g. `'en-u-ca-hebrew'`) to parse into a
12
+ * non-Gregorian calendar.
13
+ *
14
+ * @throws if `input` doesn't match `formatStr`'s shape at all
15
+ * @throws if it matches the shape but describes an impossible date (e.g. Feb
16
+ * 30) or self-contradictory data (e.g. a weekday name that doesn't match the
17
+ * actual date)
18
+ *
19
+ * @example
20
+ * parse('yyyy-MM-dd HH:mm', '2026-08-04 15:45') // Temporal.PlainDateTime
21
+ * parse('yyyy-MM', '2026-08-04T15:45:30') // throws — shape doesn't match
22
+ * parse('yyyy-MM-dd', '2026-02-30') // throws — not a real date
23
+ */
24
+ export declare function parse(formatStr: string, input: string, options?: FormatOptions): unknown | undefined;
@@ -0,0 +1,19 @@
1
+ import type { Piece } from './tokenize.js';
2
+ export interface CapturingPattern {
3
+ regex: RegExp;
4
+ groups: Array<{
5
+ name: string;
6
+ token: string;
7
+ }>;
8
+ ambiguousRuns: Array<{
9
+ groupNames: string[];
10
+ tokens: string[];
11
+ }>;
12
+ }
13
+ /**
14
+ * Same walk as buildPatternSource() in pattern.ts, but each token piece
15
+ * gets its own named capture group (positionally named so the same token,
16
+ * e.g. "yyyy", could in theory appear twice) so a caller can pull the
17
+ * matched substring for each token back out after a successful match.
18
+ */
19
+ export declare function buildCapturingPattern(pieces: Piece[], locale: string): CapturingPattern;
@@ -0,0 +1,27 @@
1
+ export declare function tokenFragment(token: string, locale: string): string;
2
+ export declare const UNPADDED_NUMERIC_TOKENS: Set<string>;
3
+ export declare const UNPADDED_NUMERIC_RANGES: Record<string, Array<{
4
+ digits: 1 | 2;
5
+ min: number;
6
+ max: number;
7
+ }>>;
8
+ /**
9
+ * Given the literal digit string a run of N adjacent unpadded-numeric
10
+ * tokens matched as a whole (e.g. "112" for a 2-token run), enumerates
11
+ * every way to split it into N pieces (one per token, each piece 1-2
12
+ * digits per that token's own width rule) and returns every split where
13
+ * every piece is independently valid for its token. Length 0 means the
14
+ * run's regex match shouldn't have been possible in the first place
15
+ * (shouldn't happen — the caller only invokes this after the whole
16
+ * pattern already matched, meaning at least one split exists: the one the
17
+ * regex actually took). Length 1 means the reading is unambiguous.
18
+ * Length 2+ means true ambiguity — the caller should throw rather than
19
+ * pick one.
20
+ *
21
+ * Recursive over token count rather than hardcoded to 2, so a 3+ token
22
+ * unseparated run (e.g. "Hms") is covered by the same logic without a
23
+ * special case — those are rarer in practice but not impossible, and a
24
+ * partial fix that only covered pairs would leave the identical bug for
25
+ * anyone writing a 3-token glued run.
26
+ */
27
+ export declare function enumerateValidSplits(digits: string, tokens: string[]): number[][];
@@ -0,0 +1,27 @@
1
+ interface TemporalFactory {
2
+ from(fields: Record<string, number | string | undefined>, options?: {
3
+ overflow?: 'constrain' | 'reject';
4
+ }): unknown;
5
+ }
6
+ export interface TemporalNamespace {
7
+ PlainDate: TemporalFactory;
8
+ PlainTime: TemporalFactory;
9
+ PlainDateTime: TemporalFactory;
10
+ ZonedDateTime: TemporalFactory;
11
+ }
12
+ /**
13
+ * Explicitly hand temporal-fmt the Temporal implementation to use, instead
14
+ * of relying on a global `Temporal`. Call this once, before your first
15
+ * `format()`/`parse()`
16
+ *
17
+ * Call with no argument (or `undefined`) to clear the override and fall
18
+ * back to `globalThis.Temporal` again.
19
+ *
20
+ * @example
21
+ * import { Temporal } from 'temporal-polyfill';
22
+ * import { setTemporal } from 'temporal-fmt';
23
+ * setTemporal(Temporal);
24
+ */
25
+ export declare function setTemporal(temporal?: TemporalNamespace): void;
26
+ export declare function getTemporal(): TemporalNamespace;
27
+ export {};
@@ -0,0 +1,14 @@
1
+ export type Piece = {
2
+ kind: 'token';
3
+ value: string;
4
+ } | {
5
+ kind: 'literal';
6
+ value: string;
7
+ };
8
+ /**
9
+ * Splits a format string like `"yyyy-MM-dd 'at' HH:mm"` into token/literal
10
+ * pieces. Text in single quotes is always literal (e.g. write 'rd' in
11
+ * "3rd" so it's not read as the day token). A doubled quote ('') means a
12
+ * literal quote character, both inside a quoted span and standalone.
13
+ */
14
+ export declare function tokenize(format: string): Piece[];
@@ -0,0 +1,23 @@
1
+ export declare function pad(n: number, len: number): string;
2
+ export interface TemporalLike {
3
+ year?: number;
4
+ month?: number;
5
+ day?: number;
6
+ hour?: number;
7
+ minute?: number;
8
+ second?: number;
9
+ millisecond?: number;
10
+ timeZoneId?: string;
11
+ dayOfWeek?: number;
12
+ calendarId?: string;
13
+ toInstant?: () => unknown;
14
+ toLocaleString?: (locale: string, options: Intl.DateTimeFormatOptions) => string;
15
+ }
16
+ export interface FormatOptions {
17
+ /** BCP 47 locale tag, e.g. 'en-US', 'fr-FR', 'ar-EG'. Defaults to 'en-US'. */
18
+ locale?: string;
19
+ }
20
+ export declare const DEFAULT_LOCALE = "en-US";
21
+ type TokenHandler = (t: TemporalLike, locale: string) => string;
22
+ export declare const TOKENS: Array<[string, TokenHandler, keyof TemporalLike]>;
23
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "temporal-fmt",
3
- "version": "0.7.8",
3
+ "version": "0.7.95",
4
4
  "description": "Format Temporal.PlainDate/PlainDateTime/PlainTime/ZonedDateTime objects using date-fns-style token strings.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -18,7 +18,7 @@
18
18
  ],
19
19
  "sideEffects": false,
20
20
  "scripts": {
21
- "build": "tsup",
21
+ "build": "tsup && tsc --declaration --emitDeclarationOnly",
22
22
  "dev": "tsup --watch",
23
23
  "test": "node --test test/*.test.js",
24
24
  "prepublishOnly": "npm run build && npm test"
@@ -44,9 +44,10 @@
44
44
  },
45
45
  "homepage": "https://github.com/DirazCoder/temporal-fmt#readme",
46
46
  "devDependencies": {
47
+ "@typescript/native-preview": "^7.0.0-dev.20260707.2",
47
48
  "temporal-polyfill": "^1.0.3",
48
49
  "tsup": "^8.5.1",
49
- "typescript": "^6.0.3"
50
+ "typescript": "^7.0.2"
50
51
  },
51
52
  "overrides": {
52
53
  "esbuild": "0.28.1"
@@ -57,4 +58,4 @@
57
58
  "allowScripts": {
58
59
  "esbuild@0.28.1": true
59
60
  }
60
- }
61
+ }
package/dist/index.d.cts DELETED
@@ -1,83 +0,0 @@
1
- interface TemporalLike {
2
- year?: number;
3
- month?: number;
4
- day?: number;
5
- hour?: number;
6
- minute?: number;
7
- second?: number;
8
- millisecond?: number;
9
- timeZoneId?: string;
10
- dayOfWeek?: number;
11
- calendarId?: string;
12
- toInstant?: () => unknown;
13
- toLocaleString?: (locale: string, options: Intl.DateTimeFormatOptions) => string;
14
- }
15
- interface FormatOptions {
16
- /** BCP 47 locale tag, e.g. 'en-US', 'fr-FR', 'ar-EG'. Defaults to 'en-US'. */
17
- locale?: string;
18
- }
19
-
20
- /**
21
- * Format a Temporal.PlainDate, PlainTime, PlainDateTime, or ZonedDateTime
22
- * using a date-fns-style token string.
23
- *
24
- * @example
25
- * format(Temporal.Now.plainDateISO(), 'yyyy-MM-dd') // "2026-08-04"
26
- * format(zdt, "MMM d, yyyy 'at' h:mm a") // "Aug 4, 2026 at 3:45 PM"
27
- * format(zdt, 'MMMM d, yyyy', { locale: 'fr-FR' }) // "août 4, 2026"
28
- *
29
- * Throws on a token the input type doesn't support (e.g. 'HH' on a PlainDate).
30
- */
31
- declare function format(temporal: TemporalLike, formatStr: string, options?: FormatOptions): string;
32
-
33
- /**
34
- * Parses `input` against `formatStr` and builds the real Temporal value it
35
- * describes: a `Temporal.PlainDate`, `PlainTime`, `PlainDateTime`, or
36
- * `ZonedDateTime` depending on which tokens are present.
37
- *
38
- * Returns `unknown` — this package has no ambient `Temporal` types to return
39
- * a real one against.
40
- *
41
- * `options.locale` picks the calendar the result is built in. Pass a locale
42
- * tag with a `-u-ca-` extension (e.g. `'en-u-ca-hebrew'`) to parse into a
43
- * non-Gregorian calendar.
44
- *
45
- * @throws if `input` doesn't match `formatStr`'s shape at all
46
- * @throws if it matches the shape but describes an impossible date (e.g. Feb
47
- * 30) or self-contradictory data (e.g. a weekday name that doesn't match the
48
- * actual date)
49
- *
50
- * @example
51
- * parse('yyyy-MM-dd HH:mm', '2026-08-04 15:45') // Temporal.PlainDateTime
52
- * parse('yyyy-MM', '2026-08-04T15:45:30') // throws — shape doesn't match
53
- * parse('yyyy-MM-dd', '2026-02-30') // throws — not a real date
54
- */
55
- declare function parse(formatStr: string, input: string, options?: FormatOptions): unknown | undefined;
56
-
57
- interface TemporalFactory {
58
- from(fields: Record<string, number | string | undefined>, options?: {
59
- overflow?: 'constrain' | 'reject';
60
- }): unknown;
61
- }
62
- interface TemporalNamespace {
63
- PlainDate: TemporalFactory;
64
- PlainTime: TemporalFactory;
65
- PlainDateTime: TemporalFactory;
66
- ZonedDateTime: TemporalFactory;
67
- }
68
- /**
69
- * Explicitly hand temporal-fmt the Temporal implementation to use, instead
70
- * of relying on a global `Temporal`. Call this once, before your first
71
- * `format()`/`parse()`
72
- *
73
- * Call with no argument (or `undefined`) to clear the override and fall
74
- * back to `globalThis.Temporal` again.
75
- *
76
- * @example
77
- * import { Temporal } from 'temporal-polyfill';
78
- * import { setTemporal } from 'temporal-fmt';
79
- * setTemporal(Temporal);
80
- */
81
- declare function setTemporal(temporal?: TemporalNamespace): void;
82
-
83
- export { type FormatOptions, type TemporalLike, type TemporalNamespace, format, parse, setTemporal };