temporal-fmt 0.8.82 → 0.8.91
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 +550 -0
- package/calendar.d.cts +1 -0
- package/calendar.d.ts +1 -0
- package/dist/analyze.d.cts +38 -0
- package/dist/analyze.d.ts +38 -0
- package/dist/arithmetic.d.cts +37 -0
- package/dist/arithmetic.d.ts +37 -0
- package/dist/businessCalendar.d.cts +20 -0
- package/dist/businessCalendar.d.ts +20 -0
- package/dist/calendarUtils.cjs +234 -0
- package/dist/calendarUtils.cjs.map +1 -0
- package/dist/calendarUtils.d.cts +28 -0
- package/dist/calendarUtils.d.ts +28 -0
- package/dist/calendarUtils.js +34 -0
- package/dist/calendarUtils.js.map +1 -0
- package/dist/chunk-5GBOYMJS.js +57 -0
- package/dist/chunk-5GBOYMJS.js.map +1 -0
- package/dist/chunk-5QZT6T4P.js +164 -0
- package/dist/chunk-5QZT6T4P.js.map +1 -0
- package/dist/chunk-7SFQUOID.js +295 -0
- package/dist/chunk-7SFQUOID.js.map +1 -0
- package/dist/chunk-7ZKINLTL.js +749 -0
- package/dist/chunk-7ZKINLTL.js.map +1 -0
- package/dist/chunk-A76C5AGZ.js +156 -0
- package/dist/chunk-A76C5AGZ.js.map +1 -0
- package/dist/chunk-C3755VAP.js +200 -0
- package/dist/chunk-C3755VAP.js.map +1 -0
- package/dist/chunk-CWHSGH2Z.js +152 -0
- package/dist/chunk-CWHSGH2Z.js.map +1 -0
- package/dist/chunk-DXDPV25F.js +176 -0
- package/dist/chunk-DXDPV25F.js.map +1 -0
- package/dist/chunk-EDUFSRWJ.js +9 -0
- package/dist/chunk-EDUFSRWJ.js.map +1 -0
- package/dist/chunk-ETS7Q56I.js +69 -0
- package/dist/chunk-ETS7Q56I.js.map +1 -0
- package/dist/chunk-GIQZ24RL.js +188 -0
- package/dist/chunk-GIQZ24RL.js.map +1 -0
- package/dist/chunk-J3YC66TB.js +150 -0
- package/dist/chunk-J3YC66TB.js.map +1 -0
- package/dist/chunk-MXXYIMFJ.js +29 -0
- package/dist/chunk-MXXYIMFJ.js.map +1 -0
- package/dist/chunk-PGWSA5QP.js +589 -0
- package/dist/chunk-PGWSA5QP.js.map +1 -0
- package/dist/chunk-QL6WPSKN.js +197 -0
- package/dist/chunk-QL6WPSKN.js.map +1 -0
- package/dist/chunk-RY7Q64ZS.js +73 -0
- package/dist/chunk-RY7Q64ZS.js.map +1 -0
- package/dist/chunk-V35OBXHF.js +206 -0
- package/dist/chunk-V35OBXHF.js.map +1 -0
- package/dist/chunk-YS52LOMJ.js +51 -0
- package/dist/chunk-YS52LOMJ.js.map +1 -0
- package/dist/comparison.d.cts +18 -0
- package/dist/comparison.d.ts +18 -0
- package/dist/config.d.cts +22 -0
- package/dist/config.d.ts +22 -0
- package/dist/duration.cjs +601 -0
- package/dist/duration.cjs.map +1 -0
- package/dist/duration.d.cts +20 -0
- package/dist/duration.d.ts +20 -0
- package/dist/duration.js +31 -0
- package/dist/duration.js.map +1 -0
- package/dist/errors.d.cts +85 -0
- package/dist/errors.d.ts +85 -0
- package/dist/extensibility.d.cts +19 -0
- package/dist/extensibility.d.ts +19 -0
- package/dist/format.cjs +594 -0
- package/dist/format.cjs.map +1 -0
- package/dist/format.d.cts +19 -0
- package/dist/format.d.ts +19 -0
- package/dist/format.js +21 -0
- package/dist/format.js.map +1 -0
- package/dist/holidays.d.cts +17 -0
- package/dist/holidays.d.ts +17 -0
- package/dist/ideData.d.cts +35 -0
- package/dist/ideData.d.ts +35 -0
- package/dist/index.cjs +5898 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +27 -3
- package/dist/index.d.ts +27 -3
- package/dist/index.js +2654 -1
- package/dist/index.js.map +1 -1
- package/dist/interval.cjs +783 -0
- package/dist/interval.cjs.map +1 -0
- package/dist/interval.d.cts +22 -0
- package/dist/interval.d.ts +22 -0
- package/dist/interval.js +42 -0
- package/dist/interval.js.map +1 -0
- package/dist/localeRegistry.cjs +282 -0
- package/dist/localeRegistry.cjs.map +1 -0
- package/dist/localeRegistry.d.cts +27 -0
- package/dist/localeRegistry.d.ts +27 -0
- package/dist/localeRegistry.js +13 -0
- package/dist/localeRegistry.js.map +1 -0
- package/dist/numbering.d.cts +12 -0
- package/dist/numbering.d.ts +12 -0
- package/dist/parse.cjs +1404 -0
- package/dist/parse.cjs.map +1 -0
- package/dist/parse.d.cts +27 -0
- package/dist/parse.d.ts +27 -0
- package/dist/parse.js +22 -0
- package/dist/parse.js.map +1 -0
- package/dist/pattern.d.cts +1 -0
- package/dist/pattern.d.ts +1 -0
- package/dist/recurrence.cjs +376 -0
- package/dist/recurrence.cjs.map +1 -0
- package/dist/recurrence.d.cts +35 -0
- package/dist/recurrence.d.ts +35 -0
- package/dist/recurrence.js +21 -0
- package/dist/recurrence.js.map +1 -0
- package/dist/relativeGrammar.d.cts +27 -0
- package/dist/relativeGrammar.d.ts +27 -0
- package/dist/relativeTime.cjs +178 -0
- package/dist/relativeTime.cjs.map +1 -0
- package/dist/relativeTime.d.cts +6 -0
- package/dist/relativeTime.d.ts +6 -0
- package/dist/relativeTime.js +15 -0
- package/dist/relativeTime.js.map +1 -0
- package/dist/rounding.d.cts +37 -0
- package/dist/rounding.d.ts +37 -0
- package/dist/serialization.d.cts +18 -0
- package/dist/serialization.d.ts +18 -0
- package/dist/temporalProvider.d.cts +19 -1
- package/dist/temporalProvider.d.ts +19 -1
- package/dist/timezone.cjs +346 -0
- package/dist/timezone.cjs.map +1 -0
- package/dist/timezone.d.cts +33 -0
- package/dist/timezone.d.ts +33 -0
- package/dist/timezone.js +27 -0
- package/dist/timezone.js.map +1 -0
- package/dist/tokenMetadata.d.cts +13 -0
- package/dist/tokenMetadata.d.ts +13 -0
- package/dist/typeGuards.d.cts +49 -0
- package/dist/typeGuards.d.ts +49 -0
- package/duration.d.cts +1 -0
- package/duration.d.ts +1 -0
- package/format.d.cts +1 -0
- package/format.d.ts +1 -0
- package/interval.d.cts +1 -0
- package/interval.d.ts +1 -0
- package/locale.d.cts +1 -0
- package/locale.d.ts +1 -0
- package/package.json +114 -4
- package/parse.d.cts +1 -0
- package/parse.d.ts +1 -0
- package/recurrence.d.cts +1 -0
- package/recurrence.d.ts +1 -0
- package/relative.d.cts +1 -0
- package/relative.d.ts +1 -0
- package/scripts/cli.mjs +182 -0
- package/timezone.d.cts +1 -0
- package/timezone.d.ts +1 -0
package/README.md
CHANGED
|
@@ -412,6 +412,556 @@ parseRelative('5. März', today, { locale: 'de-DE' }).toString() //
|
|
|
412
412
|
|
|
413
413
|
Throws a descriptive error for any phrase it doesn't recognize, naming the supported categories in the message. Accepts `PlainDate`, `PlainDateTime`, or `ZonedDateTime` as the reference (needs `dayOfWeek` to compute weekday offsets). Throws on `PlainTime`.
|
|
414
414
|
|
|
415
|
+
## Subpath imports
|
|
416
|
+
|
|
417
|
+
Each capability area is also available as a subpath import, if you only need a slice and want a smaller bundle:
|
|
418
|
+
|
|
419
|
+
```js
|
|
420
|
+
import { format } from 'temporal-fmt/format';
|
|
421
|
+
import { parse } from 'temporal-fmt/parse';
|
|
422
|
+
import { formatDuration } from 'temporal-fmt/duration';
|
|
423
|
+
import { formatRelative } from 'temporal-fmt/relative';
|
|
424
|
+
import { interval, formatRange } from 'temporal-fmt/interval';
|
|
425
|
+
import { daysInMonth, startOf } from 'temporal-fmt/calendar';
|
|
426
|
+
import { resolveZoned, isDST } from 'temporal-fmt/timezone';
|
|
427
|
+
import { recurrence } from 'temporal-fmt/recurrence';
|
|
428
|
+
import { registerLocale } from 'temporal-fmt/locale';
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
## API reference
|
|
432
|
+
|
|
433
|
+
The core surface stays zero-runtime-dependency; everything below is exported from the main entry point (and, per the subpath list above, from its matching slice).
|
|
434
|
+
|
|
435
|
+
### Formatting
|
|
436
|
+
|
|
437
|
+
- `format(temporal, formatStr, options?)` — format a Temporal value using a date-fns-style token string.
|
|
438
|
+
- `formatToParts(temporal, formatStr, options?)` — same, but returns an array of `{type, value, token?}` parts.
|
|
439
|
+
- `compileFormat(formatStr)` — pre-compile a format string for repeated use.
|
|
440
|
+
|
|
441
|
+
### Parsing
|
|
442
|
+
|
|
443
|
+
- `parse(formatStr, input, options?)` — strict parse; throws on ambiguity, contradiction, or invalid date.
|
|
444
|
+
- `safeParse(formatStr, input, options?)` — returns `{ ok: true, value }` or `{ ok: false, error: TemporalFmtError }`.
|
|
445
|
+
- `tryParse(formatStr, input, options?)` — best-effort; returns the value or `undefined`.
|
|
446
|
+
- `parseToParts(formatStr, input, options?)` — return matched token groups with positions.
|
|
447
|
+
- `compileParser(formatStr, options?)` — pre-compile a parser.
|
|
448
|
+
|
|
449
|
+
### Introspection
|
|
450
|
+
|
|
451
|
+
- `analyzeFormat(formatStr)` — returns `{ tokens, requiredFields, compatibleTypes, parseable, localeSensitive, calendarSensitive, timezoneSensitive, ambiguous, roundTripSafe, warnings }`.
|
|
452
|
+
- `explainFormat(formatStr)` — human-readable rendering of `analyzeFormat`.
|
|
453
|
+
- `tokenInfo(name)` — metadata for one token, or undefined.
|
|
454
|
+
- `listTokens()` — every recognized token with metadata.
|
|
455
|
+
- `isValidFormat(formatStr)` — true iff tokenize() accepts the string.
|
|
456
|
+
- `validateFormat(formatStr)` — throws on invalid; returns the analysis.
|
|
457
|
+
|
|
458
|
+
### Type guards
|
|
459
|
+
|
|
460
|
+
- `isTemporal(value)`, `isInstant(value)`, `isPlainDate(value)`, `isPlainTime(value)`, `isPlainDateTime(value)`, `isZonedDateTime(value)`, `isPlainYearMonth(value)`, `isPlainMonthDay(value)`, `isDuration(value)`.
|
|
461
|
+
- `assertX(value)` variants throw descriptively on type mismatch.
|
|
462
|
+
|
|
463
|
+
### Typed errors
|
|
464
|
+
|
|
465
|
+
- `TemporalFmtError` — base class with `code`, `input`, `format`, `token`, `position`, `expected`, `actual`, `reason`.
|
|
466
|
+
- Subclasses: `FormatSyntaxError`, `UnknownTokenError`, `ParseMismatchError`, `InvalidDateError`, `InvalidTimeError`, `InvalidOffsetError`, `InvalidTimeZoneError`, `InvalidCalendarError`, `AmbiguousInputError`, `InvalidLocaleError`, `InvalidDurationError`.
|
|
467
|
+
- See [Error reference](#error-reference) below for what triggers each one.
|
|
468
|
+
|
|
469
|
+
### Duration APIs
|
|
470
|
+
|
|
471
|
+
- `formatDuration(duration, formatStr, options?)` — duration-specific token grammar (see [Duration formatting](#duration-formatting) above).
|
|
472
|
+
- `formatDurationToParts(duration, formatStr, options?)` — same, as parts.
|
|
473
|
+
- `parseDuration(input, formatStr, options?)` — inverse of `formatDuration`.
|
|
474
|
+
- `parseISODuration(input)` — parse `P[n]Y[n]M[n]W[n]DT[n]H[n]M[n]S` ISO 8601 duration.
|
|
475
|
+
- `formatISODuration(duration)` — inverse of `parseISODuration`.
|
|
476
|
+
- `balanceDuration(duration)` — normalize fields to their natural ranges.
|
|
477
|
+
- `roundDuration(duration, options)` — round to a unit; throws for calendar-bound units without a relativeTo.
|
|
478
|
+
- `totalDuration(duration, unit)` — sum absolute fields into the target unit.
|
|
479
|
+
- `compareDuration(a, b)` — `-1/0/1` by total absolute length.
|
|
480
|
+
- `addDuration(a, b)`, `subtractDuration(a, b)` — field-by-field sum/difference.
|
|
481
|
+
|
|
482
|
+
### Relative time
|
|
483
|
+
|
|
484
|
+
- `formatDistance(date1, date2, options?)` — "3 days ago", "in 2 hours" (see [Relative time](#relative-time-formatdistance) above).
|
|
485
|
+
- `formatRelative(date1, date2, options?)` — calendar-relative ("yesterday", "tomorrow", "last week").
|
|
486
|
+
- `formatRelativeToNow(date, options?)` — `formatRelative(date, now)`.
|
|
487
|
+
|
|
488
|
+
### Calendar utilities
|
|
489
|
+
|
|
490
|
+
- `daysInMonth(value)`, `daysInYear(value)`, `monthsInYear(value)`.
|
|
491
|
+
- `isLeapYear(value)`, `isLeapMonth(value)` (Gregorian returns false).
|
|
492
|
+
- `dayOfYear(value)`, `weekOfYear(value)`, `weekYear(value)`.
|
|
493
|
+
- `getQuarter(value)`, `getMonth(value)`, `getWeekday(value)`.
|
|
494
|
+
- `startOf(value, unit)`, `endOf(value, unit)` — returns a new field bag with finer fields zeroed/extended.
|
|
495
|
+
- See [Calendar guide](#calendar-guide) below for Gregorian-only caveats.
|
|
496
|
+
|
|
497
|
+
### Date arithmetic
|
|
498
|
+
|
|
499
|
+
- `add(value, amount, unit)`, `subtract(value, amount, unit)`.
|
|
500
|
+
- Per-unit wrappers: `addYears`, `addMonths`, `addWeeks`, `addDays`, `addHours`, `addMinutes`, `addSeconds`, `addMilliseconds` (and `subtract*` variants).
|
|
501
|
+
- `difference(a, b, unit)` — integer count of unit boundaries.
|
|
502
|
+
- Per-unit wrappers: `differenceInYears`, …, `differenceInMilliseconds`.
|
|
503
|
+
|
|
504
|
+
### Rounding
|
|
505
|
+
|
|
506
|
+
- `round(value, options)` — round to a unit with a mode.
|
|
507
|
+
- `floor(value, unit, increment?)`, `ceil(value, unit, increment?)`, `truncate(value, unit, increment?)`.
|
|
508
|
+
|
|
509
|
+
### Comparison
|
|
510
|
+
|
|
511
|
+
- `compare(a, b)` → `-1/0/1`.
|
|
512
|
+
- `isEqual`, `isBefore`, `isAfter`.
|
|
513
|
+
- `min(values)`, `max(values)`, `clamp(value, lo, hi)`, `isBetween(value, lo, hi)`.
|
|
514
|
+
- Semantic helpers: `isToday`, `isTomorrow`, `isYesterday`, `isSameDay`, `isSameWeek`, `isSameMonth`, `isSameQuarter`, `isSameYear`, `isWeekend`, `isWeekday`.
|
|
515
|
+
|
|
516
|
+
### Intervals
|
|
517
|
+
|
|
518
|
+
- `interval(start, end, bounds?)` — bounds: `'closed'` | `'open'` | `'half-open-start'` | `'half-open-end'`.
|
|
519
|
+
- `intervalContains(iv, value)`, `overlaps(a, b)`, `intersects(a, b)`, `intervalIsBefore(a, b)`, `intervalIsAfter(a, b)`.
|
|
520
|
+
- `intersection(a, b)`, `union(a, b)`, `intervalDifference(a, b)`, `intervalSubtract(a, b)`.
|
|
521
|
+
- `mergeIntervals(intervals)` — combine overlapping.
|
|
522
|
+
- `splitInterval(iv, n)` — N equal sub-intervals.
|
|
523
|
+
- `formatRange(iv, formatStr, options?)`, `formatRangeToParts(iv, formatStr, options?)` — uses `Intl.DateTimeFormat.formatRange` when available.
|
|
524
|
+
|
|
525
|
+
### Timezone subsystem
|
|
526
|
+
|
|
527
|
+
- `resolveZoned(fields, timeZone, options?)` — construct a `ZonedDateTime` with disambiguation mode (`compatible` | `earlier` | `later` | `reject`).
|
|
528
|
+
- `getTimeZone(value)`, `getOffset(value)`, `getOffsetNanoseconds(value)`.
|
|
529
|
+
- `isDST(value)` — heuristic, compares current offset to January offset.
|
|
530
|
+
- `getNextTransition(value)`, `getPreviousTransition(value)`, `getTransitions(start, end)`.
|
|
531
|
+
- `possibleInstantsFor(fields, timeZone)` — returns the list of possible instants (0 for gaps, 2 for overlaps, 1 otherwise).
|
|
532
|
+
|
|
533
|
+
### Recurrence
|
|
534
|
+
|
|
535
|
+
- `recurrence(start, rule)` — returns an iterator with `next()` and `previous()`.
|
|
536
|
+
- `take(iter, n)` — collect N occurrences.
|
|
537
|
+
- `skip(iter, n)` — skip N occurrences.
|
|
538
|
+
- `between(start, rule, rangeStart, rangeEnd)` — occurrences in range.
|
|
539
|
+
- `parseRRule(str)`, `formatRRule(rule)` — RFC 5545 interop.
|
|
540
|
+
|
|
541
|
+
### Business calendar
|
|
542
|
+
|
|
543
|
+
- `createBusinessCalendar(options?)` — customize weekend, holidays, working hours, half days.
|
|
544
|
+
- `isBusinessDay(cal, value)`, `addBusinessDays(cal, value, n)`, `subtractBusinessDays(cal, value, n)`.
|
|
545
|
+
- `differenceInBusinessDays(cal, a, b)`, `nextBusinessDay(cal, value)`, `previousBusinessDay(cal, value)`.
|
|
546
|
+
|
|
547
|
+
### Holiday framework
|
|
548
|
+
|
|
549
|
+
- `createHolidayCalendar(specs)` — fixed-date and computed holidays.
|
|
550
|
+
- `isHoliday(cal, value)`, `nextHoliday(cal, value)`, `previousHoliday(cal, value)`, `holidaysBetween(cal, start, end)`.
|
|
551
|
+
|
|
552
|
+
### Serialization
|
|
553
|
+
|
|
554
|
+
- `parseISO(input)`, `formatISO(value)`.
|
|
555
|
+
- `parseRFC3339(input)`, `formatRFC3339(value)`.
|
|
556
|
+
- `parseRFC2822(input)`, `formatRFC2822(value)`.
|
|
557
|
+
- `parseHTTPDate(input)`, `formatHTTPDate(value)`.
|
|
558
|
+
- `parseSQL(input)`, `formatSQL(value)`.
|
|
559
|
+
- Epoch: `fromUnixSeconds`, `fromUnixMilliseconds`, `fromUnixMicroseconds`, `fromUnixNanoseconds`, `toUnixSeconds`, `toUnixMilliseconds`, `toUnixMicroseconds`, `toUnixNanoseconds`.
|
|
560
|
+
|
|
561
|
+
### Locale
|
|
562
|
+
|
|
563
|
+
- `registerLocale(locale, vocab)` — register extended vocabulary (months, weekdays, day periods, quarters, eras, ordinals, duration units, relative-time language).
|
|
564
|
+
- `getLocale(locale)`, `hasLocale(locale)`.
|
|
565
|
+
- `registerLocaleVocab(locale, vocab)` — base vocabulary (months/weekdays/day periods) only. See [Locale guide](#locale-guide) below.
|
|
566
|
+
|
|
567
|
+
### Numbering systems
|
|
568
|
+
|
|
569
|
+
- `convertDigits(s, system)` — ASCII digits to a locale's native digits.
|
|
570
|
+
- `convertDigitsToAscii(s, system)` — inverse.
|
|
571
|
+
- `SUPPORTED_NUMBERING_SYSTEMS` — set of supported system names.
|
|
572
|
+
|
|
573
|
+
### Configuration
|
|
574
|
+
|
|
575
|
+
- `createConfig(overrides?)` — frozen config with locale/calendar/timezone/numberingSystem/weekRules/rounding/disambiguation/overflow/parseLenient/durationShowZeroValues.
|
|
576
|
+
- `mergeWithConfig(config, perCall)` — fold config defaults into per-call options.
|
|
577
|
+
|
|
578
|
+
### Extensibility
|
|
579
|
+
|
|
580
|
+
- `createFormatter(options?)` — create a formatter with custom tokens (overrides built-ins of the same name).
|
|
581
|
+
|
|
582
|
+
### Natural-language parsing
|
|
583
|
+
|
|
584
|
+
- `parseRelative(input, reference, options?)` — built-in EN/ES/FR/DE grammars (see [parseRelative](#parserelative-natural-language-date-parsing) above).
|
|
585
|
+
- `registerRelativeGrammar(grammar)` — add a new language.
|
|
586
|
+
|
|
587
|
+
### IDE tooling data
|
|
588
|
+
|
|
589
|
+
- `getAutocompleteData()` — token autocomplete entries with family grouping.
|
|
590
|
+
- `getHoverDocs()` — per-token hover documentation.
|
|
591
|
+
- `getInlineDiagnostics(formatStr)` — diagnostics with position + suggested fixes.
|
|
592
|
+
- `previewFormat(formatStr, sample?)` — live preview string.
|
|
593
|
+
- `getDocUrl(tokenName)` — anchor link into this README's [Token reference](#token-reference) section. (Pre-consolidation, this pointed into a standalone `docs/` folder — if you're on an older version, update accordingly.)
|
|
594
|
+
- `DAYJS_TO_TEMPORAL_FMT`, `DATE_FNS_TO_TEMPORAL_FMT` — token conversion hints.
|
|
595
|
+
|
|
596
|
+
### CLI
|
|
597
|
+
|
|
598
|
+
Run via `npm run cli` or `node scripts/cli.mjs`:
|
|
599
|
+
|
|
600
|
+
```sh
|
|
601
|
+
temporal-fmt format "2026-08-04T15:45:30" "yyyy-MM-dd HH:mm:ss"
|
|
602
|
+
temporal-fmt parse "yyyy-MM-dd" "2026-08-04"
|
|
603
|
+
temporal-fmt inspect "MMMM d, yyyy 'at' h:mm a"
|
|
604
|
+
temporal-fmt validate "yyyy-MM-dd HH:mm:ss"
|
|
605
|
+
temporal-fmt translate dayjs "YYYY-MM-DD HH:mm:ss"
|
|
606
|
+
```
|
|
607
|
+
|
|
608
|
+
## Token reference
|
|
609
|
+
|
|
610
|
+
Beyond the format/parse/example columns in the [Tokens](#tokens) table above, every token carries structured metadata — round-trip safety, locale sensitivity, which Temporal types it works on — accessible via `tokenInfo(name)` or `TOKEN_METADATA`.
|
|
611
|
+
|
|
612
|
+
### Round-trip safety by family
|
|
613
|
+
|
|
614
|
+
| Family | Tokens | Round-trip safe | Notes |
|
|
615
|
+
|--------|--------|------------------|-------|
|
|
616
|
+
| Year | `yyyy` | yes | Preserves sign for BCE. |
|
|
617
|
+
| Year | `yy` | no | Century is lost — `parse` re-derives it via the `00–68`/`69–99` rule documented above, which isn't guaranteed to match the original century. |
|
|
618
|
+
| Month | `MMMM`, `MMM`, `MM`, `M` | yes | Numeric and name forms all round-trip; only `MMMM`/`MMM` are locale-sensitive. |
|
|
619
|
+
| Day | `dd`, `d` | yes | |
|
|
620
|
+
| Day | `do` | no | Format-only — the "st"/"nd"/"rd"/"th" suffix isn't parseable back out. |
|
|
621
|
+
| Weekday | `EEEE`, `EEE` | yes | Cross-checked against the parsed date, so a mismatched weekday throws rather than silently round-tripping wrong. |
|
|
622
|
+
| ISO week | `ww`, `RRRR` | n/a | Format-only — a week number alone can't reconstruct a specific date. |
|
|
623
|
+
|
|
624
|
+
### Format-only tokens (parse rejects)
|
|
625
|
+
|
|
626
|
+
`format()` accepts these; `parse()` throws a clear, descriptive error if you try to use them for parsing:
|
|
627
|
+
|
|
628
|
+
- `do` — ordinal suffix isn't structurally distinguishable from adjacent literal text.
|
|
629
|
+
- `ww`, `RRRR` — week alone can't reconstruct a date without a disambiguator (a weekday, or the year+month+day).
|
|
630
|
+
|
|
631
|
+
Run `analyzeFormat(formatStr).warnings` to catch these statically before you hit the runtime error. The ESLint plugin (see [Related tools](#related-tools)) surfaces the same check as a `formatOnlyToken` diagnostic.
|
|
632
|
+
|
|
633
|
+
### Inspecting metadata at runtime
|
|
634
|
+
|
|
635
|
+
```js
|
|
636
|
+
import { TOKEN_METADATA, tokenInfo, listTokens } from 'temporal-fmt';
|
|
637
|
+
|
|
638
|
+
tokenInfo('yyyy');
|
|
639
|
+
// {
|
|
640
|
+
// meaning: 'Four-digit year (preserves sign for BCE; no truncation).',
|
|
641
|
+
// formatCapable: true,
|
|
642
|
+
// parseCapable: true,
|
|
643
|
+
// localeSensitive: false,
|
|
644
|
+
// calendarSensitive: true,
|
|
645
|
+
// timezoneSensitive: false,
|
|
646
|
+
// supportedTypes: ['PlainDate', 'PlainDateTime', 'ZonedDateTime', 'PlainYearMonth'],
|
|
647
|
+
// roundTripSafe: true,
|
|
648
|
+
// }
|
|
649
|
+
```
|
|
650
|
+
|
|
651
|
+
This metadata is the same source of truth the ESLint plugin and the codemod consume, so `tokenInfo()`/`listTokens()` output won't drift from what those tools actually enforce.
|
|
652
|
+
|
|
653
|
+
## Parsing: additional detail
|
|
654
|
+
|
|
655
|
+
The [Parsing a string](#parsing-a-string) section above covers the core contract. A few more things that come up in practice:
|
|
656
|
+
|
|
657
|
+
**Return type depends on which tokens are present:**
|
|
658
|
+
|
|
659
|
+
| Tokens present | Result type |
|
|
660
|
+
|---|---|
|
|
661
|
+
| year + month + day only | `Temporal.PlainDate` |
|
|
662
|
+
| time fields only (hour, minute, second) | `Temporal.PlainTime` |
|
|
663
|
+
| full date + time, no zone | `Temporal.PlainDateTime` |
|
|
664
|
+
| any of the above + `zzz` or an offset token | `Temporal.ZonedDateTime` |
|
|
665
|
+
|
|
666
|
+
**`safeParse` and `tryParse`** — for input you don't trust and don't want to wrap in try/catch:
|
|
667
|
+
|
|
668
|
+
```js
|
|
669
|
+
import { safeParse, tryParse } from 'temporal-fmt';
|
|
670
|
+
|
|
671
|
+
const r = safeParse('yyyy-MM-dd', userInput);
|
|
672
|
+
if (r.ok) {
|
|
673
|
+
console.log(r.value.toString());
|
|
674
|
+
} else {
|
|
675
|
+
console.log(r.error.code); // 'INVALID_DATE' / 'PARSE_MISMATCH' / ...
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
const v = tryParse('yyyy-MM-dd', userInput);
|
|
679
|
+
if (v) { /* ... */ }
|
|
680
|
+
```
|
|
681
|
+
|
|
682
|
+
**`parseToParts`** returns matched token groups and positions before any Temporal value gets constructed — useful for building a custom result type or doing your own cross-checks:
|
|
683
|
+
|
|
684
|
+
```js
|
|
685
|
+
parseToParts('yyyy-MM-dd HH:mm', '2026-08-04 15:45');
|
|
686
|
+
// → [
|
|
687
|
+
// { token: 'yyyy', raw: '2026', position: 0 },
|
|
688
|
+
// { token: 'MM', raw: '08', position: 5 },
|
|
689
|
+
// { token: 'dd', raw: '04', position: 8 },
|
|
690
|
+
// { token: 'HH', raw: '15', position: 11 },
|
|
691
|
+
// { token: 'mm', raw: '45', position: 14 },
|
|
692
|
+
// ]
|
|
693
|
+
```
|
|
694
|
+
|
|
695
|
+
**Calendar-aware parsing** — a locale with a `-u-ca-` extension parses into that calendar directly:
|
|
696
|
+
|
|
697
|
+
```js
|
|
698
|
+
parse('yyyy-MM-dd', '5784-05-10', { locale: 'en-u-ca-hebrew' });
|
|
699
|
+
// → Temporal.PlainDate with calendarId 'hebrew'
|
|
700
|
+
```
|
|
701
|
+
|
|
702
|
+
## Error reference
|
|
703
|
+
|
|
704
|
+
Every error thrown by `temporal-fmt` is either a plain `Error` with a descriptive message (the legacy throw sites in `parse()` and `format()`) or a `TemporalFmtError` subclass (the typed-error surface exposed via `safeParse()` and `tryParse()`).
|
|
705
|
+
|
|
706
|
+
### Typed error classes
|
|
707
|
+
|
|
708
|
+
All inherit from `TemporalFmtError`, which carries structured fields: `code`, `input`, `format`, `token`, `position`, `expected`, `actual`, `reason`.
|
|
709
|
+
|
|
710
|
+
| Class | Code | When it fires |
|
|
711
|
+
|-------|------|---------------|
|
|
712
|
+
| `FormatSyntaxError` | `FORMAT_SYNTAX_ERROR` | Unterminated quote, format string exceeds length cap, other syntax issues. |
|
|
713
|
+
| `UnknownTokenError` | `UNKNOWN_TOKEN` | An unrecognized letter run was encountered. |
|
|
714
|
+
| `ParseMismatchError` | `PARSE_MISMATCH` | Input doesn't match the format's shape; generic catch-all. |
|
|
715
|
+
| `InvalidDateError` | `INVALID_DATE` | Date is structurally valid but doesn't exist (Feb 30), weekday/quarter contradicts date, etc. |
|
|
716
|
+
| `InvalidTimeError` | `INVALID_TIME` | Time is out of range (hour 25, etc.). |
|
|
717
|
+
| `InvalidOffsetError` | `INVALID_OFFSET` | Offset is malformed or out of IANA range (-12:00 to +14:00). |
|
|
718
|
+
| `InvalidTimeZoneError` | `INVALID_TIME_ZONE` | Time zone isn't a recognized IANA name or fixed offset. |
|
|
719
|
+
| `InvalidCalendarError` | `INVALID_CALENDAR` | Calendar isn't supported. |
|
|
720
|
+
| `AmbiguousInputError` | `AMBIGUOUS_INPUT` | Input has more than one valid reading (e.g. "Md" against "121"). |
|
|
721
|
+
| `InvalidLocaleError` | `INVALID_LOCALE` | Locale isn't a valid BCP-47 tag, or the numbering system isn't supported. |
|
|
722
|
+
| `InvalidDurationError` | `INVALID_DURATION` | Duration string doesn't match the ISO 8601 grammar, or a field value is non-finite. |
|
|
723
|
+
|
|
724
|
+
`safeParse()` classifies the underlying throw into the matching `TemporalFmtError` subclass internally — see `src/errors.ts` if you need the exact classification logic.
|
|
725
|
+
|
|
726
|
+
### Common error patterns
|
|
727
|
+
|
|
728
|
+
**"no valid pattern matches the format string and input shape"** — the input doesn't match the format string at all. Usually a missing separator, wrong digit count for a fixed-width token, or a `zzz` token that captured something that isn't a real IANA zone id.
|
|
729
|
+
|
|
730
|
+
**"token X requires Y, which this Temporal object doesn't have"** — you used a token that reads a field the value doesn't carry. Common case: `format(plainDate, 'HH:mm')` — `PlainDate` has no hour field. Switch to `PlainDateTime` or use a date-only format string.
|
|
731
|
+
|
|
732
|
+
**"format string mixes 'yyyy' and 'yy' year representations"** — don't mix the two year tokens in the same format string. Pick one.
|
|
733
|
+
|
|
734
|
+
**"format string has an incomplete date — year, month, and day tokens must all be present together"** — a partial date (year-only, say) isn't constructible as a Temporal value. Add the missing tokens, or use a different format.
|
|
735
|
+
|
|
736
|
+
**"X is ambiguous — N different ways to read tokens Y are all individually valid"** — adjacent unpadded numeric tokens with no separator (e.g. `Md` against `121`) can split multiple ways. Add a separator (`M-d`), use padded forms (`MM-dd`), or opt into `{ lenient: true }`.
|
|
737
|
+
|
|
738
|
+
**"offset hours X in 'Y' out of range (max 14 — Kiritimati, Line Islands is +14:00)"** — the offset's hour component exceeds the IANA-supported range of -12 to +14.
|
|
739
|
+
|
|
740
|
+
**"X has no such wall-clock time on this date — it falls in a DST gap"** — the input describes a wall-clock time that doesn't exist (a spring-forward gap). Pick a different time, or pass `{ disambiguation: 'compatible' }` to let Temporal choose an instant.
|
|
741
|
+
|
|
742
|
+
**"has both a 'zzz' zone (X) and an offset token (Y), but the zone's actual offset at this date/time is Z, not Y"** — the format string asks for both a zone name and an explicit offset, and the parsed offset disagrees with the zone's actual offset at that instant. Keep them consistent in the input.
|
|
743
|
+
|
|
744
|
+
## Locale guide
|
|
745
|
+
|
|
746
|
+
The [Locale support](#locale-support) section above covers the common case — passing a `locale` option to `format`/`parse`. This section covers registering your own vocabulary.
|
|
747
|
+
|
|
748
|
+
`temporal-fmt` uses `Intl.DateTimeFormat` as the default source for locale-aware token output (`MMMM`, `MMM`, `EEEE`, `EEE`, `a`). For locales Intl doesn't cover well — or where you want different vocabulary — register a custom one.
|
|
749
|
+
|
|
750
|
+
**`registerLocaleVocab` (base)** — months, weekdays, and day periods only:
|
|
751
|
+
|
|
752
|
+
```js
|
|
753
|
+
import { registerLocaleVocab } from 'temporal-fmt';
|
|
754
|
+
|
|
755
|
+
registerLocaleVocab('en-u-ca-hebrew-leap', {
|
|
756
|
+
monthLong: ['Nisan', 'Iyar', 'Sivan', 'Tammuz', 'Av', 'Elul', 'Tishrei', 'Marcheshvan', 'Kislev', 'Tevet', 'Shevat', 'Adar I', 'Adar II'],
|
|
757
|
+
monthShort: ['Nis', 'Iyy', 'Siv', 'Tam', 'Av', 'Elu', 'Tish', 'Chesh', 'Kis', 'Tev', 'Shv', 'Ad1', 'Ad2'],
|
|
758
|
+
weekdayLong: ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'],
|
|
759
|
+
weekdayShort: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
|
|
760
|
+
dayPeriod: ['AM', 'PM'],
|
|
761
|
+
});
|
|
762
|
+
```
|
|
763
|
+
|
|
764
|
+
After registration, `format()` and `parse()` use the registered vocab for that locale key.
|
|
765
|
+
|
|
766
|
+
**`registerLocale` (extended)** — for vocabulary beyond months/weekdays/day-periods: quarters, eras, ordinals, duration units, relative-time language:
|
|
767
|
+
|
|
768
|
+
```js
|
|
769
|
+
import { registerLocale } from 'temporal-fmt';
|
|
770
|
+
|
|
771
|
+
registerLocale('test-locale-1', {
|
|
772
|
+
// base vocab (required)
|
|
773
|
+
monthLong: [/* 12 entries */],
|
|
774
|
+
monthShort: [/* 12 entries */],
|
|
775
|
+
weekdayLong: [/* 7 entries */],
|
|
776
|
+
weekdayShort: [/* 7 entries */],
|
|
777
|
+
dayPeriod: ['AM', 'PM'],
|
|
778
|
+
// extended vocab (optional)
|
|
779
|
+
quartersLong: ['First', 'Second', 'Third', 'Fourth'],
|
|
780
|
+
quartersShort: ['Q1', 'Q2', 'Q3', 'Q4'],
|
|
781
|
+
erasLong: ['BCE', 'CE'],
|
|
782
|
+
erasShort: ['BC', 'AD'],
|
|
783
|
+
ordinals: ['st', 'nd', 'rd', 'th'],
|
|
784
|
+
durationUnits: { years: ['year', 'years'] /* , ... */ },
|
|
785
|
+
relativeTime: { past: 'ago', future: 'in', now: 'now' },
|
|
786
|
+
});
|
|
787
|
+
```
|
|
788
|
+
|
|
789
|
+
**Locale fallback** — `getLocale(locale)` returns the extended vocab if one's registered, otherwise the Intl-derived base vocab. Fallback is deterministic: the canonical locale key (lowercased via `Intl.Locale`) is looked up, and Intl is used if there's no entry.
|
|
790
|
+
|
|
791
|
+
**Deterministic output** — all locale options are per-call. `registerLocaleVocab` and `registerLocale` are the only global mutation points, and they invalidate the cache entry for the affected locale so subsequent calls pick up the new vocab immediately. Built-in behavior stays deterministic regardless of what's registered elsewhere in your app.
|
|
792
|
+
|
|
793
|
+
## Calendar guide
|
|
794
|
+
|
|
795
|
+
`temporal-fmt`'s calendar utilities operate on Temporal's calendar-aware types. By default, the helpers assume the `iso8601` (Gregorian) calendar — that's what `TemporalLike` fields carry for the overwhelming majority of callers.
|
|
796
|
+
|
|
797
|
+
**Gregorian-only helpers (documented limitation).** The following use Gregorian arithmetic and will produce wrong results on non-Gregorian calendars (Hebrew, Islamic, etc.):
|
|
798
|
+
|
|
799
|
+
- `daysInMonth`, `daysInYear`, `isLeapYear` — Gregorian month lengths and leap year rules.
|
|
800
|
+
- `monthsInYear` — returns 12 unconditionally.
|
|
801
|
+
- `isLeapMonth` — returns false unconditionally.
|
|
802
|
+
- `dayOfYear` — Gregorian day-of-year.
|
|
803
|
+
- `weekOfYear`, `weekYear` — ISO 8601 week numbering.
|
|
804
|
+
|
|
805
|
+
For non-Gregorian calendars, pass the value to `Temporal.PlainDate` directly and use its own calendar-aware methods instead:
|
|
806
|
+
|
|
807
|
+
```js
|
|
808
|
+
const pd = Temporal.PlainDate.from('5784-05-10[u-ca=hebrew]');
|
|
809
|
+
console.log(pd.daysInMonth); // 30 (Sivan)
|
|
810
|
+
console.log(pd.monthsInYear); // 13 (leap year)
|
|
811
|
+
```
|
|
812
|
+
|
|
813
|
+
**Locale-aware tokens ARE calendar-aware**, unlike the helpers above. `MMMM`, `MMM`, `EEEE`, `EEE`, `a` go through `Intl.DateTimeFormat`, which respects the value's `calendarId`:
|
|
814
|
+
|
|
815
|
+
```js
|
|
816
|
+
const hebrewDate = Temporal.PlainDate.from('5784-05-10[u-ca=hebrew]');
|
|
817
|
+
format(hebrewDate, 'MMMM d, yyyy');
|
|
818
|
+
// → "Sivan 10, 5784"
|
|
819
|
+
```
|
|
820
|
+
|
|
821
|
+
Numeric tokens (`yyyy`, `MM`, `dd`) read straight off the object's ISO fields — calendar-specific in the sense that the underlying Temporal value carries calendar-specific field values, but the formatting logic itself is calendar-agnostic.
|
|
822
|
+
|
|
823
|
+
**Custom calendar vocabulary.** For calendars Intl doesn't cover (Hebrew leap months, for instance), register one via `registerLocaleVocab` — see [Locale guide](#locale-guide) above for the full vocab surface and validation rules.
|
|
824
|
+
|
|
825
|
+
## Migration guide
|
|
826
|
+
|
|
827
|
+
`temporal-fmt` is designed to make migration from Day.js and date-fns straightforward. The token grammar is largely compatible, and the codemod automates the bulk of the work.
|
|
828
|
+
|
|
829
|
+
### Automated migration
|
|
830
|
+
|
|
831
|
+
The `temporal-fmt-codemod` package includes AST transforms for Day.js and date-fns:
|
|
832
|
+
|
|
833
|
+
```sh
|
|
834
|
+
npx temporal-fmt-codemod --source=dayjs path/to/src
|
|
835
|
+
npx temporal-fmt-codemod --source=date-fns path/to/src
|
|
836
|
+
```
|
|
837
|
+
|
|
838
|
+
It's conservative: it only transforms call sites where the format string is a plain string literal, and only rewrites tokens with known-safe mappings. Unmappable tokens leave a `TODO(temporal-fmt-codemod)` comment.
|
|
839
|
+
|
|
840
|
+
For a one-off translation without running the full codemod, use the CLI:
|
|
841
|
+
|
|
842
|
+
```sh
|
|
843
|
+
npx temporal-fmt translate dayjs "YYYY-MM-DD HH:mm:ss"
|
|
844
|
+
# → "yyyy-MM-dd HH:mm:ss"
|
|
845
|
+
```
|
|
846
|
+
|
|
847
|
+
### Manual migration: token mapping
|
|
848
|
+
|
|
849
|
+
Most date-fns/Day.js tokens are identical to `temporal-fmt` tokens. The differences:
|
|
850
|
+
|
|
851
|
+
| Source (Day.js / date-fns) | temporal-fmt | Notes |
|
|
852
|
+
|---|---|---|
|
|
853
|
+
| `YYYY` | `yyyy` | Lowercase in temporal-fmt |
|
|
854
|
+
| `YY` | `yy` | Same |
|
|
855
|
+
| `MMMM`, `MMM`, `MM`, `M` | same | Identical |
|
|
856
|
+
| `DD`, `D` | `dd`, `d` | Lowercase in temporal-fmt |
|
|
857
|
+
| `dddd` | `EEEE` | Long weekday |
|
|
858
|
+
| `ddd` | `EEE` | Short weekday |
|
|
859
|
+
| `HH`, `H`, `mm`, `m`, `ss`, `s` | same | Identical |
|
|
860
|
+
| `A`, `a` | `a` | Always lowercase in temporal-fmt |
|
|
861
|
+
| `Z` | `XXX` | Numeric UTC offset with colon, Z for UTC |
|
|
862
|
+
| `ZZ` | `XX` | Numeric UTC offset no colon |
|
|
863
|
+
| `X` | (not supported) | Unix timestamp — convert via `fromUnixSeconds` instead |
|
|
864
|
+
| `x` | (not supported) | Unix ms timestamp — convert via `fromUnixMilliseconds` |
|
|
865
|
+
| `P` | (not supported) | Localized long format — write the format string explicitly |
|
|
866
|
+
|
|
867
|
+
### Manual migration: API mapping
|
|
868
|
+
|
|
869
|
+
| Day.js | date-fns | temporal-fmt |
|
|
870
|
+
|---|---|---|
|
|
871
|
+
| `dayjs(str).format(fmt)` | `format(date, fmt)` | `format(temporal, fmt)` |
|
|
872
|
+
| `dayjs(str)` (parse) | `parseISO(str)` | `parseISO(str)` |
|
|
873
|
+
| `dayjs().add(n, 'day')` | `addDays(date, n)` | `add(date, n, 'days')` or `addDays(date, n)` |
|
|
874
|
+
| `dayjs().diff(other, 'day')` | `differenceInDays(a, b)` | `differenceInDays(a, b)` |
|
|
875
|
+
| `dayjs().isBefore(other)` | `isBefore(date, other)` | `isBefore(date, other)` |
|
|
876
|
+
| `dayjs().isAfter(other)` | `isAfter(date, other)` | `isAfter(date, other)` |
|
|
877
|
+
| `dayjs.duration(...)` | `intervalToDuration(...)` | `Temporal.Duration.from(...)` |
|
|
878
|
+
| `dayjs().isToday()` | `isToday(date)` | `isToday(date)` |
|
|
879
|
+
| `dayjs().isYesterday()` | `isYesterday(date)` | `isYesterday(date)` |
|
|
880
|
+
| `dayjs().isTomorrow()` | `isTomorrow(date)` | `isTomorrow(date)` |
|
|
881
|
+
|
|
882
|
+
### Key behavioral differences
|
|
883
|
+
|
|
884
|
+
1. **Strict parsing.** `temporal-fmt` throws on ambiguous input by default. Day.js silently picks one reading. For ambiguous glued numeric tokens (`Md` against `121`, say), either add separators or use `{ lenient: true }`.
|
|
885
|
+
2. **Cross-field validation.** `temporal-fmt` cross-checks weekday, quarter, and offset against the parsed date. Day.js doesn't — a `Monday` label that disagrees with the actual date will throw here.
|
|
886
|
+
3. **No silent defaults.** `temporal-fmt` doesn't fall back to "now" when input is missing. Pass an explicit value.
|
|
887
|
+
4. **Type-preserving.** `format(plainDate, 'HH:mm')` throws — `PlainDate` has no hour field. Day.js silently uses 0. Pass a `PlainDateTime`, or use a date-only format.
|
|
888
|
+
5. **Temporal-native.** The library operates on Temporal types (`PlainDate`, `PlainDateTime`, `ZonedDateTime`, etc.), not on JS `Date`. Convert at the boundary:
|
|
889
|
+
|
|
890
|
+
```js
|
|
891
|
+
import { Temporal } from 'temporal-polyfill';
|
|
892
|
+
const pd = Temporal.PlainDate.from(jsDate.toISOString().slice(0, 10));
|
|
893
|
+
const formatted = format(pd, 'yyyy-MM-dd');
|
|
894
|
+
```
|
|
895
|
+
|
|
896
|
+
### Common migration patterns
|
|
897
|
+
|
|
898
|
+
**CSV/log timestamp parsing:**
|
|
899
|
+
|
|
900
|
+
```js
|
|
901
|
+
// Before (Day.js)
|
|
902
|
+
const d = dayjs(line, 'YYYY-MM-DD HH:mm:ss');
|
|
903
|
+
// After
|
|
904
|
+
const d = parse('yyyy-MM-dd HH:mm:ss', line);
|
|
905
|
+
```
|
|
906
|
+
|
|
907
|
+
**Locale-aware formatting:**
|
|
908
|
+
|
|
909
|
+
```js
|
|
910
|
+
// Before
|
|
911
|
+
dayjs(date).locale('fr').format('MMMM D, YYYY');
|
|
912
|
+
// After
|
|
913
|
+
format(date, 'MMMM d, yyyy', { locale: 'fr-FR' });
|
|
914
|
+
// → "août 4, 2026"
|
|
915
|
+
```
|
|
916
|
+
|
|
917
|
+
**Relative time:**
|
|
918
|
+
|
|
919
|
+
```js
|
|
920
|
+
// Before
|
|
921
|
+
dayjs(date).fromNow(); // "3 days ago"
|
|
922
|
+
// After
|
|
923
|
+
formatRelativeToNow(date); // "3 days ago"
|
|
924
|
+
```
|
|
925
|
+
|
|
926
|
+
**Date arithmetic:**
|
|
927
|
+
|
|
928
|
+
```js
|
|
929
|
+
// Before
|
|
930
|
+
dayjs(date).add(7, 'day');
|
|
931
|
+
// After
|
|
932
|
+
add(date, 7, 'days');
|
|
933
|
+
// or: addDays(date, 7)
|
|
934
|
+
```
|
|
935
|
+
|
|
936
|
+
### Things that don't migrate cleanly
|
|
937
|
+
|
|
938
|
+
- Day.js's `dayjs.extend(customParseFormat)` plugin behavior — `temporal-fmt`'s parse is strict, the plugin is lenient. Audit any callers relying on lenient parsing.
|
|
939
|
+
- Day.js's mutable locale registration (`dayjs.locale('fr')`) — `temporal-fmt` uses per-call `locale` options, no global mutation.
|
|
940
|
+
- date-fns's `format` with a locale object parameter — `temporal-fmt` uses BCP-47 strings, not locale objects.
|
|
941
|
+
- Timezone-aware formatting via `dayjs-timezone` — use `Temporal.ZonedDateTime` and the `zzz`/`XXX` tokens instead.
|
|
942
|
+
|
|
943
|
+
### Running both during migration
|
|
944
|
+
|
|
945
|
+
Both libraries can coexist. Wrap migration in a feature flag:
|
|
946
|
+
|
|
947
|
+
```js
|
|
948
|
+
import dayjs from 'dayjs';
|
|
949
|
+
import { format as fmtTemporal } from 'temporal-fmt';
|
|
950
|
+
|
|
951
|
+
function formatDate(date, formatStr, opts) {
|
|
952
|
+
if (opts?.useTemporal) {
|
|
953
|
+
return fmtTemporal(date, formatStr, opts);
|
|
954
|
+
}
|
|
955
|
+
return dayjs(date).format(formatStr);
|
|
956
|
+
}
|
|
957
|
+
```
|
|
958
|
+
|
|
959
|
+
Run the codemod per-file when ready, then drop the wrapper.
|
|
960
|
+
|
|
961
|
+
## Other guides
|
|
962
|
+
|
|
963
|
+
A few narrower topics — business calendars, intervals, recurrence, durations, timezones, serialization, performance, security, and the ESLint plugin/codemod internals — don't have write-ups of their own yet beyond what's covered above and in the [API reference](#api-reference). The test files (`test/*.test.js`) are the best source for concrete usage of any of these; each one is effectively a usage guide for its corresponding module.
|
|
964
|
+
|
|
415
965
|
## Known limitations
|
|
416
966
|
|
|
417
967
|
- Numeral systems are always Western digits — see [Locale support](#locale-support).
|
package/calendar.d.cts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './dist/calendarUtils';
|
package/calendar.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './dist/calendarUtils';
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { type TemporalLike } from './tokens.cjs';
|
|
2
|
+
import { type TokenMetadata, type TemporalType } from './tokenMetadata.cjs';
|
|
3
|
+
import { type Piece } from './tokenize.cjs';
|
|
4
|
+
export type { TokenMetadata, TemporalType } from './tokenMetadata.cjs';
|
|
5
|
+
export type { Piece } from './tokenize.cjs';
|
|
6
|
+
export declare function tokenizeFormat(formatStr: string): Piece[];
|
|
7
|
+
export declare function listTokens(): Array<{
|
|
8
|
+
name: string;
|
|
9
|
+
metadata: TokenMetadata;
|
|
10
|
+
}>;
|
|
11
|
+
export declare function tokenInfo(name: string): TokenMetadata | undefined;
|
|
12
|
+
export interface AnalyzedToken {
|
|
13
|
+
name: string;
|
|
14
|
+
position: number;
|
|
15
|
+
metadata: TokenMetadata;
|
|
16
|
+
}
|
|
17
|
+
export interface FormatAnalysis {
|
|
18
|
+
tokens: AnalyzedToken[];
|
|
19
|
+
requiredFields: string[];
|
|
20
|
+
compatibleTypes: TemporalType[];
|
|
21
|
+
parseable: boolean;
|
|
22
|
+
localeSensitive: boolean;
|
|
23
|
+
calendarSensitive: boolean;
|
|
24
|
+
timezoneSensitive: boolean;
|
|
25
|
+
ambiguous: boolean;
|
|
26
|
+
roundTripSafe: boolean;
|
|
27
|
+
warnings: Array<{
|
|
28
|
+
code: string;
|
|
29
|
+
message: string;
|
|
30
|
+
}>;
|
|
31
|
+
}
|
|
32
|
+
export declare function analyzeFormat(formatStr: string): FormatAnalysis;
|
|
33
|
+
export declare function explainFormat(formatStr: string): string;
|
|
34
|
+
export declare function isValidFormat(formatStr: string): boolean;
|
|
35
|
+
export declare function validateFormat(formatStr: string): FormatAnalysis;
|
|
36
|
+
export { TOKEN_METADATA, ALL_TOKEN_NAMES } from './tokenMetadata.cjs';
|
|
37
|
+
export { FORMAT_ONLY_TOKENS } from './pattern.cjs';
|
|
38
|
+
export declare function fieldForToken(token: string): keyof TemporalLike | undefined;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { type TemporalLike } from './tokens.js';
|
|
2
|
+
import { type TokenMetadata, type TemporalType } from './tokenMetadata.js';
|
|
3
|
+
import { type Piece } from './tokenize.js';
|
|
4
|
+
export type { TokenMetadata, TemporalType } from './tokenMetadata.js';
|
|
5
|
+
export type { Piece } from './tokenize.js';
|
|
6
|
+
export declare function tokenizeFormat(formatStr: string): Piece[];
|
|
7
|
+
export declare function listTokens(): Array<{
|
|
8
|
+
name: string;
|
|
9
|
+
metadata: TokenMetadata;
|
|
10
|
+
}>;
|
|
11
|
+
export declare function tokenInfo(name: string): TokenMetadata | undefined;
|
|
12
|
+
export interface AnalyzedToken {
|
|
13
|
+
name: string;
|
|
14
|
+
position: number;
|
|
15
|
+
metadata: TokenMetadata;
|
|
16
|
+
}
|
|
17
|
+
export interface FormatAnalysis {
|
|
18
|
+
tokens: AnalyzedToken[];
|
|
19
|
+
requiredFields: string[];
|
|
20
|
+
compatibleTypes: TemporalType[];
|
|
21
|
+
parseable: boolean;
|
|
22
|
+
localeSensitive: boolean;
|
|
23
|
+
calendarSensitive: boolean;
|
|
24
|
+
timezoneSensitive: boolean;
|
|
25
|
+
ambiguous: boolean;
|
|
26
|
+
roundTripSafe: boolean;
|
|
27
|
+
warnings: Array<{
|
|
28
|
+
code: string;
|
|
29
|
+
message: string;
|
|
30
|
+
}>;
|
|
31
|
+
}
|
|
32
|
+
export declare function analyzeFormat(formatStr: string): FormatAnalysis;
|
|
33
|
+
export declare function explainFormat(formatStr: string): string;
|
|
34
|
+
export declare function isValidFormat(formatStr: string): boolean;
|
|
35
|
+
export declare function validateFormat(formatStr: string): FormatAnalysis;
|
|
36
|
+
export { TOKEN_METADATA, ALL_TOKEN_NAMES } from './tokenMetadata.js';
|
|
37
|
+
export { FORMAT_ONLY_TOKENS } from './pattern.js';
|
|
38
|
+
export declare function fieldForToken(token: string): keyof TemporalLike | undefined;
|