@vielzeug/tempo 2.2.0 → 3.0.0
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 +7 -19
- package/dist/format.cjs +1 -1
- package/dist/format.cjs.map +1 -1
- package/dist/format.d.ts +3 -0
- package/dist/format.d.ts.map +1 -1
- package/dist/format.js +76 -66
- package/dist/format.js.map +1 -1
- package/dist/range.cjs +1 -1
- package/dist/range.cjs.map +1 -1
- package/dist/range.d.ts.map +1 -1
- package/dist/range.js +13 -5
- package/dist/range.js.map +1 -1
- package/dist/tempo.cjs +1 -1
- package/dist/tempo.cjs.map +1 -1
- package/dist/tempo.iife.js +1 -1
- package/dist/tempo.iife.js.map +1 -1
- package/dist/tempo.js +1 -1
- package/dist/tempo.js.map +1 -1
- package/dist/types.d.ts +1 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +6 -6
package/README.md
CHANGED
|
@@ -1,38 +1,26 @@
|
|
|
1
1
|
# @vielzeug/tempo
|
|
2
2
|
|
|
3
|
-
Temporal utilities
|
|
4
|
-
|
|
5
|
-
`@vielzeug/tempo` ships ESM and CJS output. It depends on `@js-temporal/polyfill` and re-exports `Temporal` so all consumers use one implementation and version.
|
|
3
|
+
> Temporal-powered date utilities
|
|
6
4
|
|
|
7
5
|
## Installation
|
|
8
6
|
|
|
9
7
|
```sh
|
|
10
8
|
pnpm add @vielzeug/tempo
|
|
9
|
+
npm install @vielzeug/tempo
|
|
10
|
+
yarn add @vielzeug/tempo
|
|
11
11
|
```
|
|
12
12
|
|
|
13
13
|
## Quick Start
|
|
14
14
|
|
|
15
|
-
Parse a wall-clock input, resolve it to an instant, then format its zoned display value.
|
|
16
|
-
|
|
17
15
|
```ts
|
|
18
|
-
import { format,
|
|
16
|
+
import { format, parse, shift } from '@vielzeug/tempo';
|
|
19
17
|
|
|
20
18
|
const local = parse('2026-03-21T10:30:00', { as: 'plainDateTime' });
|
|
21
|
-
const
|
|
22
|
-
const reminder = shift(meeting, { minutes: -15 }, { timeZone: 'America/New_York' });
|
|
19
|
+
const reminder = shift(local, { minutes: -15 }, { timeZone: 'America/New_York' });
|
|
23
20
|
|
|
24
|
-
console.log(format(
|
|
21
|
+
console.log(format(reminder, { locale: 'en-US', pattern: 'short' }));
|
|
25
22
|
```
|
|
26
23
|
|
|
27
|
-
## API Rules
|
|
28
|
-
|
|
29
|
-
- `parse()` always requires `{ as }`; Tempo does not auto-detect strings.
|
|
30
|
-
- `timeZone` is required whenever plain values become instants or zoned values.
|
|
31
|
-
- `disambiguation` controls DST overlaps and gaps.
|
|
32
|
-
- `difference()`, `contains()`, `clamp()`, and `classifyExpiry()` take object inputs.
|
|
33
|
-
- `classifyExpiry()` accepts fixed elapsed-time thresholds only; months and years are rejected.
|
|
34
|
-
- `Temporal` remains exported for advanced Temporal operations and type identity.
|
|
35
|
-
|
|
36
24
|
## Documentation
|
|
37
25
|
|
|
38
26
|
- [Overview](https://vielzeug.dev/tempo/)
|
|
@@ -43,4 +31,4 @@ console.log(format(inTimeZone(reminder, 'America/New_York'), { locale: 'en-US',
|
|
|
43
31
|
|
|
44
32
|
## License
|
|
45
33
|
|
|
46
|
-
MIT © Helmuth Saatkamp — part of Vielzeug.
|
|
34
|
+
MIT © [Helmuth Saatkamp](https://github.com/helmuthdu) — part of the [Vielzeug](https://github.com/helmuthdu/vielzeug) monorepo.
|
package/dist/format.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const e=require("./errors.cjs"),t=require("./_tz.cjs"),n=require("./_convert.cjs");let r=require("@js-temporal/polyfill");var i=128;function a(e,t,n){let r=e.get(t);if(r!==void 0)return r;if(e.size>=i){let t=e.keys().next().value;t!==void 0&&e.delete(t)}let a=n();return e.set(t,a),a}var o=new Map,s=new Map,c=new Map,l={"date-only":{dateStyle:`short`},long:{dateStyle:`full`,timeStyle:`long`},medium:{dateStyle:`medium`,timeStyle:`short`},short:{dateStyle:`short`,timeStyle:`short`},"time-only":{timeStyle:`short`}};function u(e){return JSON.stringify(Object.entries(e).filter(([,e])=>e!==void 0).sort(([e],[t])=>e.localeCompare(t)).map(([e,t])=>[e,String(t)]))}function d(e,t){let n=e.timeZone??t,r=e.locale;if(e.intl!==void 0)return a(o,`${String(r??``)}|intl|${n??``}|${u(e.intl)}`,()=>{let t=n===void 0?e.intl:{...e.intl,timeZone:n};return new Intl.DateTimeFormat(r,t)});let i=e.pattern??`medium`;return a(o,`${String(r??``)}|${i}|${n??``}`,()=>new Intl.DateTimeFormat(r,{...l[i],timeZone:n}))}function f(e){return a(s,`${String(e.locale??``)}|${e.numeric??`auto`}|${e.style??`long`}`,()=>new Intl.RelativeTimeFormat(e.locale,{numeric:e.numeric??`auto`,style:e.style??`long`}))}function p(e){let t=Intl;return t.DurationFormat?a(c,`${String(e.locale??``)}|${e.style??``}`,()=>new t.DurationFormat(e.locale,{style:e.style})):null}var m=60,h=3600,g=86400,_=604800,v=
|
|
1
|
+
const e=require("./errors.cjs"),t=require("./_tz.cjs"),n=require("./_convert.cjs");let r=require("@js-temporal/polyfill");var i=128;function a(e,t,n){let r=e.get(t);if(r!==void 0)return r;if(e.size>=i){let t=e.keys().next().value;t!==void 0&&e.delete(t)}let a=n();return e.set(t,a),a}var o=new Map,s=new Map,c=new Map,l={"date-only":{dateStyle:`short`},long:{dateStyle:`full`,timeStyle:`long`},medium:{dateStyle:`medium`,timeStyle:`short`},short:{dateStyle:`short`,timeStyle:`short`},"time-only":{timeStyle:`short`}};function u(e){return JSON.stringify(Object.entries(e).filter(([,e])=>e!==void 0).sort(([e],[t])=>e.localeCompare(t)).map(([e,t])=>[e,String(t)]))}function d(e,t){let n=e.timeZone??t,r=e.locale;if(e.intl!==void 0)return a(o,`${String(r??``)}|intl|${n??``}|${u(e.intl)}`,()=>{let t=n===void 0?e.intl:{...e.intl,timeZone:n};return new Intl.DateTimeFormat(r,t)});let i=e.pattern??`medium`;return a(o,`${String(r??``)}|${i}|${n??``}`,()=>new Intl.DateTimeFormat(r,{...l[i],timeZone:n}))}function f(e){return a(s,`${String(e.locale??``)}|${e.numeric??`auto`}|${e.style??`long`}`,()=>new Intl.RelativeTimeFormat(e.locale,{numeric:e.numeric??`auto`,style:e.style??`long`}))}function p(e){let t=Intl;return t.DurationFormat?a(c,`${String(e.locale??``)}|${e.style??``}`,()=>new t.DurationFormat(e.locale,{style:e.style})):null}var m=60,h=3600,g=86400,_=604800,v=[{scale:1,thresholdToPromote:m,unit:`second`},{scale:m,thresholdToPromote:h/m,unit:`minute`},{scale:h,thresholdToPromote:g/h,unit:`hour`},{scale:g,thresholdToPromote:_/g,unit:`day`},{scale:_,thresholdToPromote:1/0,unit:`week`}];function y(t){Number.isFinite(t)||e.fail(`formatRelative received a non-finite time difference.`);let n=Math.round(t);for(let{scale:e,thresholdToPromote:t,unit:r}of v){let i=Math.round(n/e);if(Math.abs(i)<t)return{unit:r,value:i}}return{unit:`week`,value:Math.round(n/_)}}function b(n,i,a){if(a)return t.validateTimeZone(a);let o=n instanceof r.Temporal.ZonedDateTime?n.timeZoneId:void 0,s=i instanceof r.Temporal.ZonedDateTime?i.timeZoneId:void 0;return o&&s&&o!==s&&e.fail(`formatRelative received ZonedDateTime inputs with different time zones. Pass options.timeZone explicitly.`),o??s??`UTC`}function x(e,t,i){let a=n.toZoned(e,{timeZone:i}),o=n.toZoned(t,{timeZone:i}),s=r.Temporal.ZonedDateTime.compare(a,o);if(s===0)return{unit:`second`,value:0};let c=s>0?a.since(o,{largestUnit:`year`}):o.since(a,{largestUnit:`year`}),l=s>0?1:-1;return c.years?{unit:`year`,value:c.years*l}:c.months?{unit:`month`,value:c.months*l}:y((e.epochMilliseconds-t.epochMilliseconds)/1e3)}var S=[`years`,`months`,`weeks`,`days`,`hours`,`minutes`,`seconds`,`milliseconds`,`microseconds`,`nanoseconds`];function C(e){let t=[];for(let n of S){let r=Math.abs(e[n]);r!==0&&t.push(`${r} ${r===1?n.slice(0,-1):n}`)}return t.length===0?`0 seconds`:t.join(`, `)}function w(t,n,i,a){if(i.timeZone)return i.timeZone;let o=t instanceof r.Temporal.ZonedDateTime?t.timeZoneId:void 0,s=n instanceof r.Temporal.ZonedDateTime?n.timeZoneId:void 0;return o&&s&&o!==s&&e.fail(`${a} received ZonedDateTime inputs with different time zones. Pass options.timeZone explicitly.`),o??s}function T(e,t={}){let i=t.timeZone??(e instanceof r.Temporal.ZonedDateTime?e.timeZoneId:void 0);return d(t,i).format(new Date(n.toInstant(e,{timeZone:i}).epochMilliseconds))}function E(e,t,r={}){let i=w(e,t,r,`formatRange`);return d(r,i).formatRange(new Date(n.toInstant(e,{timeZone:i}).epochMilliseconds),new Date(n.toInstant(t,{timeZone:i}).epochMilliseconds))}function D(e,t,r={}){let i=w(e,t,r,`formatRangeParts`);return d(r,i).formatRangeToParts(new Date(n.toInstant(e,{timeZone:i}).epochMilliseconds),new Date(n.toInstant(t,{timeZone:i}).epochMilliseconds))}function O(e,t={}){return n.toInstant(e,t).toString()}function k(e,r={}){let i=t.inferTimeZone(e,r);return n.toZoned(e,{timeZone:i}).toString()}function A(e,t={}){let n=t.base??r.Temporal.Now.instant(),{unit:i,value:a}=x(e,n,b(e,n,t.timeZone));return f(t).format(a,i)}function j(t){try{return r.Temporal.Duration.from(t)}catch(n){e.fail(`Invalid duration input: "${String(t)}". Expected an ISO 8601 duration string or Temporal.DurationLike.`,e.TempoInvalidInputError,{cause:n})}}function M(e,t={}){let n=j(e),r=p(t);return r?r.format(n):C(n)}function N(e,t={}){let i=t.timeZone??(e instanceof r.Temporal.ZonedDateTime?e.timeZoneId:void 0);return d(t,i).formatToParts(new Date(n.toInstant(e,{timeZone:i}).epochMilliseconds))}function P(e,t={}){let{unit:n,value:r}=e;return`${t.locale?new Intl.NumberFormat(t.locale).format(r):String(r)} ${r===1?n:`${n}s`}`}exports.format=T,exports.formatDuration=M,exports.formatInstant=O,exports.formatParts=N,exports.formatRange=E,exports.formatRangeParts=D,exports.formatRelative=A,exports.formatZoned=k,exports.humanize=P,exports.parseDuration=j;
|
|
2
2
|
//# sourceMappingURL=format.cjs.map
|
package/dist/format.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"format.cjs","names":[],"sources":["../src/format.ts"],"sourcesContent":["import { Temporal } from '@js-temporal/polyfill';\nimport { toInstant, toZoned } from './_convert';\nimport { inferTimeZone } from './_tz';\nimport { fail, TempoInvalidInputError } from './errors';\nimport type {\n DurationFormatOptions,\n FormatOptions,\n FormatPattern,\n RelativeFormatOptions,\n RelativeTimeInput,\n TimeDiffResult,\n TimeInput,\n TimeZoneOptions,\n} from './types';\n\n// ─── Formatter types ──────────────────────────────────────────────────────────\n\ntype DurationFormatter = { format(value: Temporal.Duration): string };\ntype DurationFormatterConstructor = new (\n locales?: Intl.LocalesArgument,\n options?: { style?: 'digital' | 'long' | 'narrow' | 'short' },\n) => DurationFormatter;\n\n// ─── Formatter caches ─────────────────────────────────────────────────────────\n\nconst FORMATTER_CACHE_MAX = 128;\n\nfunction cappedGetOrCreate<V>(cache: Map<string, V>, key: string, factory: () => V): V {\n const cached = cache.get(key);\n\n if (cached !== undefined) return cached;\n\n if (cache.size >= FORMATTER_CACHE_MAX) {\n const oldest = cache.keys().next().value;\n\n if (oldest !== undefined) cache.delete(oldest);\n }\n\n const value = factory();\n\n cache.set(key, value);\n\n return value;\n}\n\nconst DATE_TIME_FORMATTER_CACHE = new Map<string, Intl.DateTimeFormat>();\nconst RELATIVE_TIME_FORMATTER_CACHE = new Map<string, Intl.RelativeTimeFormat>();\nconst DURATION_FORMATTER_CACHE = new Map<string, DurationFormatter>();\n\n// ─── Format presets ───────────────────────────────────────────────────────────\n\nconst FORMAT_PRESETS: Record<FormatPattern, Intl.DateTimeFormatOptions> = {\n 'date-only': { dateStyle: 'short' },\n long: { dateStyle: 'full', timeStyle: 'long' },\n medium: { dateStyle: 'medium', timeStyle: 'short' },\n short: { dateStyle: 'short', timeStyle: 'short' },\n 'time-only': { timeStyle: 'short' },\n};\n\n// ─── Formatter factory helpers ────────────────────────────────────────────────\n\nfunction serializeIntlOptions(options: Intl.DateTimeFormatOptions): string {\n return JSON.stringify(\n Object.entries(options)\n .filter(([, value]) => value !== undefined)\n .sort(([l], [r]) => l.localeCompare(r))\n .map(([key, value]) => [key, String(value)]),\n );\n}\n\nfunction makeFormatter(options: FormatOptions, fallbackTz?: string): Intl.DateTimeFormat {\n const timeZone = options.timeZone ?? fallbackTz;\n const locale = options.locale;\n\n if (options.intl !== undefined) {\n const cacheKey = `${String(locale ?? '')}|intl|${timeZone ?? ''}|${serializeIntlOptions(options.intl)}`;\n\n return cappedGetOrCreate(DATE_TIME_FORMATTER_CACHE, cacheKey, () => {\n const intlOptions = timeZone !== undefined ? { ...options.intl, timeZone: timeZone } : options.intl;\n\n return new Intl.DateTimeFormat(locale, intlOptions);\n });\n }\n\n const pattern = options.pattern ?? 'medium';\n const cacheKey = `${String(locale ?? '')}|${pattern}|${timeZone ?? ''}`;\n\n return cappedGetOrCreate(\n DATE_TIME_FORMATTER_CACHE,\n cacheKey,\n () => new Intl.DateTimeFormat(locale, { ...FORMAT_PRESETS[pattern], timeZone: timeZone }),\n );\n}\n\nfunction getRelativeFormatter(options: {\n locale?: Intl.LocalesArgument;\n numeric?: Intl.RelativeTimeFormatNumeric;\n style?: Intl.RelativeTimeFormatStyle;\n}): Intl.RelativeTimeFormat {\n const cacheKey = `${String(options.locale ?? '')}|${options.numeric ?? 'auto'}|${options.style ?? 'long'}`;\n\n return cappedGetOrCreate(\n RELATIVE_TIME_FORMATTER_CACHE,\n cacheKey,\n () =>\n new Intl.RelativeTimeFormat(options.locale, {\n numeric: options.numeric ?? 'auto',\n style: options.style ?? 'long',\n }),\n );\n}\n\nfunction getDurationFormatter(options: {\n locale?: Intl.LocalesArgument;\n style?: 'digital' | 'long' | 'narrow' | 'short';\n}): DurationFormatter | null {\n const IntlWithDurationFormat = Intl as typeof Intl & { DurationFormat?: DurationFormatterConstructor };\n\n if (!IntlWithDurationFormat.DurationFormat) return null;\n\n const cacheKey = `${String(options.locale ?? '')}|${options.style ?? ''}`;\n\n return cappedGetOrCreate(\n DURATION_FORMATTER_CACHE,\n cacheKey,\n () => new IntlWithDurationFormat.DurationFormat!(options.locale, { style: options.style }),\n );\n}\n\n// ─── Time scale constants ─────────────────────────────────────────────────────\n\nconst SECONDS_PER_MINUTE = 60;\nconst SECONDS_PER_HOUR = 3_600;\nconst SECONDS_PER_DAY = 86_400;\nconst SECONDS_PER_WEEK = 604_800;\nconst SECONDS_PER_MONTH = 2_629_800; // ≈ 30.4375 days × 86400\nconst SECONDS_PER_YEAR = 31_557_600; // 365.25 days × 86400\n\n// ─── Relative time helpers ────────────────────────────────────────────────────\n\nconst RELATIVE_UNITS: ReadonlyArray<{ scale: number; thresholdToPromote: number; unit: Intl.RelativeTimeFormatUnit }> =\n [\n { scale: 1, thresholdToPromote: SECONDS_PER_MINUTE, unit: 'second' },\n { scale: SECONDS_PER_MINUTE, thresholdToPromote: SECONDS_PER_HOUR / SECONDS_PER_MINUTE, unit: 'minute' },\n { scale: SECONDS_PER_HOUR, thresholdToPromote: SECONDS_PER_DAY / SECONDS_PER_HOUR, unit: 'hour' },\n { scale: SECONDS_PER_DAY, thresholdToPromote: SECONDS_PER_WEEK / SECONDS_PER_DAY, unit: 'day' },\n { scale: SECONDS_PER_WEEK, thresholdToPromote: SECONDS_PER_MONTH / SECONDS_PER_WEEK, unit: 'week' },\n { scale: SECONDS_PER_MONTH, thresholdToPromote: 12, unit: 'month' },\n { scale: SECONDS_PER_YEAR, thresholdToPromote: Number.POSITIVE_INFINITY, unit: 'year' },\n ];\n\nfunction toRelativeUnit(seconds: number): { unit: Intl.RelativeTimeFormatUnit; value: number } {\n if (!Number.isFinite(seconds)) fail('formatRelative received a non-finite time difference.');\n\n const roundedSeconds = Math.round(seconds);\n\n for (const { scale, thresholdToPromote, unit } of RELATIVE_UNITS) {\n const value = Math.round(roundedSeconds / scale);\n\n if (Math.abs(value) < thresholdToPromote) return { unit, value };\n }\n\n return { unit: 'year', value: Math.round(roundedSeconds / SECONDS_PER_YEAR) };\n}\n\n// ─── Duration fallback renderer ───────────────────────────────────────────────\n\n// All English duration unit names follow the same pluralization rule: singular = plural.slice(0, -1)\nconst DURATION_UNITS = [\n 'years',\n 'months',\n 'weeks',\n 'days',\n 'hours',\n 'minutes',\n 'seconds',\n 'milliseconds',\n 'microseconds',\n 'nanoseconds',\n] as const satisfies ReadonlyArray<keyof Temporal.Duration>;\n\n// English-only fallback; runs only when Intl.DurationFormat is unavailable in the runtime.\nfunction buildDurationFallback(duration: Temporal.Duration): string {\n const parts: string[] = [];\n\n for (const unit of DURATION_UNITS) {\n const value = Math.abs(duration[unit] as number);\n\n if (value !== 0) parts.push(`${value} ${value === 1 ? unit.slice(0, -1) : unit}`);\n }\n\n return parts.length === 0 ? '0 seconds' : parts.join(', ');\n}\n\n// ─── Private helpers ──────────────────────────────────────────────────────────\n\n/**\n * Resolves a shared display timezone for two-input range functions.\n * Throws when both inputs are `ZonedDateTime` with different zones and no `options.timeZone` override.\n */\nfunction resolveRangeTz(start: TimeInput, end: TimeInput, options: FormatOptions, caller: string): string | undefined {\n if (options.timeZone) return options.timeZone;\n\n const startTz = start instanceof Temporal.ZonedDateTime ? start.timeZoneId : undefined;\n const endTz = end instanceof Temporal.ZonedDateTime ? end.timeZoneId : undefined;\n\n if (startTz && endTz && startTz !== endTz) {\n fail(`${caller} received ZonedDateTime inputs with different time zones. Pass options.timeZone explicitly.`);\n }\n\n return startTz ?? endTz;\n}\n\n// ─── Public API ───────────────────────────────────────────────────────────────\n\n/**\n * Formats `input` using `Intl.DateTimeFormat`. Defaults to `pattern: 'medium'`.\n *\n * Pass `intl` for full `Intl.DateTimeFormatOptions` control (mutually exclusive with `pattern`).\n * The timezone is inferred from a `ZonedDateTime` input or from `options.timeZone`.\n *\n * @example\n * ```ts\n * format(parse('2026-03-21T10:15:30Z', { as: 'instant' }), { locale: 'en-GB', pattern: 'short', timeZone: 'UTC' })\n * // '21/03/2026, 10:15'\n * ```\n */\nexport function format(input: TimeInput, options: FormatOptions = {}): string {\n const timeZone = options.timeZone ?? (input instanceof Temporal.ZonedDateTime ? input.timeZoneId : undefined);\n\n return makeFormatter(options, timeZone).format(new Date(toInstant(input, { timeZone }).epochMilliseconds));\n}\n\n/**\n * Formats a time span between `start` and `end` using `Intl.DateTimeFormat.formatRange`.\n *\n * @example\n * ```ts\n * formatRange(start, end, { locale: 'en-GB', pattern: 'short', timeZone: 'UTC' })\n * // '21/03/2026, 10:00 – 12:00'\n * ```\n */\nexport function formatRange(start: TimeInput, end: TimeInput, options: FormatOptions = {}): string {\n const timeZone = resolveRangeTz(start, end, options, 'formatRange');\n const formatter = makeFormatter(options, timeZone);\n\n return formatter.formatRange(\n new Date(toInstant(start, { timeZone }).epochMilliseconds),\n new Date(toInstant(end, { timeZone }).epochMilliseconds),\n );\n}\n\n/**\n * Returns the raw `Intl.DateTimeRangeFormatPart[]` array for a time span, enabling\n * fine-grained rendering of range start, end, and shared parts separately.\n *\n * @example\n * ```ts\n * formatRangeParts(start, end, { locale: 'en-US', pattern: 'short', timeZone: 'UTC' })\n * // [{ type: 'month', value: '3', source: 'startRange' }, ...]\n * ```\n */\nexport function formatRangeParts(\n start: TimeInput,\n end: TimeInput,\n options: FormatOptions = {},\n): ReturnType<Intl.DateTimeFormat['formatRangeToParts']> {\n const timeZone = resolveRangeTz(start, end, options, 'formatRangeParts');\n const formatter = makeFormatter(options, timeZone);\n\n return formatter.formatRangeToParts(\n new Date(toInstant(start, { timeZone }).epochMilliseconds),\n new Date(toInstant(end, { timeZone }).epochMilliseconds),\n );\n}\n\n/**\n * Serializes `input` to a UTC ISO 8601 instant string (`2026-03-21T10:15:30Z`).\n *\n * Pass `options.timeZone` when `input` is a `PlainDate` or `PlainDateTime`.\n * Inferred from `ZonedDateTime`; ignored for `Instant`.\n *\n * @throws {TempoMissingTzError} When `input` is a `PlainDate` or `PlainDateTime` and `options.timeZone` is omitted.\n * @throws {TempoInvalidTzError} When `options.timeZone` is not a valid IANA timezone or UTC offset.\n *\n * @example\n * ```ts\n * formatInstant(parse('2026-03-21T11:15:30+01:00[Europe/Berlin]', { as: 'zonedDateTime' }))\n * // '2026-03-21T10:15:30Z'\n * ```\n */\nexport function formatInstant(input: TimeInput, options: TimeZoneOptions = {}): string {\n return toInstant(input, options).toString();\n}\n\n/**\n * Serializes `input` to a zoned ISO 8601 string (`2026-03-21T11:15:30+01:00[Europe/Berlin]`).\n *\n * @param options.timeZone - Required when `input` is a `PlainDate`, `PlainDateTime`, or `Instant`.\n * Inferred automatically from a `ZonedDateTime` input.\n *\n * @throws {TempoMissingTzError} When `input` is not a `ZonedDateTime` and `options.timeZone` is omitted.\n * @throws {TempoInvalidTzError} When `options.timeZone` is not a valid IANA timezone or UTC offset.\n *\n * @example\n * ```ts\n * formatZoned(parse('2026-03-21T10:15:30Z', { as: 'instant' }), { timeZone: 'Europe/Berlin' })\n * // '2026-03-21T11:15:30+01:00[Europe/Berlin]'\n *\n * formatZoned(parse('2026-03-21T11:15:30+01:00[Europe/Berlin]', { as: 'zonedDateTime' }))\n * // '2026-03-21T11:15:30+01:00[Europe/Berlin]' (timeZone inferred)\n * ```\n */\nexport function formatZoned(input: TimeInput, options: TimeZoneOptions = {}): string {\n const timeZone = inferTimeZone(input, options);\n\n return toZoned(input, { timeZone }).toString();\n}\n\n/**\n * Formats `input` relative to `options.base` (defaults to now) using `Intl.RelativeTimeFormat`.\n *\n * @example\n * ```ts\n * formatRelative(parse('2026-03-21T12:00:00Z', { as: 'instant' }), {\n * base: parse('2026-03-21T10:00:00Z', { as: 'instant' }),\n * locale: 'en-US',\n * numeric: 'always',\n * })\n * // 'in 2 hours'\n * ```\n */\nexport function formatRelative(input: RelativeTimeInput, options: RelativeFormatOptions = {}): string {\n const target = input instanceof Temporal.Instant ? input : input.toInstant();\n const base = options.base\n ? options.base instanceof Temporal.Instant\n ? options.base\n : options.base.toInstant()\n : Temporal.Now.instant();\n const differenceInSeconds = (target.epochMilliseconds - base.epochMilliseconds) / 1000;\n const { unit, value } = toRelativeUnit(differenceInSeconds);\n\n return getRelativeFormatter(options).format(value, unit);\n}\n\n/**\n * Parses an ISO duration string or `Temporal.DurationLike` into a `Temporal.Duration`.\n *\n * @example\n * ```ts\n * parseDuration('PT2H30M').toString() // 'PT2H30M'\n * parseDuration({ hours: 2, minutes: 30 }).toString() // 'PT2H30M'\n * ```\n */\nexport function parseDuration(input: string | Temporal.DurationLike): Temporal.Duration {\n try {\n return Temporal.Duration.from(input);\n } catch (error) {\n fail(\n `Invalid duration input: \"${String(input)}\". Expected an ISO 8601 duration string or Temporal.DurationLike.`,\n TempoInvalidInputError,\n { cause: error },\n );\n }\n}\n\n/**\n * Formats a duration using `Intl.DurationFormat` when available, falling back to\n * a human-readable plain-English string.\n *\n * @example\n * ```ts\n * formatDuration('PT2H30M', { locale: 'en-US', style: 'long' })\n * // '2 hours, 30 minutes'\n * ```\n */\nexport function formatDuration(input: string | Temporal.DurationLike, options: DurationFormatOptions = {}): string {\n const duration = parseDuration(input);\n const formatter = getDurationFormatter(options);\n\n if (formatter) return formatter.format(duration);\n\n return buildDurationFallback(duration);\n}\n\n/**\n * Returns the raw `Intl.DateTimeFormatPart[]` array for `input`, enabling\n * custom rendering where individual parts (year, month, day, etc.) need\n * to be styled or composed differently.\n *\n * @example\n * ```ts\n * formatParts(parse('2026-03-21T10:15:30Z', { as: 'instant' }), { pattern: 'medium', timeZone: 'UTC' })\n * // [{ type: 'month', value: 'Mar' }, { type: 'literal', value: ' ' }, ...]\n * ```\n */\nexport function formatParts(input: TimeInput, options: FormatOptions = {}): Intl.DateTimeFormatPart[] {\n const timeZone = options.timeZone ?? (input instanceof Temporal.ZonedDateTime ? input.timeZoneId : undefined);\n\n return makeFormatter(options, timeZone).formatToParts(new Date(toInstant(input, { timeZone }).epochMilliseconds));\n}\n\n/**\n * Converts a `TimeDiffResult` to a human-readable string.\n * Uses the singular unit name when value is 1, plural (unit + 's') otherwise.\n *\n * Pass `options.locale` to localize the numeric part via `Intl.NumberFormat`.\n * Unit names remain English — for fully localized output use {@link formatRelative}\n * or {@link formatDuration} instead.\n *\n * @example\n * ```ts\n * humanize({ unit: 'day', value: 1 }) // '1 day'\n * humanize({ unit: 'day', value: 3 }) // '3 days'\n * humanize({ unit: 'day', value: 3 }, { locale: 'ar' }) // '٣ days'\n * humanize({ unit: 'millisecond', value: 0 }) // '0 milliseconds'\n * ```\n */\nexport function humanize(diff: TimeDiffResult, options: { locale?: Intl.LocalesArgument } = {}): string {\n const { unit, value } = diff;\n const formatted = options.locale ? new Intl.NumberFormat(options.locale).format(value) : String(value);\n\n return `${formatted} ${value === 1 ? unit : `${unit}s`}`;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"format.cjs","names":[],"sources":["../src/format.ts"],"sourcesContent":["import { Temporal } from '@js-temporal/polyfill';\nimport { toInstant, toZoned } from './_convert';\nimport { inferTimeZone, validateTimeZone } from './_tz';\nimport { fail, TempoInvalidInputError } from './errors';\nimport type {\n DurationFormatOptions,\n FormatOptions,\n FormatPattern,\n RelativeFormatOptions,\n RelativeTimeInput,\n TimeDiffResult,\n TimeInput,\n TimeZoneOptions,\n} from './types';\n\n// ─── Formatter types ──────────────────────────────────────────────────────────\n\ntype DurationFormatter = { format(value: Temporal.Duration): string };\ntype DurationFormatterConstructor = new (\n locales?: Intl.LocalesArgument,\n options?: { style?: 'digital' | 'long' | 'narrow' | 'short' },\n) => DurationFormatter;\n\n// ─── Formatter caches ─────────────────────────────────────────────────────────\n\nconst FORMATTER_CACHE_MAX = 128;\n\nfunction cappedGetOrCreate<V>(cache: Map<string, V>, key: string, factory: () => V): V {\n const cached = cache.get(key);\n\n if (cached !== undefined) return cached;\n\n if (cache.size >= FORMATTER_CACHE_MAX) {\n const oldest = cache.keys().next().value;\n\n if (oldest !== undefined) cache.delete(oldest);\n }\n\n const value = factory();\n\n cache.set(key, value);\n\n return value;\n}\n\nconst DATE_TIME_FORMATTER_CACHE = new Map<string, Intl.DateTimeFormat>();\nconst RELATIVE_TIME_FORMATTER_CACHE = new Map<string, Intl.RelativeTimeFormat>();\nconst DURATION_FORMATTER_CACHE = new Map<string, DurationFormatter>();\n\n// ─── Format presets ───────────────────────────────────────────────────────────\n\nconst FORMAT_PRESETS: Record<FormatPattern, Intl.DateTimeFormatOptions> = {\n 'date-only': { dateStyle: 'short' },\n long: { dateStyle: 'full', timeStyle: 'long' },\n medium: { dateStyle: 'medium', timeStyle: 'short' },\n short: { dateStyle: 'short', timeStyle: 'short' },\n 'time-only': { timeStyle: 'short' },\n};\n\n// ─── Formatter factory helpers ────────────────────────────────────────────────\n\nfunction serializeIntlOptions(options: Intl.DateTimeFormatOptions): string {\n return JSON.stringify(\n Object.entries(options)\n .filter(([, value]) => value !== undefined)\n .sort(([l], [r]) => l.localeCompare(r))\n .map(([key, value]) => [key, String(value)]),\n );\n}\n\nfunction makeFormatter(options: FormatOptions, fallbackTz?: string): Intl.DateTimeFormat {\n const timeZone = options.timeZone ?? fallbackTz;\n const locale = options.locale;\n\n if (options.intl !== undefined) {\n const cacheKey = `${String(locale ?? '')}|intl|${timeZone ?? ''}|${serializeIntlOptions(options.intl)}`;\n\n return cappedGetOrCreate(DATE_TIME_FORMATTER_CACHE, cacheKey, () => {\n const intlOptions = timeZone !== undefined ? { ...options.intl, timeZone: timeZone } : options.intl;\n\n return new Intl.DateTimeFormat(locale, intlOptions);\n });\n }\n\n const pattern = options.pattern ?? 'medium';\n const cacheKey = `${String(locale ?? '')}|${pattern}|${timeZone ?? ''}`;\n\n return cappedGetOrCreate(\n DATE_TIME_FORMATTER_CACHE,\n cacheKey,\n () => new Intl.DateTimeFormat(locale, { ...FORMAT_PRESETS[pattern], timeZone: timeZone }),\n );\n}\n\nfunction getRelativeFormatter(options: {\n locale?: Intl.LocalesArgument;\n numeric?: Intl.RelativeTimeFormatNumeric;\n style?: Intl.RelativeTimeFormatStyle;\n}): Intl.RelativeTimeFormat {\n const cacheKey = `${String(options.locale ?? '')}|${options.numeric ?? 'auto'}|${options.style ?? 'long'}`;\n\n return cappedGetOrCreate(\n RELATIVE_TIME_FORMATTER_CACHE,\n cacheKey,\n () =>\n new Intl.RelativeTimeFormat(options.locale, {\n numeric: options.numeric ?? 'auto',\n style: options.style ?? 'long',\n }),\n );\n}\n\nfunction getDurationFormatter(options: {\n locale?: Intl.LocalesArgument;\n style?: 'digital' | 'long' | 'narrow' | 'short';\n}): DurationFormatter | null {\n const IntlWithDurationFormat = Intl as typeof Intl & { DurationFormat?: DurationFormatterConstructor };\n\n if (!IntlWithDurationFormat.DurationFormat) return null;\n\n const cacheKey = `${String(options.locale ?? '')}|${options.style ?? ''}`;\n\n return cappedGetOrCreate(\n DURATION_FORMATTER_CACHE,\n cacheKey,\n () => new IntlWithDurationFormat.DurationFormat!(options.locale, { style: options.style }),\n );\n}\n\n// ─── Time scale constants ─────────────────────────────────────────────────────\n\nconst SECONDS_PER_MINUTE = 60;\nconst SECONDS_PER_HOUR = 3_600;\nconst SECONDS_PER_DAY = 86_400;\nconst SECONDS_PER_WEEK = 604_800; // 7 × 86400 — fixed\n\n// ─── Relative time helpers ────────────────────────────────────────────────────\n\nconst RELATIVE_UNITS: ReadonlyArray<{ scale: number; thresholdToPromote: number; unit: Intl.RelativeTimeFormatUnit }> =\n [\n { scale: 1, thresholdToPromote: SECONDS_PER_MINUTE, unit: 'second' },\n { scale: SECONDS_PER_MINUTE, thresholdToPromote: SECONDS_PER_HOUR / SECONDS_PER_MINUTE, unit: 'minute' },\n { scale: SECONDS_PER_HOUR, thresholdToPromote: SECONDS_PER_DAY / SECONDS_PER_HOUR, unit: 'hour' },\n { scale: SECONDS_PER_DAY, thresholdToPromote: SECONDS_PER_WEEK / SECONDS_PER_DAY, unit: 'day' },\n { scale: SECONDS_PER_WEEK, thresholdToPromote: Number.POSITIVE_INFINITY, unit: 'week' },\n ];\n\nfunction toRelativeUnit(seconds: number): { unit: Intl.RelativeTimeFormatUnit; value: number } {\n if (!Number.isFinite(seconds)) fail('formatRelative received a non-finite time difference.');\n\n const roundedSeconds = Math.round(seconds);\n\n for (const { scale, thresholdToPromote, unit } of RELATIVE_UNITS) {\n const value = Math.round(roundedSeconds / scale);\n\n if (Math.abs(value) < thresholdToPromote) return { unit, value };\n }\n\n return { unit: 'week', value: Math.round(roundedSeconds / SECONDS_PER_WEEK) };\n}\n\nfunction resolveRelativeTimeZone(input: RelativeTimeInput, base: RelativeTimeInput, timeZone?: string): string {\n if (timeZone) return validateTimeZone(timeZone);\n\n const inputTimeZone = input instanceof Temporal.ZonedDateTime ? input.timeZoneId : undefined;\n const baseTimeZone = base instanceof Temporal.ZonedDateTime ? base.timeZoneId : undefined;\n\n if (inputTimeZone && baseTimeZone && inputTimeZone !== baseTimeZone) {\n fail('formatRelative received ZonedDateTime inputs with different time zones. Pass options.timeZone explicitly.');\n }\n\n return inputTimeZone ?? baseTimeZone ?? 'UTC';\n}\n\nfunction toCalendarRelativeUnit(\n input: RelativeTimeInput,\n base: RelativeTimeInput,\n timeZone: string,\n): { unit: Intl.RelativeTimeFormatUnit; value: number } {\n const target = toZoned(input, { timeZone });\n const reference = toZoned(base, { timeZone });\n const comparison = Temporal.ZonedDateTime.compare(target, reference);\n\n if (comparison === 0) return { unit: 'second', value: 0 };\n\n const duration =\n comparison > 0\n ? target.since(reference, { largestUnit: 'year' })\n : reference.since(target, { largestUnit: 'year' });\n const direction = comparison > 0 ? 1 : -1;\n\n if (duration.years) return { unit: 'year', value: duration.years * direction };\n\n if (duration.months) return { unit: 'month', value: duration.months * direction };\n\n return toRelativeUnit((input.epochMilliseconds - base.epochMilliseconds) / 1000);\n}\n\n// ─── Duration fallback renderer ───────────────────────────────────────────────\n\n// All English duration unit names follow the same pluralization rule: singular = plural.slice(0, -1)\nconst DURATION_UNITS = [\n 'years',\n 'months',\n 'weeks',\n 'days',\n 'hours',\n 'minutes',\n 'seconds',\n 'milliseconds',\n 'microseconds',\n 'nanoseconds',\n] as const satisfies ReadonlyArray<keyof Temporal.Duration>;\n\n// English-only fallback; runs only when Intl.DurationFormat is unavailable in the runtime.\nfunction buildDurationFallback(duration: Temporal.Duration): string {\n const parts: string[] = [];\n\n for (const unit of DURATION_UNITS) {\n const value = Math.abs(duration[unit] as number);\n\n if (value !== 0) parts.push(`${value} ${value === 1 ? unit.slice(0, -1) : unit}`);\n }\n\n return parts.length === 0 ? '0 seconds' : parts.join(', ');\n}\n\n// ─── Private helpers ──────────────────────────────────────────────────────────\n\n/**\n * Resolves a shared display timezone for two-input range functions.\n * Throws when both inputs are `ZonedDateTime` with different zones and no `options.timeZone` override.\n */\nfunction resolveRangeTz(start: TimeInput, end: TimeInput, options: FormatOptions, caller: string): string | undefined {\n if (options.timeZone) return options.timeZone;\n\n const startTz = start instanceof Temporal.ZonedDateTime ? start.timeZoneId : undefined;\n const endTz = end instanceof Temporal.ZonedDateTime ? end.timeZoneId : undefined;\n\n if (startTz && endTz && startTz !== endTz) {\n fail(`${caller} received ZonedDateTime inputs with different time zones. Pass options.timeZone explicitly.`);\n }\n\n return startTz ?? endTz;\n}\n\n// ─── Public API ───────────────────────────────────────────────────────────────\n\n/**\n * Formats `input` using `Intl.DateTimeFormat`. Defaults to `pattern: 'medium'`.\n *\n * Pass `intl` for full `Intl.DateTimeFormatOptions` control (mutually exclusive with `pattern`).\n * The timezone is inferred from a `ZonedDateTime` input or from `options.timeZone`.\n *\n * @example\n * ```ts\n * format(parse('2026-03-21T10:15:30Z', { as: 'instant' }), { locale: 'en-GB', pattern: 'short', timeZone: 'UTC' })\n * // '21/03/2026, 10:15'\n * ```\n */\nexport function format(input: TimeInput, options: FormatOptions = {}): string {\n const timeZone = options.timeZone ?? (input instanceof Temporal.ZonedDateTime ? input.timeZoneId : undefined);\n\n return makeFormatter(options, timeZone).format(new Date(toInstant(input, { timeZone }).epochMilliseconds));\n}\n\n/**\n * Formats a time span between `start` and `end` using `Intl.DateTimeFormat.formatRange`.\n *\n * @example\n * ```ts\n * formatRange(start, end, { locale: 'en-GB', pattern: 'short', timeZone: 'UTC' })\n * // '21/03/2026, 10:00 – 12:00'\n * ```\n */\nexport function formatRange(start: TimeInput, end: TimeInput, options: FormatOptions = {}): string {\n const timeZone = resolveRangeTz(start, end, options, 'formatRange');\n const formatter = makeFormatter(options, timeZone);\n\n return formatter.formatRange(\n new Date(toInstant(start, { timeZone }).epochMilliseconds),\n new Date(toInstant(end, { timeZone }).epochMilliseconds),\n );\n}\n\n/**\n * Returns the raw `Intl.DateTimeRangeFormatPart[]` array for a time span, enabling\n * fine-grained rendering of range start, end, and shared parts separately.\n *\n * @example\n * ```ts\n * formatRangeParts(start, end, { locale: 'en-US', pattern: 'short', timeZone: 'UTC' })\n * // [{ type: 'month', value: '3', source: 'startRange' }, ...]\n * ```\n */\nexport function formatRangeParts(\n start: TimeInput,\n end: TimeInput,\n options: FormatOptions = {},\n): ReturnType<Intl.DateTimeFormat['formatRangeToParts']> {\n const timeZone = resolveRangeTz(start, end, options, 'formatRangeParts');\n const formatter = makeFormatter(options, timeZone);\n\n return formatter.formatRangeToParts(\n new Date(toInstant(start, { timeZone }).epochMilliseconds),\n new Date(toInstant(end, { timeZone }).epochMilliseconds),\n );\n}\n\n/**\n * Serializes `input` to a UTC ISO 8601 instant string (`2026-03-21T10:15:30Z`).\n *\n * Pass `options.timeZone` when `input` is a `PlainDate` or `PlainDateTime`.\n * Inferred from `ZonedDateTime`; ignored for `Instant`.\n *\n * @throws {TempoMissingTzError} When `input` is a `PlainDate` or `PlainDateTime` and `options.timeZone` is omitted.\n * @throws {TempoInvalidTzError} When `options.timeZone` is not a valid IANA timezone or UTC offset.\n *\n * @example\n * ```ts\n * formatInstant(parse('2026-03-21T11:15:30+01:00[Europe/Berlin]', { as: 'zonedDateTime' }))\n * // '2026-03-21T10:15:30Z'\n * ```\n */\nexport function formatInstant(input: TimeInput, options: TimeZoneOptions = {}): string {\n return toInstant(input, options).toString();\n}\n\n/**\n * Serializes `input` to a zoned ISO 8601 string (`2026-03-21T11:15:30+01:00[Europe/Berlin]`).\n *\n * @param options.timeZone - Required when `input` is a `PlainDate`, `PlainDateTime`, or `Instant`.\n * Inferred automatically from a `ZonedDateTime` input.\n *\n * @throws {TempoMissingTzError} When `input` is not a `ZonedDateTime` and `options.timeZone` is omitted.\n * @throws {TempoInvalidTzError} When `options.timeZone` is not a valid IANA timezone or UTC offset.\n *\n * @example\n * ```ts\n * formatZoned(parse('2026-03-21T10:15:30Z', { as: 'instant' }), { timeZone: 'Europe/Berlin' })\n * // '2026-03-21T11:15:30+01:00[Europe/Berlin]'\n *\n * formatZoned(parse('2026-03-21T11:15:30+01:00[Europe/Berlin]', { as: 'zonedDateTime' }))\n * // '2026-03-21T11:15:30+01:00[Europe/Berlin]' (timeZone inferred)\n * ```\n */\nexport function formatZoned(input: TimeInput, options: TimeZoneOptions = {}): string {\n const timeZone = inferTimeZone(input, options);\n\n return toZoned(input, { timeZone }).toString();\n}\n\n/**\n * Formats `input` relative to `options.base` (defaults to now) using `Intl.RelativeTimeFormat`.\n *\n * Complete calendar months and years are resolved in the requested or inferred timezone.\n * Shorter spans use fixed elapsed-time units.\n *\n * @example\n * ```ts\n * formatRelative(parse('2026-03-21T12:00:00Z', { as: 'instant' }), {\n * base: parse('2026-03-21T10:00:00Z', { as: 'instant' }),\n * locale: 'en-US',\n * numeric: 'always',\n * })\n * // 'in 2 hours'\n * ```\n */\nexport function formatRelative(input: RelativeTimeInput, options: RelativeFormatOptions = {}): string {\n const base = options.base ?? Temporal.Now.instant();\n const timeZone = resolveRelativeTimeZone(input, base, options.timeZone);\n const { unit, value } = toCalendarRelativeUnit(input, base, timeZone);\n\n return getRelativeFormatter(options).format(value, unit);\n}\n\n/**\n * Parses an ISO duration string or `Temporal.DurationLike` into a `Temporal.Duration`.\n *\n * @example\n * ```ts\n * parseDuration('PT2H30M').toString() // 'PT2H30M'\n * parseDuration({ hours: 2, minutes: 30 }).toString() // 'PT2H30M'\n * ```\n */\nexport function parseDuration(input: string | Temporal.DurationLike): Temporal.Duration {\n try {\n return Temporal.Duration.from(input);\n } catch (error) {\n fail(\n `Invalid duration input: \"${String(input)}\". Expected an ISO 8601 duration string or Temporal.DurationLike.`,\n TempoInvalidInputError,\n { cause: error },\n );\n }\n}\n\n/**\n * Formats a duration using `Intl.DurationFormat` when available, falling back to\n * a human-readable plain-English string.\n *\n * @example\n * ```ts\n * formatDuration('PT2H30M', { locale: 'en-US', style: 'long' })\n * // '2 hours, 30 minutes'\n * ```\n */\nexport function formatDuration(input: string | Temporal.DurationLike, options: DurationFormatOptions = {}): string {\n const duration = parseDuration(input);\n const formatter = getDurationFormatter(options);\n\n if (formatter) return formatter.format(duration);\n\n return buildDurationFallback(duration);\n}\n\n/**\n * Returns the raw `Intl.DateTimeFormatPart[]` array for `input`, enabling\n * custom rendering where individual parts (year, month, day, etc.) need\n * to be styled or composed differently.\n *\n * @example\n * ```ts\n * formatParts(parse('2026-03-21T10:15:30Z', { as: 'instant' }), { pattern: 'medium', timeZone: 'UTC' })\n * // [{ type: 'month', value: 'Mar' }, { type: 'literal', value: ' ' }, ...]\n * ```\n */\nexport function formatParts(input: TimeInput, options: FormatOptions = {}): Intl.DateTimeFormatPart[] {\n const timeZone = options.timeZone ?? (input instanceof Temporal.ZonedDateTime ? input.timeZoneId : undefined);\n\n return makeFormatter(options, timeZone).formatToParts(new Date(toInstant(input, { timeZone }).epochMilliseconds));\n}\n\n/**\n * Converts a `TimeDiffResult` to a human-readable string.\n * Uses the singular unit name when value is 1, plural (unit + 's') otherwise.\n *\n * Pass `options.locale` to localize the numeric part via `Intl.NumberFormat`.\n * Unit names remain English — for fully localized output use {@link formatRelative}\n * or {@link formatDuration} instead.\n *\n * @example\n * ```ts\n * humanize({ unit: 'day', value: 1 }) // '1 day'\n * humanize({ unit: 'day', value: 3 }) // '3 days'\n * humanize({ unit: 'day', value: 3 }, { locale: 'ar' }) // '٣ days'\n * humanize({ unit: 'millisecond', value: 0 }) // '0 milliseconds'\n * ```\n */\nexport function humanize(diff: TimeDiffResult, options: { locale?: Intl.LocalesArgument } = {}): string {\n const { unit, value } = diff;\n const formatted = options.locale ? new Intl.NumberFormat(options.locale).format(value) : String(value);\n\n return `${formatted} ${value === 1 ? unit : `${unit}s`}`;\n}\n"],"mappings":"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"}
|
package/dist/format.d.ts
CHANGED
|
@@ -72,6 +72,9 @@ export declare function formatZoned(input: TimeInput, options?: TimeZoneOptions)
|
|
|
72
72
|
/**
|
|
73
73
|
* Formats `input` relative to `options.base` (defaults to now) using `Intl.RelativeTimeFormat`.
|
|
74
74
|
*
|
|
75
|
+
* Complete calendar months and years are resolved in the requested or inferred timezone.
|
|
76
|
+
* Shorter spans use fixed elapsed-time units.
|
|
77
|
+
*
|
|
75
78
|
* @example
|
|
76
79
|
* ```ts
|
|
77
80
|
* formatRelative(parse('2026-03-21T12:00:00Z', { as: 'instant' }), {
|
package/dist/format.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"format.d.ts","sourceRoot":"","sources":["../src/format.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAIjD,OAAO,KAAK,EACV,qBAAqB,EACrB,aAAa,EAEb,qBAAqB,EACrB,iBAAiB,EACjB,cAAc,EACd,SAAS,EACT,eAAe,EAChB,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"format.d.ts","sourceRoot":"","sources":["../src/format.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAIjD,OAAO,KAAK,EACV,qBAAqB,EACrB,aAAa,EAEb,qBAAqB,EACrB,iBAAiB,EACjB,cAAc,EACd,SAAS,EACT,eAAe,EAChB,MAAM,SAAS,CAAC;AA2OjB;;;;;;;;;;;GAWG;AACH,wBAAgB,MAAM,CAAC,KAAK,EAAE,SAAS,EAAE,OAAO,GAAE,aAAkB,GAAG,MAAM,CAI5E;AAED;;;;;;;;GAQG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,SAAS,EAAE,GAAG,EAAE,SAAS,EAAE,OAAO,GAAE,aAAkB,GAAG,MAAM,CAQjG;AAED;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,SAAS,EAChB,GAAG,EAAE,SAAS,EACd,OAAO,GAAE,aAAkB,GAC1B,UAAU,CAAC,IAAI,CAAC,cAAc,CAAC,oBAAoB,CAAC,CAAC,CAQvD;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,SAAS,EAAE,OAAO,GAAE,eAAoB,GAAG,MAAM,CAErF;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,SAAS,EAAE,OAAO,GAAE,eAAoB,GAAG,MAAM,CAInF;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,iBAAiB,EAAE,OAAO,GAAE,qBAA0B,GAAG,MAAM,CAMpG;AAED;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,QAAQ,CAAC,YAAY,GAAG,QAAQ,CAAC,QAAQ,CAUtF;AAED;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,QAAQ,CAAC,YAAY,EAAE,OAAO,GAAE,qBAA0B,GAAG,MAAM,CAOjH;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,SAAS,EAAE,OAAO,GAAE,aAAkB,GAAG,IAAI,CAAC,kBAAkB,EAAE,CAIpG;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,cAAc,EAAE,OAAO,GAAE;IAAE,MAAM,CAAC,EAAE,IAAI,CAAC,eAAe,CAAA;CAAO,GAAG,MAAM,CAKtG"}
|
package/dist/format.js
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
import { TempoInvalidInputError as e, fail as t } from "./errors.js";
|
|
2
|
-
import { inferTimeZone as n } from "./_tz.js";
|
|
3
|
-
import { toInstant as
|
|
4
|
-
import { Temporal as
|
|
2
|
+
import { inferTimeZone as n, validateTimeZone as r } from "./_tz.js";
|
|
3
|
+
import { toInstant as i, toZoned as a } from "./_convert.js";
|
|
4
|
+
import { Temporal as o } from "@js-temporal/polyfill";
|
|
5
5
|
//#region src/format.ts
|
|
6
|
-
var
|
|
7
|
-
function
|
|
6
|
+
var s = 128;
|
|
7
|
+
function c(e, t, n) {
|
|
8
8
|
let r = e.get(t);
|
|
9
9
|
if (r !== void 0) return r;
|
|
10
|
-
if (e.size >=
|
|
10
|
+
if (e.size >= s) {
|
|
11
11
|
let t = e.keys().next().value;
|
|
12
12
|
t !== void 0 && e.delete(t);
|
|
13
13
|
}
|
|
14
14
|
let i = n();
|
|
15
15
|
return e.set(t, i), i;
|
|
16
16
|
}
|
|
17
|
-
var
|
|
17
|
+
var l = /* @__PURE__ */ new Map(), u = /* @__PURE__ */ new Map(), d = /* @__PURE__ */ new Map(), f = {
|
|
18
18
|
"date-only": { dateStyle: "short" },
|
|
19
19
|
long: {
|
|
20
20
|
dateStyle: "full",
|
|
@@ -30,12 +30,12 @@ var c = /* @__PURE__ */ new Map(), l = /* @__PURE__ */ new Map(), u = /* @__PURE
|
|
|
30
30
|
},
|
|
31
31
|
"time-only": { timeStyle: "short" }
|
|
32
32
|
};
|
|
33
|
-
function
|
|
33
|
+
function p(e) {
|
|
34
34
|
return JSON.stringify(Object.entries(e).filter(([, e]) => e !== void 0).sort(([e], [t]) => e.localeCompare(t)).map(([e, t]) => [e, String(t)]));
|
|
35
35
|
}
|
|
36
|
-
function
|
|
36
|
+
function m(e, t) {
|
|
37
37
|
let n = e.timeZone ?? t, r = e.locale;
|
|
38
|
-
if (e.intl !== void 0) return
|
|
38
|
+
if (e.intl !== void 0) return c(l, `${String(r ?? "")}|intl|${n ?? ""}|${p(e.intl)}`, () => {
|
|
39
39
|
let t = n === void 0 ? e.intl : {
|
|
40
40
|
...e.intl,
|
|
41
41
|
timeZone: n
|
|
@@ -43,62 +43,52 @@ function p(e, t) {
|
|
|
43
43
|
return new Intl.DateTimeFormat(r, t);
|
|
44
44
|
});
|
|
45
45
|
let i = e.pattern ?? "medium";
|
|
46
|
-
return
|
|
47
|
-
...
|
|
46
|
+
return c(l, `${String(r ?? "")}|${i}|${n ?? ""}`, () => new Intl.DateTimeFormat(r, {
|
|
47
|
+
...f[i],
|
|
48
48
|
timeZone: n
|
|
49
49
|
}));
|
|
50
50
|
}
|
|
51
|
-
function
|
|
52
|
-
return
|
|
51
|
+
function h(e) {
|
|
52
|
+
return c(u, `${String(e.locale ?? "")}|${e.numeric ?? "auto"}|${e.style ?? "long"}`, () => new Intl.RelativeTimeFormat(e.locale, {
|
|
53
53
|
numeric: e.numeric ?? "auto",
|
|
54
54
|
style: e.style ?? "long"
|
|
55
55
|
}));
|
|
56
56
|
}
|
|
57
|
-
function
|
|
57
|
+
function g(e) {
|
|
58
58
|
let t = Intl;
|
|
59
|
-
return t.DurationFormat ?
|
|
59
|
+
return t.DurationFormat ? c(d, `${String(e.locale ?? "")}|${e.style ?? ""}`, () => new t.DurationFormat(e.locale, { style: e.style })) : null;
|
|
60
60
|
}
|
|
61
|
-
var
|
|
61
|
+
var _ = 60, v = 3600, y = 86400, b = 604800, x = [
|
|
62
62
|
{
|
|
63
63
|
scale: 1,
|
|
64
|
-
thresholdToPromote:
|
|
64
|
+
thresholdToPromote: _,
|
|
65
65
|
unit: "second"
|
|
66
66
|
},
|
|
67
|
-
{
|
|
68
|
-
scale: g,
|
|
69
|
-
thresholdToPromote: _ / g,
|
|
70
|
-
unit: "minute"
|
|
71
|
-
},
|
|
72
67
|
{
|
|
73
68
|
scale: _,
|
|
74
69
|
thresholdToPromote: v / _,
|
|
75
|
-
unit: "
|
|
70
|
+
unit: "minute"
|
|
76
71
|
},
|
|
77
72
|
{
|
|
78
73
|
scale: v,
|
|
79
74
|
thresholdToPromote: y / v,
|
|
80
|
-
unit: "
|
|
75
|
+
unit: "hour"
|
|
81
76
|
},
|
|
82
77
|
{
|
|
83
78
|
scale: y,
|
|
84
79
|
thresholdToPromote: b / y,
|
|
85
|
-
unit: "
|
|
80
|
+
unit: "day"
|
|
86
81
|
},
|
|
87
82
|
{
|
|
88
83
|
scale: b,
|
|
89
|
-
thresholdToPromote: 12,
|
|
90
|
-
unit: "month"
|
|
91
|
-
},
|
|
92
|
-
{
|
|
93
|
-
scale: x,
|
|
94
84
|
thresholdToPromote: Infinity,
|
|
95
|
-
unit: "
|
|
85
|
+
unit: "week"
|
|
96
86
|
}
|
|
97
87
|
];
|
|
98
|
-
function
|
|
88
|
+
function S(e) {
|
|
99
89
|
Number.isFinite(e) || t("formatRelative received a non-finite time difference.");
|
|
100
90
|
let n = Math.round(e);
|
|
101
|
-
for (let { scale: e, thresholdToPromote: t, unit: r } of
|
|
91
|
+
for (let { scale: e, thresholdToPromote: t, unit: r } of x) {
|
|
102
92
|
let i = Math.round(n / e);
|
|
103
93
|
if (Math.abs(i) < t) return {
|
|
104
94
|
unit: r,
|
|
@@ -106,11 +96,31 @@ function C(e) {
|
|
|
106
96
|
};
|
|
107
97
|
}
|
|
108
98
|
return {
|
|
109
|
-
unit: "
|
|
110
|
-
value: Math.round(n /
|
|
99
|
+
unit: "week",
|
|
100
|
+
value: Math.round(n / b)
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
function C(e, n, i) {
|
|
104
|
+
if (i) return r(i);
|
|
105
|
+
let a = e instanceof o.ZonedDateTime ? e.timeZoneId : void 0, s = n instanceof o.ZonedDateTime ? n.timeZoneId : void 0;
|
|
106
|
+
return a && s && a !== s && t("formatRelative received ZonedDateTime inputs with different time zones. Pass options.timeZone explicitly."), a ?? s ?? "UTC";
|
|
107
|
+
}
|
|
108
|
+
function w(e, t, n) {
|
|
109
|
+
let r = a(e, { timeZone: n }), i = a(t, { timeZone: n }), s = o.ZonedDateTime.compare(r, i);
|
|
110
|
+
if (s === 0) return {
|
|
111
|
+
unit: "second",
|
|
112
|
+
value: 0
|
|
111
113
|
};
|
|
114
|
+
let c = s > 0 ? r.since(i, { largestUnit: "year" }) : i.since(r, { largestUnit: "year" }), l = s > 0 ? 1 : -1;
|
|
115
|
+
return c.years ? {
|
|
116
|
+
unit: "year",
|
|
117
|
+
value: c.years * l
|
|
118
|
+
} : c.months ? {
|
|
119
|
+
unit: "month",
|
|
120
|
+
value: c.months * l
|
|
121
|
+
} : S((e.epochMilliseconds - t.epochMilliseconds) / 1e3);
|
|
112
122
|
}
|
|
113
|
-
var
|
|
123
|
+
var T = [
|
|
114
124
|
"years",
|
|
115
125
|
"months",
|
|
116
126
|
"weeks",
|
|
@@ -122,62 +132,62 @@ var w = [
|
|
|
122
132
|
"microseconds",
|
|
123
133
|
"nanoseconds"
|
|
124
134
|
];
|
|
125
|
-
function
|
|
135
|
+
function E(e) {
|
|
126
136
|
let t = [];
|
|
127
|
-
for (let n of
|
|
137
|
+
for (let n of T) {
|
|
128
138
|
let r = Math.abs(e[n]);
|
|
129
139
|
r !== 0 && t.push(`${r} ${r === 1 ? n.slice(0, -1) : n}`);
|
|
130
140
|
}
|
|
131
141
|
return t.length === 0 ? "0 seconds" : t.join(", ");
|
|
132
142
|
}
|
|
133
|
-
function
|
|
143
|
+
function D(e, n, r, i) {
|
|
134
144
|
if (r.timeZone) return r.timeZone;
|
|
135
|
-
let
|
|
136
|
-
return
|
|
145
|
+
let a = e instanceof o.ZonedDateTime ? e.timeZoneId : void 0, s = n instanceof o.ZonedDateTime ? n.timeZoneId : void 0;
|
|
146
|
+
return a && s && a !== s && t(`${i} received ZonedDateTime inputs with different time zones. Pass options.timeZone explicitly.`), a ?? s;
|
|
137
147
|
}
|
|
138
|
-
function
|
|
139
|
-
let n = t.timeZone ?? (e instanceof
|
|
140
|
-
return
|
|
141
|
-
}
|
|
142
|
-
function O(e, t, n = {}) {
|
|
143
|
-
let i = E(e, t, n, "formatRange");
|
|
144
|
-
return p(n, i).formatRange(new Date(r(e, { timeZone: i }).epochMilliseconds), new Date(r(t, { timeZone: i }).epochMilliseconds));
|
|
148
|
+
function O(e, t = {}) {
|
|
149
|
+
let n = t.timeZone ?? (e instanceof o.ZonedDateTime ? e.timeZoneId : void 0);
|
|
150
|
+
return m(t, n).format(new Date(i(e, { timeZone: n }).epochMilliseconds));
|
|
145
151
|
}
|
|
146
152
|
function k(e, t, n = {}) {
|
|
147
|
-
let
|
|
148
|
-
return
|
|
153
|
+
let r = D(e, t, n, "formatRange");
|
|
154
|
+
return m(n, r).formatRange(new Date(i(e, { timeZone: r }).epochMilliseconds), new Date(i(t, { timeZone: r }).epochMilliseconds));
|
|
149
155
|
}
|
|
150
|
-
function A(e, t = {}) {
|
|
151
|
-
|
|
156
|
+
function A(e, t, n = {}) {
|
|
157
|
+
let r = D(e, t, n, "formatRangeParts");
|
|
158
|
+
return m(n, r).formatRangeToParts(new Date(i(e, { timeZone: r }).epochMilliseconds), new Date(i(t, { timeZone: r }).epochMilliseconds));
|
|
152
159
|
}
|
|
153
160
|
function j(e, t = {}) {
|
|
154
|
-
|
|
155
|
-
return i(e, { timeZone: r }).toString();
|
|
161
|
+
return i(e, t).toString();
|
|
156
162
|
}
|
|
157
163
|
function M(e, t = {}) {
|
|
158
|
-
let
|
|
159
|
-
return
|
|
164
|
+
let r = n(e, t);
|
|
165
|
+
return a(e, { timeZone: r }).toString();
|
|
160
166
|
}
|
|
161
|
-
function N(
|
|
167
|
+
function N(e, t = {}) {
|
|
168
|
+
let n = t.base ?? o.Now.instant(), { unit: r, value: i } = w(e, n, C(e, n, t.timeZone));
|
|
169
|
+
return h(t).format(i, r);
|
|
170
|
+
}
|
|
171
|
+
function P(n) {
|
|
162
172
|
try {
|
|
163
|
-
return
|
|
173
|
+
return o.Duration.from(n);
|
|
164
174
|
} catch (r) {
|
|
165
175
|
t(`Invalid duration input: "${String(n)}". Expected an ISO 8601 duration string or Temporal.DurationLike.`, e, { cause: r });
|
|
166
176
|
}
|
|
167
177
|
}
|
|
168
|
-
function P(e, t = {}) {
|
|
169
|
-
let n = N(e), r = h(t);
|
|
170
|
-
return r ? r.format(n) : T(n);
|
|
171
|
-
}
|
|
172
178
|
function F(e, t = {}) {
|
|
173
|
-
let n =
|
|
174
|
-
return
|
|
179
|
+
let n = P(e), r = g(t);
|
|
180
|
+
return r ? r.format(n) : E(n);
|
|
175
181
|
}
|
|
176
182
|
function I(e, t = {}) {
|
|
183
|
+
let n = t.timeZone ?? (e instanceof o.ZonedDateTime ? e.timeZoneId : void 0);
|
|
184
|
+
return m(t, n).formatToParts(new Date(i(e, { timeZone: n }).epochMilliseconds));
|
|
185
|
+
}
|
|
186
|
+
function L(e, t = {}) {
|
|
177
187
|
let { unit: n, value: r } = e;
|
|
178
188
|
return `${t.locale ? new Intl.NumberFormat(t.locale).format(r) : String(r)} ${r === 1 ? n : `${n}s`}`;
|
|
179
189
|
}
|
|
180
190
|
//#endregion
|
|
181
|
-
export {
|
|
191
|
+
export { O as format, F as formatDuration, j as formatInstant, I as formatParts, k as formatRange, A as formatRangeParts, N as formatRelative, M as formatZoned, L as humanize, P as parseDuration };
|
|
182
192
|
|
|
183
193
|
//# sourceMappingURL=format.js.map
|
package/dist/format.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"format.js","names":[],"sources":["../src/format.ts"],"sourcesContent":["import { Temporal } from '@js-temporal/polyfill';\nimport { toInstant, toZoned } from './_convert';\nimport { inferTimeZone } from './_tz';\nimport { fail, TempoInvalidInputError } from './errors';\nimport type {\n DurationFormatOptions,\n FormatOptions,\n FormatPattern,\n RelativeFormatOptions,\n RelativeTimeInput,\n TimeDiffResult,\n TimeInput,\n TimeZoneOptions,\n} from './types';\n\n// ─── Formatter types ──────────────────────────────────────────────────────────\n\ntype DurationFormatter = { format(value: Temporal.Duration): string };\ntype DurationFormatterConstructor = new (\n locales?: Intl.LocalesArgument,\n options?: { style?: 'digital' | 'long' | 'narrow' | 'short' },\n) => DurationFormatter;\n\n// ─── Formatter caches ─────────────────────────────────────────────────────────\n\nconst FORMATTER_CACHE_MAX = 128;\n\nfunction cappedGetOrCreate<V>(cache: Map<string, V>, key: string, factory: () => V): V {\n const cached = cache.get(key);\n\n if (cached !== undefined) return cached;\n\n if (cache.size >= FORMATTER_CACHE_MAX) {\n const oldest = cache.keys().next().value;\n\n if (oldest !== undefined) cache.delete(oldest);\n }\n\n const value = factory();\n\n cache.set(key, value);\n\n return value;\n}\n\nconst DATE_TIME_FORMATTER_CACHE = new Map<string, Intl.DateTimeFormat>();\nconst RELATIVE_TIME_FORMATTER_CACHE = new Map<string, Intl.RelativeTimeFormat>();\nconst DURATION_FORMATTER_CACHE = new Map<string, DurationFormatter>();\n\n// ─── Format presets ───────────────────────────────────────────────────────────\n\nconst FORMAT_PRESETS: Record<FormatPattern, Intl.DateTimeFormatOptions> = {\n 'date-only': { dateStyle: 'short' },\n long: { dateStyle: 'full', timeStyle: 'long' },\n medium: { dateStyle: 'medium', timeStyle: 'short' },\n short: { dateStyle: 'short', timeStyle: 'short' },\n 'time-only': { timeStyle: 'short' },\n};\n\n// ─── Formatter factory helpers ────────────────────────────────────────────────\n\nfunction serializeIntlOptions(options: Intl.DateTimeFormatOptions): string {\n return JSON.stringify(\n Object.entries(options)\n .filter(([, value]) => value !== undefined)\n .sort(([l], [r]) => l.localeCompare(r))\n .map(([key, value]) => [key, String(value)]),\n );\n}\n\nfunction makeFormatter(options: FormatOptions, fallbackTz?: string): Intl.DateTimeFormat {\n const timeZone = options.timeZone ?? fallbackTz;\n const locale = options.locale;\n\n if (options.intl !== undefined) {\n const cacheKey = `${String(locale ?? '')}|intl|${timeZone ?? ''}|${serializeIntlOptions(options.intl)}`;\n\n return cappedGetOrCreate(DATE_TIME_FORMATTER_CACHE, cacheKey, () => {\n const intlOptions = timeZone !== undefined ? { ...options.intl, timeZone: timeZone } : options.intl;\n\n return new Intl.DateTimeFormat(locale, intlOptions);\n });\n }\n\n const pattern = options.pattern ?? 'medium';\n const cacheKey = `${String(locale ?? '')}|${pattern}|${timeZone ?? ''}`;\n\n return cappedGetOrCreate(\n DATE_TIME_FORMATTER_CACHE,\n cacheKey,\n () => new Intl.DateTimeFormat(locale, { ...FORMAT_PRESETS[pattern], timeZone: timeZone }),\n );\n}\n\nfunction getRelativeFormatter(options: {\n locale?: Intl.LocalesArgument;\n numeric?: Intl.RelativeTimeFormatNumeric;\n style?: Intl.RelativeTimeFormatStyle;\n}): Intl.RelativeTimeFormat {\n const cacheKey = `${String(options.locale ?? '')}|${options.numeric ?? 'auto'}|${options.style ?? 'long'}`;\n\n return cappedGetOrCreate(\n RELATIVE_TIME_FORMATTER_CACHE,\n cacheKey,\n () =>\n new Intl.RelativeTimeFormat(options.locale, {\n numeric: options.numeric ?? 'auto',\n style: options.style ?? 'long',\n }),\n );\n}\n\nfunction getDurationFormatter(options: {\n locale?: Intl.LocalesArgument;\n style?: 'digital' | 'long' | 'narrow' | 'short';\n}): DurationFormatter | null {\n const IntlWithDurationFormat = Intl as typeof Intl & { DurationFormat?: DurationFormatterConstructor };\n\n if (!IntlWithDurationFormat.DurationFormat) return null;\n\n const cacheKey = `${String(options.locale ?? '')}|${options.style ?? ''}`;\n\n return cappedGetOrCreate(\n DURATION_FORMATTER_CACHE,\n cacheKey,\n () => new IntlWithDurationFormat.DurationFormat!(options.locale, { style: options.style }),\n );\n}\n\n// ─── Time scale constants ─────────────────────────────────────────────────────\n\nconst SECONDS_PER_MINUTE = 60;\nconst SECONDS_PER_HOUR = 3_600;\nconst SECONDS_PER_DAY = 86_400;\nconst SECONDS_PER_WEEK = 604_800;\nconst SECONDS_PER_MONTH = 2_629_800; // ≈ 30.4375 days × 86400\nconst SECONDS_PER_YEAR = 31_557_600; // 365.25 days × 86400\n\n// ─── Relative time helpers ────────────────────────────────────────────────────\n\nconst RELATIVE_UNITS: ReadonlyArray<{ scale: number; thresholdToPromote: number; unit: Intl.RelativeTimeFormatUnit }> =\n [\n { scale: 1, thresholdToPromote: SECONDS_PER_MINUTE, unit: 'second' },\n { scale: SECONDS_PER_MINUTE, thresholdToPromote: SECONDS_PER_HOUR / SECONDS_PER_MINUTE, unit: 'minute' },\n { scale: SECONDS_PER_HOUR, thresholdToPromote: SECONDS_PER_DAY / SECONDS_PER_HOUR, unit: 'hour' },\n { scale: SECONDS_PER_DAY, thresholdToPromote: SECONDS_PER_WEEK / SECONDS_PER_DAY, unit: 'day' },\n { scale: SECONDS_PER_WEEK, thresholdToPromote: SECONDS_PER_MONTH / SECONDS_PER_WEEK, unit: 'week' },\n { scale: SECONDS_PER_MONTH, thresholdToPromote: 12, unit: 'month' },\n { scale: SECONDS_PER_YEAR, thresholdToPromote: Number.POSITIVE_INFINITY, unit: 'year' },\n ];\n\nfunction toRelativeUnit(seconds: number): { unit: Intl.RelativeTimeFormatUnit; value: number } {\n if (!Number.isFinite(seconds)) fail('formatRelative received a non-finite time difference.');\n\n const roundedSeconds = Math.round(seconds);\n\n for (const { scale, thresholdToPromote, unit } of RELATIVE_UNITS) {\n const value = Math.round(roundedSeconds / scale);\n\n if (Math.abs(value) < thresholdToPromote) return { unit, value };\n }\n\n return { unit: 'year', value: Math.round(roundedSeconds / SECONDS_PER_YEAR) };\n}\n\n// ─── Duration fallback renderer ───────────────────────────────────────────────\n\n// All English duration unit names follow the same pluralization rule: singular = plural.slice(0, -1)\nconst DURATION_UNITS = [\n 'years',\n 'months',\n 'weeks',\n 'days',\n 'hours',\n 'minutes',\n 'seconds',\n 'milliseconds',\n 'microseconds',\n 'nanoseconds',\n] as const satisfies ReadonlyArray<keyof Temporal.Duration>;\n\n// English-only fallback; runs only when Intl.DurationFormat is unavailable in the runtime.\nfunction buildDurationFallback(duration: Temporal.Duration): string {\n const parts: string[] = [];\n\n for (const unit of DURATION_UNITS) {\n const value = Math.abs(duration[unit] as number);\n\n if (value !== 0) parts.push(`${value} ${value === 1 ? unit.slice(0, -1) : unit}`);\n }\n\n return parts.length === 0 ? '0 seconds' : parts.join(', ');\n}\n\n// ─── Private helpers ──────────────────────────────────────────────────────────\n\n/**\n * Resolves a shared display timezone for two-input range functions.\n * Throws when both inputs are `ZonedDateTime` with different zones and no `options.timeZone` override.\n */\nfunction resolveRangeTz(start: TimeInput, end: TimeInput, options: FormatOptions, caller: string): string | undefined {\n if (options.timeZone) return options.timeZone;\n\n const startTz = start instanceof Temporal.ZonedDateTime ? start.timeZoneId : undefined;\n const endTz = end instanceof Temporal.ZonedDateTime ? end.timeZoneId : undefined;\n\n if (startTz && endTz && startTz !== endTz) {\n fail(`${caller} received ZonedDateTime inputs with different time zones. Pass options.timeZone explicitly.`);\n }\n\n return startTz ?? endTz;\n}\n\n// ─── Public API ───────────────────────────────────────────────────────────────\n\n/**\n * Formats `input` using `Intl.DateTimeFormat`. Defaults to `pattern: 'medium'`.\n *\n * Pass `intl` for full `Intl.DateTimeFormatOptions` control (mutually exclusive with `pattern`).\n * The timezone is inferred from a `ZonedDateTime` input or from `options.timeZone`.\n *\n * @example\n * ```ts\n * format(parse('2026-03-21T10:15:30Z', { as: 'instant' }), { locale: 'en-GB', pattern: 'short', timeZone: 'UTC' })\n * // '21/03/2026, 10:15'\n * ```\n */\nexport function format(input: TimeInput, options: FormatOptions = {}): string {\n const timeZone = options.timeZone ?? (input instanceof Temporal.ZonedDateTime ? input.timeZoneId : undefined);\n\n return makeFormatter(options, timeZone).format(new Date(toInstant(input, { timeZone }).epochMilliseconds));\n}\n\n/**\n * Formats a time span between `start` and `end` using `Intl.DateTimeFormat.formatRange`.\n *\n * @example\n * ```ts\n * formatRange(start, end, { locale: 'en-GB', pattern: 'short', timeZone: 'UTC' })\n * // '21/03/2026, 10:00 – 12:00'\n * ```\n */\nexport function formatRange(start: TimeInput, end: TimeInput, options: FormatOptions = {}): string {\n const timeZone = resolveRangeTz(start, end, options, 'formatRange');\n const formatter = makeFormatter(options, timeZone);\n\n return formatter.formatRange(\n new Date(toInstant(start, { timeZone }).epochMilliseconds),\n new Date(toInstant(end, { timeZone }).epochMilliseconds),\n );\n}\n\n/**\n * Returns the raw `Intl.DateTimeRangeFormatPart[]` array for a time span, enabling\n * fine-grained rendering of range start, end, and shared parts separately.\n *\n * @example\n * ```ts\n * formatRangeParts(start, end, { locale: 'en-US', pattern: 'short', timeZone: 'UTC' })\n * // [{ type: 'month', value: '3', source: 'startRange' }, ...]\n * ```\n */\nexport function formatRangeParts(\n start: TimeInput,\n end: TimeInput,\n options: FormatOptions = {},\n): ReturnType<Intl.DateTimeFormat['formatRangeToParts']> {\n const timeZone = resolveRangeTz(start, end, options, 'formatRangeParts');\n const formatter = makeFormatter(options, timeZone);\n\n return formatter.formatRangeToParts(\n new Date(toInstant(start, { timeZone }).epochMilliseconds),\n new Date(toInstant(end, { timeZone }).epochMilliseconds),\n );\n}\n\n/**\n * Serializes `input` to a UTC ISO 8601 instant string (`2026-03-21T10:15:30Z`).\n *\n * Pass `options.timeZone` when `input` is a `PlainDate` or `PlainDateTime`.\n * Inferred from `ZonedDateTime`; ignored for `Instant`.\n *\n * @throws {TempoMissingTzError} When `input` is a `PlainDate` or `PlainDateTime` and `options.timeZone` is omitted.\n * @throws {TempoInvalidTzError} When `options.timeZone` is not a valid IANA timezone or UTC offset.\n *\n * @example\n * ```ts\n * formatInstant(parse('2026-03-21T11:15:30+01:00[Europe/Berlin]', { as: 'zonedDateTime' }))\n * // '2026-03-21T10:15:30Z'\n * ```\n */\nexport function formatInstant(input: TimeInput, options: TimeZoneOptions = {}): string {\n return toInstant(input, options).toString();\n}\n\n/**\n * Serializes `input` to a zoned ISO 8601 string (`2026-03-21T11:15:30+01:00[Europe/Berlin]`).\n *\n * @param options.timeZone - Required when `input` is a `PlainDate`, `PlainDateTime`, or `Instant`.\n * Inferred automatically from a `ZonedDateTime` input.\n *\n * @throws {TempoMissingTzError} When `input` is not a `ZonedDateTime` and `options.timeZone` is omitted.\n * @throws {TempoInvalidTzError} When `options.timeZone` is not a valid IANA timezone or UTC offset.\n *\n * @example\n * ```ts\n * formatZoned(parse('2026-03-21T10:15:30Z', { as: 'instant' }), { timeZone: 'Europe/Berlin' })\n * // '2026-03-21T11:15:30+01:00[Europe/Berlin]'\n *\n * formatZoned(parse('2026-03-21T11:15:30+01:00[Europe/Berlin]', { as: 'zonedDateTime' }))\n * // '2026-03-21T11:15:30+01:00[Europe/Berlin]' (timeZone inferred)\n * ```\n */\nexport function formatZoned(input: TimeInput, options: TimeZoneOptions = {}): string {\n const timeZone = inferTimeZone(input, options);\n\n return toZoned(input, { timeZone }).toString();\n}\n\n/**\n * Formats `input` relative to `options.base` (defaults to now) using `Intl.RelativeTimeFormat`.\n *\n * @example\n * ```ts\n * formatRelative(parse('2026-03-21T12:00:00Z', { as: 'instant' }), {\n * base: parse('2026-03-21T10:00:00Z', { as: 'instant' }),\n * locale: 'en-US',\n * numeric: 'always',\n * })\n * // 'in 2 hours'\n * ```\n */\nexport function formatRelative(input: RelativeTimeInput, options: RelativeFormatOptions = {}): string {\n const target = input instanceof Temporal.Instant ? input : input.toInstant();\n const base = options.base\n ? options.base instanceof Temporal.Instant\n ? options.base\n : options.base.toInstant()\n : Temporal.Now.instant();\n const differenceInSeconds = (target.epochMilliseconds - base.epochMilliseconds) / 1000;\n const { unit, value } = toRelativeUnit(differenceInSeconds);\n\n return getRelativeFormatter(options).format(value, unit);\n}\n\n/**\n * Parses an ISO duration string or `Temporal.DurationLike` into a `Temporal.Duration`.\n *\n * @example\n * ```ts\n * parseDuration('PT2H30M').toString() // 'PT2H30M'\n * parseDuration({ hours: 2, minutes: 30 }).toString() // 'PT2H30M'\n * ```\n */\nexport function parseDuration(input: string | Temporal.DurationLike): Temporal.Duration {\n try {\n return Temporal.Duration.from(input);\n } catch (error) {\n fail(\n `Invalid duration input: \"${String(input)}\". Expected an ISO 8601 duration string or Temporal.DurationLike.`,\n TempoInvalidInputError,\n { cause: error },\n );\n }\n}\n\n/**\n * Formats a duration using `Intl.DurationFormat` when available, falling back to\n * a human-readable plain-English string.\n *\n * @example\n * ```ts\n * formatDuration('PT2H30M', { locale: 'en-US', style: 'long' })\n * // '2 hours, 30 minutes'\n * ```\n */\nexport function formatDuration(input: string | Temporal.DurationLike, options: DurationFormatOptions = {}): string {\n const duration = parseDuration(input);\n const formatter = getDurationFormatter(options);\n\n if (formatter) return formatter.format(duration);\n\n return buildDurationFallback(duration);\n}\n\n/**\n * Returns the raw `Intl.DateTimeFormatPart[]` array for `input`, enabling\n * custom rendering where individual parts (year, month, day, etc.) need\n * to be styled or composed differently.\n *\n * @example\n * ```ts\n * formatParts(parse('2026-03-21T10:15:30Z', { as: 'instant' }), { pattern: 'medium', timeZone: 'UTC' })\n * // [{ type: 'month', value: 'Mar' }, { type: 'literal', value: ' ' }, ...]\n * ```\n */\nexport function formatParts(input: TimeInput, options: FormatOptions = {}): Intl.DateTimeFormatPart[] {\n const timeZone = options.timeZone ?? (input instanceof Temporal.ZonedDateTime ? input.timeZoneId : undefined);\n\n return makeFormatter(options, timeZone).formatToParts(new Date(toInstant(input, { timeZone }).epochMilliseconds));\n}\n\n/**\n * Converts a `TimeDiffResult` to a human-readable string.\n * Uses the singular unit name when value is 1, plural (unit + 's') otherwise.\n *\n * Pass `options.locale` to localize the numeric part via `Intl.NumberFormat`.\n * Unit names remain English — for fully localized output use {@link formatRelative}\n * or {@link formatDuration} instead.\n *\n * @example\n * ```ts\n * humanize({ unit: 'day', value: 1 }) // '1 day'\n * humanize({ unit: 'day', value: 3 }) // '3 days'\n * humanize({ unit: 'day', value: 3 }, { locale: 'ar' }) // '٣ days'\n * humanize({ unit: 'millisecond', value: 0 }) // '0 milliseconds'\n * ```\n */\nexport function humanize(diff: TimeDiffResult, options: { locale?: Intl.LocalesArgument } = {}): string {\n const { unit, value } = diff;\n const formatted = options.locale ? new Intl.NumberFormat(options.locale).format(value) : String(value);\n\n return `${formatted} ${value === 1 ? unit : `${unit}s`}`;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"format.js","names":[],"sources":["../src/format.ts"],"sourcesContent":["import { Temporal } from '@js-temporal/polyfill';\nimport { toInstant, toZoned } from './_convert';\nimport { inferTimeZone, validateTimeZone } from './_tz';\nimport { fail, TempoInvalidInputError } from './errors';\nimport type {\n DurationFormatOptions,\n FormatOptions,\n FormatPattern,\n RelativeFormatOptions,\n RelativeTimeInput,\n TimeDiffResult,\n TimeInput,\n TimeZoneOptions,\n} from './types';\n\n// ─── Formatter types ──────────────────────────────────────────────────────────\n\ntype DurationFormatter = { format(value: Temporal.Duration): string };\ntype DurationFormatterConstructor = new (\n locales?: Intl.LocalesArgument,\n options?: { style?: 'digital' | 'long' | 'narrow' | 'short' },\n) => DurationFormatter;\n\n// ─── Formatter caches ─────────────────────────────────────────────────────────\n\nconst FORMATTER_CACHE_MAX = 128;\n\nfunction cappedGetOrCreate<V>(cache: Map<string, V>, key: string, factory: () => V): V {\n const cached = cache.get(key);\n\n if (cached !== undefined) return cached;\n\n if (cache.size >= FORMATTER_CACHE_MAX) {\n const oldest = cache.keys().next().value;\n\n if (oldest !== undefined) cache.delete(oldest);\n }\n\n const value = factory();\n\n cache.set(key, value);\n\n return value;\n}\n\nconst DATE_TIME_FORMATTER_CACHE = new Map<string, Intl.DateTimeFormat>();\nconst RELATIVE_TIME_FORMATTER_CACHE = new Map<string, Intl.RelativeTimeFormat>();\nconst DURATION_FORMATTER_CACHE = new Map<string, DurationFormatter>();\n\n// ─── Format presets ───────────────────────────────────────────────────────────\n\nconst FORMAT_PRESETS: Record<FormatPattern, Intl.DateTimeFormatOptions> = {\n 'date-only': { dateStyle: 'short' },\n long: { dateStyle: 'full', timeStyle: 'long' },\n medium: { dateStyle: 'medium', timeStyle: 'short' },\n short: { dateStyle: 'short', timeStyle: 'short' },\n 'time-only': { timeStyle: 'short' },\n};\n\n// ─── Formatter factory helpers ────────────────────────────────────────────────\n\nfunction serializeIntlOptions(options: Intl.DateTimeFormatOptions): string {\n return JSON.stringify(\n Object.entries(options)\n .filter(([, value]) => value !== undefined)\n .sort(([l], [r]) => l.localeCompare(r))\n .map(([key, value]) => [key, String(value)]),\n );\n}\n\nfunction makeFormatter(options: FormatOptions, fallbackTz?: string): Intl.DateTimeFormat {\n const timeZone = options.timeZone ?? fallbackTz;\n const locale = options.locale;\n\n if (options.intl !== undefined) {\n const cacheKey = `${String(locale ?? '')}|intl|${timeZone ?? ''}|${serializeIntlOptions(options.intl)}`;\n\n return cappedGetOrCreate(DATE_TIME_FORMATTER_CACHE, cacheKey, () => {\n const intlOptions = timeZone !== undefined ? { ...options.intl, timeZone: timeZone } : options.intl;\n\n return new Intl.DateTimeFormat(locale, intlOptions);\n });\n }\n\n const pattern = options.pattern ?? 'medium';\n const cacheKey = `${String(locale ?? '')}|${pattern}|${timeZone ?? ''}`;\n\n return cappedGetOrCreate(\n DATE_TIME_FORMATTER_CACHE,\n cacheKey,\n () => new Intl.DateTimeFormat(locale, { ...FORMAT_PRESETS[pattern], timeZone: timeZone }),\n );\n}\n\nfunction getRelativeFormatter(options: {\n locale?: Intl.LocalesArgument;\n numeric?: Intl.RelativeTimeFormatNumeric;\n style?: Intl.RelativeTimeFormatStyle;\n}): Intl.RelativeTimeFormat {\n const cacheKey = `${String(options.locale ?? '')}|${options.numeric ?? 'auto'}|${options.style ?? 'long'}`;\n\n return cappedGetOrCreate(\n RELATIVE_TIME_FORMATTER_CACHE,\n cacheKey,\n () =>\n new Intl.RelativeTimeFormat(options.locale, {\n numeric: options.numeric ?? 'auto',\n style: options.style ?? 'long',\n }),\n );\n}\n\nfunction getDurationFormatter(options: {\n locale?: Intl.LocalesArgument;\n style?: 'digital' | 'long' | 'narrow' | 'short';\n}): DurationFormatter | null {\n const IntlWithDurationFormat = Intl as typeof Intl & { DurationFormat?: DurationFormatterConstructor };\n\n if (!IntlWithDurationFormat.DurationFormat) return null;\n\n const cacheKey = `${String(options.locale ?? '')}|${options.style ?? ''}`;\n\n return cappedGetOrCreate(\n DURATION_FORMATTER_CACHE,\n cacheKey,\n () => new IntlWithDurationFormat.DurationFormat!(options.locale, { style: options.style }),\n );\n}\n\n// ─── Time scale constants ─────────────────────────────────────────────────────\n\nconst SECONDS_PER_MINUTE = 60;\nconst SECONDS_PER_HOUR = 3_600;\nconst SECONDS_PER_DAY = 86_400;\nconst SECONDS_PER_WEEK = 604_800; // 7 × 86400 — fixed\n\n// ─── Relative time helpers ────────────────────────────────────────────────────\n\nconst RELATIVE_UNITS: ReadonlyArray<{ scale: number; thresholdToPromote: number; unit: Intl.RelativeTimeFormatUnit }> =\n [\n { scale: 1, thresholdToPromote: SECONDS_PER_MINUTE, unit: 'second' },\n { scale: SECONDS_PER_MINUTE, thresholdToPromote: SECONDS_PER_HOUR / SECONDS_PER_MINUTE, unit: 'minute' },\n { scale: SECONDS_PER_HOUR, thresholdToPromote: SECONDS_PER_DAY / SECONDS_PER_HOUR, unit: 'hour' },\n { scale: SECONDS_PER_DAY, thresholdToPromote: SECONDS_PER_WEEK / SECONDS_PER_DAY, unit: 'day' },\n { scale: SECONDS_PER_WEEK, thresholdToPromote: Number.POSITIVE_INFINITY, unit: 'week' },\n ];\n\nfunction toRelativeUnit(seconds: number): { unit: Intl.RelativeTimeFormatUnit; value: number } {\n if (!Number.isFinite(seconds)) fail('formatRelative received a non-finite time difference.');\n\n const roundedSeconds = Math.round(seconds);\n\n for (const { scale, thresholdToPromote, unit } of RELATIVE_UNITS) {\n const value = Math.round(roundedSeconds / scale);\n\n if (Math.abs(value) < thresholdToPromote) return { unit, value };\n }\n\n return { unit: 'week', value: Math.round(roundedSeconds / SECONDS_PER_WEEK) };\n}\n\nfunction resolveRelativeTimeZone(input: RelativeTimeInput, base: RelativeTimeInput, timeZone?: string): string {\n if (timeZone) return validateTimeZone(timeZone);\n\n const inputTimeZone = input instanceof Temporal.ZonedDateTime ? input.timeZoneId : undefined;\n const baseTimeZone = base instanceof Temporal.ZonedDateTime ? base.timeZoneId : undefined;\n\n if (inputTimeZone && baseTimeZone && inputTimeZone !== baseTimeZone) {\n fail('formatRelative received ZonedDateTime inputs with different time zones. Pass options.timeZone explicitly.');\n }\n\n return inputTimeZone ?? baseTimeZone ?? 'UTC';\n}\n\nfunction toCalendarRelativeUnit(\n input: RelativeTimeInput,\n base: RelativeTimeInput,\n timeZone: string,\n): { unit: Intl.RelativeTimeFormatUnit; value: number } {\n const target = toZoned(input, { timeZone });\n const reference = toZoned(base, { timeZone });\n const comparison = Temporal.ZonedDateTime.compare(target, reference);\n\n if (comparison === 0) return { unit: 'second', value: 0 };\n\n const duration =\n comparison > 0\n ? target.since(reference, { largestUnit: 'year' })\n : reference.since(target, { largestUnit: 'year' });\n const direction = comparison > 0 ? 1 : -1;\n\n if (duration.years) return { unit: 'year', value: duration.years * direction };\n\n if (duration.months) return { unit: 'month', value: duration.months * direction };\n\n return toRelativeUnit((input.epochMilliseconds - base.epochMilliseconds) / 1000);\n}\n\n// ─── Duration fallback renderer ───────────────────────────────────────────────\n\n// All English duration unit names follow the same pluralization rule: singular = plural.slice(0, -1)\nconst DURATION_UNITS = [\n 'years',\n 'months',\n 'weeks',\n 'days',\n 'hours',\n 'minutes',\n 'seconds',\n 'milliseconds',\n 'microseconds',\n 'nanoseconds',\n] as const satisfies ReadonlyArray<keyof Temporal.Duration>;\n\n// English-only fallback; runs only when Intl.DurationFormat is unavailable in the runtime.\nfunction buildDurationFallback(duration: Temporal.Duration): string {\n const parts: string[] = [];\n\n for (const unit of DURATION_UNITS) {\n const value = Math.abs(duration[unit] as number);\n\n if (value !== 0) parts.push(`${value} ${value === 1 ? unit.slice(0, -1) : unit}`);\n }\n\n return parts.length === 0 ? '0 seconds' : parts.join(', ');\n}\n\n// ─── Private helpers ──────────────────────────────────────────────────────────\n\n/**\n * Resolves a shared display timezone for two-input range functions.\n * Throws when both inputs are `ZonedDateTime` with different zones and no `options.timeZone` override.\n */\nfunction resolveRangeTz(start: TimeInput, end: TimeInput, options: FormatOptions, caller: string): string | undefined {\n if (options.timeZone) return options.timeZone;\n\n const startTz = start instanceof Temporal.ZonedDateTime ? start.timeZoneId : undefined;\n const endTz = end instanceof Temporal.ZonedDateTime ? end.timeZoneId : undefined;\n\n if (startTz && endTz && startTz !== endTz) {\n fail(`${caller} received ZonedDateTime inputs with different time zones. Pass options.timeZone explicitly.`);\n }\n\n return startTz ?? endTz;\n}\n\n// ─── Public API ───────────────────────────────────────────────────────────────\n\n/**\n * Formats `input` using `Intl.DateTimeFormat`. Defaults to `pattern: 'medium'`.\n *\n * Pass `intl` for full `Intl.DateTimeFormatOptions` control (mutually exclusive with `pattern`).\n * The timezone is inferred from a `ZonedDateTime` input or from `options.timeZone`.\n *\n * @example\n * ```ts\n * format(parse('2026-03-21T10:15:30Z', { as: 'instant' }), { locale: 'en-GB', pattern: 'short', timeZone: 'UTC' })\n * // '21/03/2026, 10:15'\n * ```\n */\nexport function format(input: TimeInput, options: FormatOptions = {}): string {\n const timeZone = options.timeZone ?? (input instanceof Temporal.ZonedDateTime ? input.timeZoneId : undefined);\n\n return makeFormatter(options, timeZone).format(new Date(toInstant(input, { timeZone }).epochMilliseconds));\n}\n\n/**\n * Formats a time span between `start` and `end` using `Intl.DateTimeFormat.formatRange`.\n *\n * @example\n * ```ts\n * formatRange(start, end, { locale: 'en-GB', pattern: 'short', timeZone: 'UTC' })\n * // '21/03/2026, 10:00 – 12:00'\n * ```\n */\nexport function formatRange(start: TimeInput, end: TimeInput, options: FormatOptions = {}): string {\n const timeZone = resolveRangeTz(start, end, options, 'formatRange');\n const formatter = makeFormatter(options, timeZone);\n\n return formatter.formatRange(\n new Date(toInstant(start, { timeZone }).epochMilliseconds),\n new Date(toInstant(end, { timeZone }).epochMilliseconds),\n );\n}\n\n/**\n * Returns the raw `Intl.DateTimeRangeFormatPart[]` array for a time span, enabling\n * fine-grained rendering of range start, end, and shared parts separately.\n *\n * @example\n * ```ts\n * formatRangeParts(start, end, { locale: 'en-US', pattern: 'short', timeZone: 'UTC' })\n * // [{ type: 'month', value: '3', source: 'startRange' }, ...]\n * ```\n */\nexport function formatRangeParts(\n start: TimeInput,\n end: TimeInput,\n options: FormatOptions = {},\n): ReturnType<Intl.DateTimeFormat['formatRangeToParts']> {\n const timeZone = resolveRangeTz(start, end, options, 'formatRangeParts');\n const formatter = makeFormatter(options, timeZone);\n\n return formatter.formatRangeToParts(\n new Date(toInstant(start, { timeZone }).epochMilliseconds),\n new Date(toInstant(end, { timeZone }).epochMilliseconds),\n );\n}\n\n/**\n * Serializes `input` to a UTC ISO 8601 instant string (`2026-03-21T10:15:30Z`).\n *\n * Pass `options.timeZone` when `input` is a `PlainDate` or `PlainDateTime`.\n * Inferred from `ZonedDateTime`; ignored for `Instant`.\n *\n * @throws {TempoMissingTzError} When `input` is a `PlainDate` or `PlainDateTime` and `options.timeZone` is omitted.\n * @throws {TempoInvalidTzError} When `options.timeZone` is not a valid IANA timezone or UTC offset.\n *\n * @example\n * ```ts\n * formatInstant(parse('2026-03-21T11:15:30+01:00[Europe/Berlin]', { as: 'zonedDateTime' }))\n * // '2026-03-21T10:15:30Z'\n * ```\n */\nexport function formatInstant(input: TimeInput, options: TimeZoneOptions = {}): string {\n return toInstant(input, options).toString();\n}\n\n/**\n * Serializes `input` to a zoned ISO 8601 string (`2026-03-21T11:15:30+01:00[Europe/Berlin]`).\n *\n * @param options.timeZone - Required when `input` is a `PlainDate`, `PlainDateTime`, or `Instant`.\n * Inferred automatically from a `ZonedDateTime` input.\n *\n * @throws {TempoMissingTzError} When `input` is not a `ZonedDateTime` and `options.timeZone` is omitted.\n * @throws {TempoInvalidTzError} When `options.timeZone` is not a valid IANA timezone or UTC offset.\n *\n * @example\n * ```ts\n * formatZoned(parse('2026-03-21T10:15:30Z', { as: 'instant' }), { timeZone: 'Europe/Berlin' })\n * // '2026-03-21T11:15:30+01:00[Europe/Berlin]'\n *\n * formatZoned(parse('2026-03-21T11:15:30+01:00[Europe/Berlin]', { as: 'zonedDateTime' }))\n * // '2026-03-21T11:15:30+01:00[Europe/Berlin]' (timeZone inferred)\n * ```\n */\nexport function formatZoned(input: TimeInput, options: TimeZoneOptions = {}): string {\n const timeZone = inferTimeZone(input, options);\n\n return toZoned(input, { timeZone }).toString();\n}\n\n/**\n * Formats `input` relative to `options.base` (defaults to now) using `Intl.RelativeTimeFormat`.\n *\n * Complete calendar months and years are resolved in the requested or inferred timezone.\n * Shorter spans use fixed elapsed-time units.\n *\n * @example\n * ```ts\n * formatRelative(parse('2026-03-21T12:00:00Z', { as: 'instant' }), {\n * base: parse('2026-03-21T10:00:00Z', { as: 'instant' }),\n * locale: 'en-US',\n * numeric: 'always',\n * })\n * // 'in 2 hours'\n * ```\n */\nexport function formatRelative(input: RelativeTimeInput, options: RelativeFormatOptions = {}): string {\n const base = options.base ?? Temporal.Now.instant();\n const timeZone = resolveRelativeTimeZone(input, base, options.timeZone);\n const { unit, value } = toCalendarRelativeUnit(input, base, timeZone);\n\n return getRelativeFormatter(options).format(value, unit);\n}\n\n/**\n * Parses an ISO duration string or `Temporal.DurationLike` into a `Temporal.Duration`.\n *\n * @example\n * ```ts\n * parseDuration('PT2H30M').toString() // 'PT2H30M'\n * parseDuration({ hours: 2, minutes: 30 }).toString() // 'PT2H30M'\n * ```\n */\nexport function parseDuration(input: string | Temporal.DurationLike): Temporal.Duration {\n try {\n return Temporal.Duration.from(input);\n } catch (error) {\n fail(\n `Invalid duration input: \"${String(input)}\". Expected an ISO 8601 duration string or Temporal.DurationLike.`,\n TempoInvalidInputError,\n { cause: error },\n );\n }\n}\n\n/**\n * Formats a duration using `Intl.DurationFormat` when available, falling back to\n * a human-readable plain-English string.\n *\n * @example\n * ```ts\n * formatDuration('PT2H30M', { locale: 'en-US', style: 'long' })\n * // '2 hours, 30 minutes'\n * ```\n */\nexport function formatDuration(input: string | Temporal.DurationLike, options: DurationFormatOptions = {}): string {\n const duration = parseDuration(input);\n const formatter = getDurationFormatter(options);\n\n if (formatter) return formatter.format(duration);\n\n return buildDurationFallback(duration);\n}\n\n/**\n * Returns the raw `Intl.DateTimeFormatPart[]` array for `input`, enabling\n * custom rendering where individual parts (year, month, day, etc.) need\n * to be styled or composed differently.\n *\n * @example\n * ```ts\n * formatParts(parse('2026-03-21T10:15:30Z', { as: 'instant' }), { pattern: 'medium', timeZone: 'UTC' })\n * // [{ type: 'month', value: 'Mar' }, { type: 'literal', value: ' ' }, ...]\n * ```\n */\nexport function formatParts(input: TimeInput, options: FormatOptions = {}): Intl.DateTimeFormatPart[] {\n const timeZone = options.timeZone ?? (input instanceof Temporal.ZonedDateTime ? input.timeZoneId : undefined);\n\n return makeFormatter(options, timeZone).formatToParts(new Date(toInstant(input, { timeZone }).epochMilliseconds));\n}\n\n/**\n * Converts a `TimeDiffResult` to a human-readable string.\n * Uses the singular unit name when value is 1, plural (unit + 's') otherwise.\n *\n * Pass `options.locale` to localize the numeric part via `Intl.NumberFormat`.\n * Unit names remain English — for fully localized output use {@link formatRelative}\n * or {@link formatDuration} instead.\n *\n * @example\n * ```ts\n * humanize({ unit: 'day', value: 1 }) // '1 day'\n * humanize({ unit: 'day', value: 3 }) // '3 days'\n * humanize({ unit: 'day', value: 3 }, { locale: 'ar' }) // '٣ days'\n * humanize({ unit: 'millisecond', value: 0 }) // '0 milliseconds'\n * ```\n */\nexport function humanize(diff: TimeDiffResult, options: { locale?: Intl.LocalesArgument } = {}): string {\n const { unit, value } = diff;\n const formatted = options.locale ? new Intl.NumberFormat(options.locale).format(value) : String(value);\n\n return `${formatted} ${value === 1 ? unit : `${unit}s`}`;\n}\n"],"mappings":"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"}
|
package/dist/range.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const e=require("./errors.cjs"),t=require("./_tz.cjs"),n=require("./_convert.cjs");let r=require("@js-temporal/polyfill");function i(i,o,s,c={}){let l=t.inferTimeZone(i,c),u=n.toZoned(i,{timeZone:l}),d=n.toZoned(o,{timeZone:l});if(r.Temporal.ZonedDateTime.compare(u.add(s),u)<=0)throw new e.TempoInvalidInputError(`dateRange: step must advance time forward.`);return a(u,d,s)}function*a(
|
|
1
|
+
const e=require("./errors.cjs"),t=require("./_tz.cjs"),n=require("./_convert.cjs");let r=require("@js-temporal/polyfill");function i(i,o,s,c={}){let l=t.inferTimeZone(i,c),u=n.toZoned(i,{timeZone:l}),d=n.toZoned(o,{timeZone:l});if(r.Temporal.ZonedDateTime.compare(u.add(s),u)<=0)throw new e.TempoInvalidInputError(`dateRange: step must advance time forward.`);return a(u,d,s)}function*a(t,n,i){for(let a=t;r.Temporal.ZonedDateTime.compare(a,n)<=0;){yield a;let t=a.add(i);if(r.Temporal.ZonedDateTime.compare(t,a)<=0)throw new e.TempoInvalidInputError(`dateRange: step must advance time forward.`);a=t}}function o(r,i,a={}){let o=i.interval??1;if(!Number.isSafeInteger(o)||o<=0)throw new e.TempoInvalidInputError(`recurrence: interval must be a positive safe integer.`);if(i.count!==void 0&&(!Number.isSafeInteger(i.count)||i.count<0))throw new e.TempoInvalidInputError(`recurrence: count must be a non-negative safe integer.`);let c=t.inferTimeZone(r,a),l=i.frequency===`daily`?{days:o}:i.frequency===`weekly`?{weeks:o}:i.frequency===`monthly`?{months:o}:{years:o},u=i.until?n.toInstant(i.until,{timeZone:c}):void 0;return s(n.toZoned(r,{timeZone:c}),l,i.count,u)}function*s(e,t,n,i){for(let a=e,o=0;n===void 0||o<n;a=a.add(t),o++){if(i&&r.Temporal.Instant.compare(a.toInstant(),i)>0)return;yield a}}exports.dateRange=i,exports.recurrence=o;
|
|
2
2
|
//# sourceMappingURL=range.cjs.map
|
package/dist/range.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"range.cjs","names":[],"sources":["../src/range.ts"],"sourcesContent":["import { Temporal } from '@js-temporal/polyfill';\nimport { toInstant, toZoned } from './_convert';\nimport { inferTimeZone } from './_tz';\nimport { TempoInvalidInputError } from './errors';\nimport type { RecurrenceRule, TimeInput, TimeZoneOptions } from './types';\n\nexport function dateRange(\n start: TimeInput,\n end: TimeInput,\n step: Temporal.DurationLike,\n options: TimeZoneOptions = {},\n): Generator<Temporal.ZonedDateTime> {\n const timeZone = inferTimeZone(start, options);\n const startZoned = toZoned(start, { timeZone });\n const endZoned = toZoned(end, { timeZone });\n\n if (Temporal.ZonedDateTime.compare(startZoned.add(step), startZoned) <= 0) {\n throw new TempoInvalidInputError('dateRange: step must advance time forward.');\n }\n\n return generateRange(startZoned, endZoned, step);\n}\n\nfunction* generateRange(\n start: Temporal.ZonedDateTime,\n end: Temporal.ZonedDateTime,\n step: Temporal.DurationLike,\n): Generator<Temporal.ZonedDateTime> {\n for (let current = start; Temporal.ZonedDateTime.compare(current, end) <= 0; current = current.add(step)) {\n
|
|
1
|
+
{"version":3,"file":"range.cjs","names":[],"sources":["../src/range.ts"],"sourcesContent":["import { Temporal } from '@js-temporal/polyfill';\nimport { toInstant, toZoned } from './_convert';\nimport { inferTimeZone } from './_tz';\nimport { TempoInvalidInputError } from './errors';\nimport type { RecurrenceRule, TimeInput, TimeZoneOptions } from './types';\n\nexport function dateRange(\n start: TimeInput,\n end: TimeInput,\n step: Temporal.DurationLike,\n options: TimeZoneOptions = {},\n): Generator<Temporal.ZonedDateTime> {\n const timeZone = inferTimeZone(start, options);\n const startZoned = toZoned(start, { timeZone });\n const endZoned = toZoned(end, { timeZone });\n\n if (Temporal.ZonedDateTime.compare(startZoned.add(step), startZoned) <= 0) {\n throw new TempoInvalidInputError('dateRange: step must advance time forward.');\n }\n\n return generateRange(startZoned, endZoned, step);\n}\n\nfunction* generateRange(\n start: Temporal.ZonedDateTime,\n end: Temporal.ZonedDateTime,\n step: Temporal.DurationLike,\n): Generator<Temporal.ZonedDateTime> {\n for (let current = start; Temporal.ZonedDateTime.compare(current, end) <= 0; ) {\n yield current;\n\n const next = current.add(step);\n\n if (Temporal.ZonedDateTime.compare(next, current) <= 0) {\n throw new TempoInvalidInputError('dateRange: step must advance time forward.');\n }\n\n current = next;\n }\n}\n\nexport function recurrence(\n start: TimeInput,\n rule: RecurrenceRule,\n options: TimeZoneOptions = {},\n): Generator<Temporal.ZonedDateTime> {\n const interval = rule.interval ?? 1;\n\n if (!Number.isSafeInteger(interval) || interval <= 0) {\n throw new TempoInvalidInputError('recurrence: interval must be a positive safe integer.');\n }\n\n if (rule.count !== undefined && (!Number.isSafeInteger(rule.count) || rule.count < 0)) {\n throw new TempoInvalidInputError('recurrence: count must be a non-negative safe integer.');\n }\n\n const timeZone = inferTimeZone(start, options);\n const step =\n rule.frequency === 'daily'\n ? { days: interval }\n : rule.frequency === 'weekly'\n ? { weeks: interval }\n : rule.frequency === 'monthly'\n ? { months: interval }\n : { years: interval };\n const until = rule.until ? toInstant(rule.until, { timeZone }) : undefined;\n\n return generateRecurrence(toZoned(start, { timeZone }), step, rule.count, until);\n}\n\nfunction* generateRecurrence(\n start: Temporal.ZonedDateTime,\n step: Temporal.DurationLike,\n count: number | undefined,\n until: Temporal.Instant | undefined,\n): Generator<Temporal.ZonedDateTime> {\n for (\n let current = start, emitted = 0;\n count === undefined || emitted < count;\n current = current.add(step), emitted++\n ) {\n if (until && Temporal.Instant.compare(current.toInstant(), until) > 0) return;\n\n yield current;\n }\n}\n"],"mappings":"0HAMA,SAAgB,EACd,EACA,EACA,EACA,EAA2B,CAAC,EACO,CACnC,IAAM,EAAW,EAAA,cAAc,EAAO,CAAO,EACvC,EAAa,EAAA,QAAQ,EAAO,CAAE,UAAS,CAAC,EACxC,EAAW,EAAA,QAAQ,EAAK,CAAE,UAAS,CAAC,EAE1C,GAAI,EAAA,SAAS,cAAc,QAAQ,EAAW,IAAI,CAAI,EAAG,CAAU,GAAK,EACtE,MAAM,IAAI,EAAA,uBAAuB,4CAA4C,EAG/E,OAAO,EAAc,EAAY,EAAU,CAAI,CACjD,CAEA,SAAU,EACR,EACA,EACA,EACmC,CACnC,IAAK,IAAI,EAAU,EAAO,EAAA,SAAS,cAAc,QAAQ,EAAS,CAAG,GAAK,GAAK,CAC7E,MAAM,EAEN,IAAM,EAAO,EAAQ,IAAI,CAAI,EAE7B,GAAI,EAAA,SAAS,cAAc,QAAQ,EAAM,CAAO,GAAK,EACnD,MAAM,IAAI,EAAA,uBAAuB,4CAA4C,EAG/E,EAAU,CACZ,CACF,CAEA,SAAgB,EACd,EACA,EACA,EAA2B,CAAC,EACO,CACnC,IAAM,EAAW,EAAK,UAAY,EAElC,GAAI,CAAC,OAAO,cAAc,CAAQ,GAAK,GAAY,EACjD,MAAM,IAAI,EAAA,uBAAuB,uDAAuD,EAG1F,GAAI,EAAK,QAAU,IAAA,KAAc,CAAC,OAAO,cAAc,EAAK,KAAK,GAAK,EAAK,MAAQ,GACjF,MAAM,IAAI,EAAA,uBAAuB,wDAAwD,EAG3F,IAAM,EAAW,EAAA,cAAc,EAAO,CAAO,EACvC,EACJ,EAAK,YAAc,QACf,CAAE,KAAM,CAAS,EACjB,EAAK,YAAc,SACjB,CAAE,MAAO,CAAS,EAClB,EAAK,YAAc,UACjB,CAAE,OAAQ,CAAS,EACnB,CAAE,MAAO,CAAS,EACtB,EAAQ,EAAK,MAAQ,EAAA,UAAU,EAAK,MAAO,CAAE,UAAS,CAAC,EAAI,IAAA,GAEjE,OAAO,EAAmB,EAAA,QAAQ,EAAO,CAAE,UAAS,CAAC,EAAG,EAAM,EAAK,MAAO,CAAK,CACjF,CAEA,SAAU,EACR,EACA,EACA,EACA,EACmC,CACnC,IACE,IAAI,EAAU,EAAO,EAAU,EAC/B,IAAU,IAAA,IAAa,EAAU,EACjC,EAAU,EAAQ,IAAI,CAAI,EAAG,IAC7B,CACA,GAAI,GAAS,EAAA,SAAS,QAAQ,QAAQ,EAAQ,UAAU,EAAG,CAAK,EAAI,EAAG,OAEvE,MAAM,CACR,CACF"}
|
package/dist/range.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"range.d.ts","sourceRoot":"","sources":["../src/range.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAIjD,OAAO,KAAK,EAAE,cAAc,EAAE,SAAS,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAE1E,wBAAgB,SAAS,CACvB,KAAK,EAAE,SAAS,EAChB,GAAG,EAAE,SAAS,EACd,IAAI,EAAE,QAAQ,CAAC,YAAY,EAC3B,OAAO,GAAE,eAAoB,GAC5B,SAAS,CAAC,QAAQ,CAAC,aAAa,CAAC,CAUnC;
|
|
1
|
+
{"version":3,"file":"range.d.ts","sourceRoot":"","sources":["../src/range.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAIjD,OAAO,KAAK,EAAE,cAAc,EAAE,SAAS,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAE1E,wBAAgB,SAAS,CACvB,KAAK,EAAE,SAAS,EAChB,GAAG,EAAE,SAAS,EACd,IAAI,EAAE,QAAQ,CAAC,YAAY,EAC3B,OAAO,GAAE,eAAoB,GAC5B,SAAS,CAAC,QAAQ,CAAC,aAAa,CAAC,CAUnC;AAoBD,wBAAgB,UAAU,CACxB,KAAK,EAAE,SAAS,EAChB,IAAI,EAAE,cAAc,EACpB,OAAO,GAAE,eAAoB,GAC5B,SAAS,CAAC,QAAQ,CAAC,aAAa,CAAC,CAuBnC"}
|