temporal-fmt 0.9.2 → 0.9.4
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 +480 -9
- package/dist/calendarUtils.cjs +259 -4
- package/dist/calendarUtils.cjs.map +1 -1
- package/dist/calendarUtils.js +33 -1
- package/dist/chunk-4N3WCGEY.js +183 -0
- package/dist/{chunk-4JWGUR4O.js.map → chunk-4N3WCGEY.js.map} +1 -1
- package/dist/chunk-5UTV5VP6.js +984 -0
- package/dist/chunk-5UTV5VP6.js.map +1 -0
- package/dist/chunk-ACKKWH5Z.js +578 -0
- package/dist/{chunk-2JHEV7ZF.js.map → chunk-ACKKWH5Z.js.map} +1 -1
- package/dist/chunk-EGUUO6LE.js +439 -0
- package/dist/chunk-EGUUO6LE.js.map +1 -0
- package/dist/chunk-FBPHNRVW.js +2939 -0
- package/dist/chunk-FBPHNRVW.js.map +1 -0
- package/dist/chunk-GPB4JHGI.js +65 -0
- package/dist/chunk-GPB4JHGI.js.map +1 -0
- package/dist/chunk-H7YQX25T.js +170 -0
- package/dist/{chunk-J2AQNXF2.js.map → chunk-H7YQX25T.js.map} +1 -1
- package/dist/chunk-HXY2GUUM.js +182 -0
- package/dist/{chunk-EHLRZULM.js.map → chunk-HXY2GUUM.js.map} +1 -1
- package/dist/chunk-L24NPTAJ.js +328 -0
- package/dist/chunk-L24NPTAJ.js.map +1 -0
- package/dist/chunk-M3GDTHMT.js +169 -0
- package/dist/chunk-M3GDTHMT.js.map +1 -0
- package/dist/chunk-MKPDLQKD.js +51 -0
- package/dist/chunk-MKPDLQKD.js.map +1 -0
- package/dist/chunk-TSLPIURC.js +242 -0
- package/dist/{chunk-VQXUMFB2.js.map → chunk-TSLPIURC.js.map} +1 -1
- package/dist/chunk-UOPAV3WX.js +75 -0
- package/dist/chunk-UOPAV3WX.js.map +1 -0
- package/dist/chunk-VNIUIQA4.js +398 -0
- package/dist/chunk-VNIUIQA4.js.map +1 -0
- package/dist/chunk-YEUASGDK.js +9 -0
- package/dist/chunk-YEUASGDK.js.map +1 -0
- package/dist/chunk-ZQZF5EXW.js +29 -0
- package/dist/chunk-ZQZF5EXW.js.map +1 -0
- package/dist/config.d.cts +4 -2
- package/dist/config.d.ts +4 -2
- package/dist/duration.cjs +638 -26
- package/dist/duration.cjs.map +1 -1
- package/dist/duration.js +28 -1
- package/dist/format.cjs +833 -35
- package/dist/format.cjs.map +1 -1
- package/dist/format.js +20 -1
- package/dist/index.cjs +7698 -163
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +108 -24
- package/dist/index.d.ts +108 -24
- package/dist/index.js +1536 -58
- package/dist/index.js.map +1 -1
- package/dist/interval.cjs +5480 -35
- package/dist/interval.cjs.map +1 -1
- package/dist/interval.js +46 -1
- package/dist/localeRegistry.cjs +375 -32
- package/dist/localeRegistry.cjs.map +1 -1
- package/dist/localeRegistry.js +11 -1
- package/dist/modApi.d.cts +131 -0
- package/dist/modApi.d.ts +131 -0
- package/dist/parse.cjs +1797 -70
- package/dist/parse.cjs.map +1 -1
- package/dist/parse.js +20 -1
- package/dist/pattern.d.cts +1 -0
- package/dist/pattern.d.ts +1 -0
- package/dist/recurrence.cjs +452 -20
- package/dist/recurrence.cjs.map +1 -1
- package/dist/recurrence.js +19 -1
- package/dist/relativeTime.cjs +268 -21
- package/dist/relativeTime.cjs.map +1 -1
- package/dist/relativeTime.js +14 -1
- package/dist/runtime.d.cts +201 -0
- package/dist/runtime.d.ts +201 -0
- package/dist/timezone.cjs +387 -20
- package/dist/timezone.cjs.map +1 -1
- package/dist/timezone.js +26 -1
- package/package.json +4 -6
- package/scripts/cli.mjs +27 -5
- package/dist/chunk-2JHEV7ZF.js +0 -7
- package/dist/chunk-3MZLTVP3.js +0 -2
- package/dist/chunk-3MZLTVP3.js.map +0 -1
- package/dist/chunk-4JWGUR4O.js +0 -11
- package/dist/chunk-5U5WJ465.js +0 -36
- package/dist/chunk-5U5WJ465.js.map +0 -1
- package/dist/chunk-C5YESWFT.js +0 -2
- package/dist/chunk-C5YESWFT.js.map +0 -1
- package/dist/chunk-C6ZZ62ET.js +0 -2
- package/dist/chunk-C6ZZ62ET.js.map +0 -1
- package/dist/chunk-EHLRZULM.js +0 -5
- package/dist/chunk-F4RGUDA3.js +0 -2
- package/dist/chunk-F4RGUDA3.js.map +0 -1
- package/dist/chunk-J2AQNXF2.js +0 -2
- package/dist/chunk-NBXF7V5B.js +0 -2
- package/dist/chunk-NBXF7V5B.js.map +0 -1
- package/dist/chunk-R52YOKI3.js +0 -7
- package/dist/chunk-R52YOKI3.js.map +0 -1
- package/dist/chunk-UJR6DHXY.js +0 -2
- package/dist/chunk-UJR6DHXY.js.map +0 -1
- package/dist/chunk-VQXUMFB2.js +0 -21
- package/dist/chunk-XH46JNPK.js +0 -2
- package/dist/chunk-XH46JNPK.js.map +0 -1
- package/dist/chunk-YB6YAG7E.js +0 -35
- package/dist/chunk-YB6YAG7E.js.map +0 -1
- package/dist/chunk-ZGMTY52O.js +0 -2
- package/dist/chunk-ZGMTY52O.js.map +0 -1
package/dist/format.cjs
CHANGED
|
@@ -1,39 +1,837 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/format.ts
|
|
21
|
+
var format_exports = {};
|
|
22
|
+
__export(format_exports, {
|
|
23
|
+
_getPieces: () => _getPieces,
|
|
24
|
+
_handlerFor: () => _handlerFor,
|
|
25
|
+
compileFormat: () => compileFormat,
|
|
26
|
+
format: () => format,
|
|
27
|
+
formatToParts: () => formatToParts
|
|
28
|
+
});
|
|
29
|
+
module.exports = __toCommonJS(format_exports);
|
|
30
|
+
|
|
31
|
+
// src/temporalProvider.ts
|
|
32
|
+
var injectedTemporal;
|
|
33
|
+
var onTemporalChanged = [];
|
|
34
|
+
function subscribeToTemporalChanges(listener) {
|
|
35
|
+
onTemporalChanged.push(listener);
|
|
36
|
+
}
|
|
37
|
+
function resolveTemporal() {
|
|
38
|
+
return injectedTemporal ?? globalThis.Temporal;
|
|
39
|
+
}
|
|
40
|
+
function getTemporal() {
|
|
41
|
+
const temporal = resolveTemporal();
|
|
42
|
+
if (!temporal) {
|
|
43
|
+
throw new Error(
|
|
44
|
+
"temporal-fmt: parse() needs a Temporal implementation to construct its result. Call setTemporal(Temporal) once at startup, or assign one to globalThis.Temporal (native on Node 26+, or a polyfill like temporal-polyfill)."
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
return temporal;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// src/errors.ts
|
|
51
|
+
var TemporalFmtError = class extends Error {
|
|
52
|
+
code;
|
|
53
|
+
input;
|
|
54
|
+
format;
|
|
55
|
+
token;
|
|
56
|
+
position;
|
|
57
|
+
expected;
|
|
58
|
+
actual;
|
|
59
|
+
reason;
|
|
60
|
+
constructor(message, fields) {
|
|
61
|
+
super(message);
|
|
62
|
+
this.name = "TemporalFmtError";
|
|
63
|
+
this.code = fields.code;
|
|
64
|
+
this.input = fields.input;
|
|
65
|
+
this.format = fields.format;
|
|
66
|
+
this.token = fields.token;
|
|
67
|
+
this.position = fields.position;
|
|
68
|
+
this.expected = fields.expected;
|
|
69
|
+
this.actual = fields.actual;
|
|
70
|
+
this.reason = fields.reason;
|
|
71
|
+
const capture = Error.captureStackTrace;
|
|
72
|
+
if (typeof capture === "function") {
|
|
73
|
+
capture(this, this.constructor);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
// lets callers just call err.toJSON() for logging. plain Error doesn't
|
|
77
|
+
// serialize its non-enumerable fields on its own, so this picks them
|
|
78
|
+
// up explicitly
|
|
79
|
+
toJSON() {
|
|
80
|
+
return {
|
|
81
|
+
name: this.name,
|
|
82
|
+
message: this.message,
|
|
83
|
+
code: this.code,
|
|
84
|
+
input: this.input,
|
|
85
|
+
format: this.format,
|
|
86
|
+
token: this.token,
|
|
87
|
+
position: this.position,
|
|
88
|
+
expected: this.expected,
|
|
89
|
+
actual: this.actual,
|
|
90
|
+
reason: this.reason
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
var FormatSyntaxError = class extends TemporalFmtError {
|
|
95
|
+
constructor(fields) {
|
|
96
|
+
const { message, ...rest } = fields;
|
|
97
|
+
super(
|
|
98
|
+
message ?? `format string "${fields.format ?? ""}" has a syntax error${fields.reason ? `: ${fields.reason}` : ""}.`,
|
|
99
|
+
{ code: "FORMAT_SYNTAX_ERROR", ...rest }
|
|
100
|
+
);
|
|
101
|
+
this.name = "FormatSyntaxError";
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
var UnknownTokenError = class extends TemporalFmtError {
|
|
105
|
+
constructor(fields) {
|
|
106
|
+
const { message, ...rest } = fields;
|
|
107
|
+
super(
|
|
108
|
+
message ?? `token "${fields.token ?? ""}" is not a recognized temporal-fmt token${fields.format ? ` in format string "${fields.format}"` : ""}.`,
|
|
109
|
+
{ code: "UNKNOWN_TOKEN", ...rest }
|
|
110
|
+
);
|
|
111
|
+
this.name = "UnknownTokenError";
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
/* c8 ignore start @preserve -- InvalidTimeError is part of the public
|
|
115
|
+
error-class surface (exported from index.ts, code 'INVALID_TIME') but
|
|
116
|
+
nothing in this package actually constructs one. went and checked
|
|
117
|
+
whether it could get wired in the same way InvalidTimeZoneError just
|
|
118
|
+
was (see parse.ts's zzz-validation loop) — does hour/minute/second have
|
|
119
|
+
a post-match semantic range check the way zone ids do? nope.
|
|
7
120
|
pattern.ts's regex fragments for HH/H/hh/h/mm/m/ss/s already enforce
|
|
8
|
-
their valid ranges at the regex level (e.g. HH is
|
|
9
|
-
which can't match "99"
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
redundant check purely to give this class a body.
|
|
13
|
-
until the library actually
|
|
121
|
+
their valid ranges right at the regex level (e.g. HH is
|
|
122
|
+
'(?:[01]\d|2[0-3])', which literally can't match "99"), so an
|
|
123
|
+
out-of-range time just gets rejected as a plain shape mismatch before
|
|
124
|
+
any semantic check would even run. no live gap to hook this into
|
|
125
|
+
without inventing a redundant check purely to give this class a body.
|
|
126
|
+
leaving it unconstructed until the library actually hits a real
|
|
127
|
+
invalid-time case worth reporting */
|
|
128
|
+
/* c8 ignore stop @preserve */
|
|
129
|
+
/* c8 ignore start @preserve -- InvalidCalendarError is part of the
|
|
14
130
|
public error-class surface (exported from index.ts, code
|
|
15
|
-
'INVALID_CALENDAR') but
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
131
|
+
'INVALID_CALENDAR') but same story as InvalidTimeError above — nothing
|
|
132
|
+
in this package constructs one. checked for a wiring opportunity the
|
|
133
|
+
same way: there's no user-supplied calendar identifier anywhere in the
|
|
134
|
+
library that we'd validate against a supported list. resolveCalendar()
|
|
19
135
|
in parse.ts derives the calendar entirely from Intl's own resolution
|
|
20
|
-
of the locale string — it's never handed an arbitrary "calendar"
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
parameter that could be invalid
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
136
|
+
of the locale string — it's never handed an arbitrary "calendar" value
|
|
137
|
+
a caller could actually get wrong. no live input to reject here.
|
|
138
|
+
leaving it unconstructed until the library accepts an actual calendar
|
|
139
|
+
parameter that could be invalid */
|
|
140
|
+
/* c8 ignore stop @preserve */
|
|
141
|
+
var InvalidLocaleError = class extends TemporalFmtError {
|
|
142
|
+
constructor(fields) {
|
|
143
|
+
const { message, ...rest } = fields;
|
|
144
|
+
super(
|
|
145
|
+
message ?? `locale "${fields.actual ?? ""}" is not a valid BCP-47 tag${fields.reason ? `: ${fields.reason}` : ""}.`,
|
|
146
|
+
{ code: "INVALID_LOCALE", ...rest }
|
|
147
|
+
);
|
|
148
|
+
this.name = "InvalidLocaleError";
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
/* c8 ignore start @preserve -- unreachable from the current test suite,
|
|
152
|
+
see rationale above */
|
|
153
|
+
/* c8 ignore stop @preserve */
|
|
154
|
+
|
|
155
|
+
// src/localeVocab.ts
|
|
156
|
+
var customVocabs = /* @__PURE__ */ new Map();
|
|
157
|
+
function normalizeLocaleTag(locale) {
|
|
158
|
+
return locale.replace(/_/g, "-");
|
|
159
|
+
}
|
|
160
|
+
var canonicalKeyCache = /* @__PURE__ */ new Map();
|
|
161
|
+
var MAX_CANONICAL_KEY_CACHE = 500;
|
|
162
|
+
function canonicalCacheKey(locale) {
|
|
163
|
+
const hit = canonicalKeyCache.get(locale);
|
|
164
|
+
if (hit !== void 0) return hit;
|
|
165
|
+
let key;
|
|
166
|
+
try {
|
|
167
|
+
key = new Intl.Locale(locale.replace(/_/g, "-")).toString().toLowerCase();
|
|
168
|
+
} catch {
|
|
169
|
+
key = locale;
|
|
170
|
+
}
|
|
171
|
+
if (canonicalKeyCache.size >= MAX_CANONICAL_KEY_CACHE) {
|
|
172
|
+
const oldestKey = canonicalKeyCache.keys().next().value;
|
|
173
|
+
if (oldestKey !== void 0) canonicalKeyCache.delete(oldestKey);
|
|
174
|
+
}
|
|
175
|
+
canonicalKeyCache.set(locale, key);
|
|
176
|
+
return key;
|
|
177
|
+
}
|
|
178
|
+
function getCustomVocab(locale) {
|
|
179
|
+
const cacheKey = canonicalCacheKey(locale);
|
|
180
|
+
return customVocabs.get(cacheKey);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// src/isoWeek.ts
|
|
184
|
+
var CUMULATIVE_DAYS_BY_MONTH = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334];
|
|
185
|
+
function isGregorianLeapYear(year) {
|
|
186
|
+
return year % 4 === 0 && year % 100 !== 0 || year % 400 === 0;
|
|
187
|
+
}
|
|
188
|
+
function daysInYear(year) {
|
|
189
|
+
return isGregorianLeapYear(year) ? 366 : 365;
|
|
190
|
+
}
|
|
191
|
+
function dayOfYear(year, month, day) {
|
|
192
|
+
let doy = CUMULATIVE_DAYS_BY_MONTH[month - 1] + day;
|
|
193
|
+
if (month > 2 && isGregorianLeapYear(year)) doy += 1;
|
|
194
|
+
return doy;
|
|
195
|
+
}
|
|
196
|
+
var REFERENCE_YEAR = 2e3;
|
|
197
|
+
function dayOfWeekOfJan1(year) {
|
|
198
|
+
let offset = 0;
|
|
199
|
+
if (year >= REFERENCE_YEAR) {
|
|
200
|
+
for (let y = REFERENCE_YEAR; y < year; y++) offset += daysInYear(y);
|
|
201
|
+
} else {
|
|
202
|
+
for (let y = year; y < REFERENCE_YEAR; y++) offset -= daysInYear(y);
|
|
203
|
+
}
|
|
204
|
+
const zeroIndexed = ((6 - 1 + offset) % 7 + 7) % 7;
|
|
205
|
+
return zeroIndexed + 1;
|
|
206
|
+
}
|
|
207
|
+
function isoWeekYearAndWeek(year, month, day, dayOfWeek) {
|
|
208
|
+
const doy = dayOfYear(year, month, day);
|
|
209
|
+
const thursdayDoyRelative = doy + (4 - dayOfWeek);
|
|
210
|
+
let isoYear;
|
|
211
|
+
let thursdayDoy;
|
|
212
|
+
if (thursdayDoyRelative < 1) {
|
|
213
|
+
isoYear = year - 1;
|
|
214
|
+
thursdayDoy = thursdayDoyRelative + daysInYear(isoYear);
|
|
215
|
+
} else if (thursdayDoyRelative > daysInYear(year)) {
|
|
216
|
+
isoYear = year + 1;
|
|
217
|
+
thursdayDoy = thursdayDoyRelative - daysInYear(year);
|
|
218
|
+
} else {
|
|
219
|
+
isoYear = year;
|
|
220
|
+
thursdayDoy = thursdayDoyRelative;
|
|
221
|
+
}
|
|
222
|
+
const jan1Dow = dayOfWeekOfJan1(isoYear);
|
|
223
|
+
const firstThursdayDoy = 1 + (4 - jan1Dow + 7) % 7;
|
|
224
|
+
const week = 1 + Math.floor((thursdayDoy - firstThursdayDoy) / 7);
|
|
225
|
+
return { isoYear, week };
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// src/tokens.ts
|
|
229
|
+
function pad(n, len) {
|
|
230
|
+
const negative = n < 0;
|
|
231
|
+
const digits = String(Math.abs(n)).padStart(len, "0");
|
|
232
|
+
return negative ? "-" + digits : digits;
|
|
233
|
+
}
|
|
234
|
+
pad.fraction = function formatFraction(t, width) {
|
|
235
|
+
const nanoOfSecond = t.millisecond * 1e6 + (t.microsecond ?? 0) * 1e3 + (t.nanosecond ?? 0);
|
|
236
|
+
return pad(nanoOfSecond, 9).slice(0, width);
|
|
237
|
+
};
|
|
238
|
+
var DEFAULT_LOCALE = "en-US";
|
|
239
|
+
var formatterCache = /* @__PURE__ */ new Map();
|
|
240
|
+
var MAX_CACHE_SIZE = 500;
|
|
241
|
+
function getFormatter(locale, options) {
|
|
242
|
+
const key = JSON.stringify([canonicalCacheKey(locale), options]);
|
|
243
|
+
let formatter = formatterCache.get(key);
|
|
244
|
+
if (formatter) {
|
|
245
|
+
return formatter;
|
|
246
|
+
}
|
|
247
|
+
if (formatterCache.size >= MAX_CACHE_SIZE) {
|
|
248
|
+
const oldestKey = formatterCache.keys().next().value;
|
|
249
|
+
if (oldestKey !== void 0) formatterCache.delete(oldestKey);
|
|
250
|
+
}
|
|
251
|
+
try {
|
|
252
|
+
formatter = new Intl.DateTimeFormat(normalizeLocaleTag(locale), options);
|
|
253
|
+
} catch (err) {
|
|
254
|
+
throw new InvalidLocaleError({ actual: locale, reason: err.message });
|
|
255
|
+
}
|
|
256
|
+
formatterCache.set(key, formatter);
|
|
257
|
+
return formatter;
|
|
258
|
+
}
|
|
259
|
+
var nativeSupport;
|
|
260
|
+
subscribeToTemporalChanges(() => {
|
|
261
|
+
nativeSupport = void 0;
|
|
262
|
+
});
|
|
263
|
+
function intlSupportsNativeTemporal() {
|
|
264
|
+
if (nativeSupport === void 0) {
|
|
265
|
+
nativeSupport = false;
|
|
266
|
+
try {
|
|
267
|
+
const temporal = getTemporal();
|
|
268
|
+
new Intl.DateTimeFormat("en-US", { day: "numeric" }).formatToParts(temporal.PlainDate.from({ year: 1970, month: 1, day: 1 }));
|
|
269
|
+
nativeSupport = true;
|
|
270
|
+
} catch {
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
return nativeSupport;
|
|
274
|
+
}
|
|
275
|
+
function intlPart(temporal, locale, options, partType) {
|
|
276
|
+
const calendar = temporal?.calendarId;
|
|
277
|
+
const formatterOptions = {
|
|
278
|
+
...options,
|
|
279
|
+
calendar: calendar && calendar !== "iso8601" ? calendar : "gregory"
|
|
280
|
+
};
|
|
281
|
+
const ls = temporal.toLocaleString;
|
|
282
|
+
if (typeof ls !== "function" || ls === Object.prototype.toLocaleString) {
|
|
283
|
+
throw new Error(
|
|
284
|
+
`temporal-fmt: locale-aware part "${partType}" needs a value that implements toLocaleString (a real Temporal object). A plain field bag cannot render locale-aware names \u2014 pass a Temporal.PlainDate/PlainDateTime/ZonedDateTime.`
|
|
285
|
+
);
|
|
286
|
+
}
|
|
287
|
+
if (!intlSupportsNativeTemporal()) {
|
|
288
|
+
try {
|
|
289
|
+
return ls.call(temporal, normalizeLocaleTag(locale), formatterOptions);
|
|
290
|
+
} catch (err) {
|
|
291
|
+
if (err instanceof RangeError) {
|
|
292
|
+
throw new InvalidLocaleError({ actual: locale, reason: err.message });
|
|
293
|
+
}
|
|
294
|
+
throw err;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
const { toInstant, timeZoneId } = temporal;
|
|
298
|
+
const isZoned = typeof toInstant === "function" && typeof timeZoneId === "string";
|
|
299
|
+
const intlSafeTemporal = isZoned ? temporal.toInstant() : temporal;
|
|
300
|
+
const nativeOptions = {
|
|
301
|
+
...formatterOptions,
|
|
302
|
+
...isZoned ? { timeZone: timeZoneId } : {}
|
|
303
|
+
};
|
|
304
|
+
const formatter = getFormatter(locale, nativeOptions);
|
|
305
|
+
const parts = formatter.formatToParts(intlSafeTemporal);
|
|
306
|
+
const index = parts.findIndex((p) => p.type === partType);
|
|
307
|
+
if (index === -1) {
|
|
308
|
+
throw new Error(
|
|
309
|
+
`temporal-fmt: locale "${locale}" produced no "${partType}" part for this token. This usually means the Temporal object is missing the field the token needs.`
|
|
310
|
+
);
|
|
311
|
+
}
|
|
312
|
+
let value = parts[index].value;
|
|
313
|
+
const prev = parts[index - 1];
|
|
314
|
+
const next = parts[index + 1];
|
|
315
|
+
if (prev?.type === "literal" && !/\s/.test(prev.value)) value = prev.value + value;
|
|
316
|
+
if (next?.type === "literal" && !/\s/.test(next.value)) value = value + next.value;
|
|
317
|
+
return value;
|
|
318
|
+
}
|
|
319
|
+
function dayPeriodPart(hour, locale) {
|
|
320
|
+
const custom = getCustomVocab(locale);
|
|
321
|
+
if (custom) {
|
|
322
|
+
return hour < 12 ? custom.dayPeriod[0] : custom.dayPeriod[1];
|
|
323
|
+
}
|
|
324
|
+
const date = new Date(Date.UTC(1970, 0, 1, hour));
|
|
325
|
+
const formatter = getFormatter(locale, { hour: "numeric", hour12: true, timeZone: "UTC" });
|
|
326
|
+
const part = formatter.formatToParts(date).find((p) => p.type === "dayPeriod");
|
|
327
|
+
if (!part) {
|
|
328
|
+
throw new Error(`temporal-fmt: locale "${locale}" produced no "dayPeriod" part for token "a".`);
|
|
329
|
+
}
|
|
330
|
+
return part.value;
|
|
331
|
+
}
|
|
332
|
+
function localeAwareName(temporal, locale, options, partType, customArray, customIndex) {
|
|
333
|
+
if (customArray && customIndex !== void 0 && customIndex >= 0 && customIndex < customArray.length) {
|
|
334
|
+
return customArray[customIndex];
|
|
335
|
+
}
|
|
336
|
+
return intlPart(temporal, locale, options, partType);
|
|
337
|
+
}
|
|
338
|
+
function formatOffset(offset, variant) {
|
|
339
|
+
if (offset === "+00:00" && (variant === "X" || variant === "XX" || variant === "XXX")) {
|
|
340
|
+
return "Z";
|
|
341
|
+
}
|
|
342
|
+
if (offset.length > 6) {
|
|
343
|
+
if (variant === "xxx") {
|
|
344
|
+
return offset;
|
|
345
|
+
}
|
|
346
|
+
throw new Error(
|
|
347
|
+
`temporal-fmt: token "${variant}" cannot represent the offset "${offset}", which has a seconds component. None of the X/XX/XXX/x/xx tokens support offset seconds; use "xxx" instead, which formats the full offset unchanged.`
|
|
348
|
+
);
|
|
349
|
+
}
|
|
350
|
+
const sign = offset[0];
|
|
351
|
+
const hours = offset.slice(1, 3);
|
|
352
|
+
const minutes = offset.slice(4, 6);
|
|
353
|
+
switch (variant) {
|
|
354
|
+
case "X":
|
|
355
|
+
case "x":
|
|
356
|
+
return minutes === "00" ? `${sign}${hours}` : `${sign}${hours}${minutes}`;
|
|
357
|
+
case "XX":
|
|
358
|
+
case "xx":
|
|
359
|
+
return `${sign}${hours}${minutes}`;
|
|
360
|
+
case "XXX":
|
|
361
|
+
case "xxx":
|
|
362
|
+
return `${sign}${hours}:${minutes}`;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
var TOKENS = [
|
|
366
|
+
["yyyy", (t) => pad(t.year, 4), "year"],
|
|
367
|
+
["yy", (t) => {
|
|
368
|
+
if (t.year < 0) {
|
|
369
|
+
throw new Error(
|
|
370
|
+
`temporal-fmt: token "yy" doesn't support negative years (got ${t.year}), since truncating to 2 digits would make it indistinguishable from a positive year. Use "yyyy" instead.`
|
|
371
|
+
);
|
|
372
|
+
}
|
|
373
|
+
return pad(t.year % 100, 2);
|
|
374
|
+
}, "year"],
|
|
375
|
+
// Unpadded year — no minimum width, unlike yyyy's fixed 4 digits.
|
|
376
|
+
// pad(n, 0) still does the right thing here: Math.abs(n) with no
|
|
377
|
+
// padStart floor just yields the plain digit string, and the sign
|
|
378
|
+
// handling (split off before padding) already covers negative years,
|
|
379
|
+
// so this doesn't need its own sign branch the way "yy" does.
|
|
380
|
+
["y", (t) => pad(t.year, 0), "year"],
|
|
381
|
+
["MMMM", (t, locale) => {
|
|
382
|
+
const custom = getCustomVocab(locale);
|
|
383
|
+
return localeAwareName(t, locale, { month: "long" }, "month", custom?.monthLong, t.month - 1);
|
|
384
|
+
}, "month"],
|
|
385
|
+
["MMM", (t, locale) => {
|
|
386
|
+
const custom = getCustomVocab(locale);
|
|
387
|
+
return localeAwareName(t, locale, { month: "short" }, "month", custom?.monthShort, t.month - 1);
|
|
388
|
+
}, "month"],
|
|
389
|
+
["MM", (t) => pad(t.month, 2), "month"],
|
|
390
|
+
["M", (t) => String(t.month), "month"],
|
|
391
|
+
["dd", (t) => pad(t.day, 2), "day"],
|
|
392
|
+
["d", (t) => String(t.day), "day"],
|
|
393
|
+
["EEEE", (t, locale) => {
|
|
394
|
+
const custom = getCustomVocab(locale);
|
|
395
|
+
return localeAwareName(t, locale, { weekday: "long" }, "weekday", custom?.weekdayLong, t.dayOfWeek - 1);
|
|
396
|
+
}, "dayOfWeek"],
|
|
397
|
+
["EEE", (t, locale) => {
|
|
398
|
+
const custom = getCustomVocab(locale);
|
|
399
|
+
return localeAwareName(t, locale, { weekday: "short" }, "weekday", custom?.weekdayShort, t.dayOfWeek - 1);
|
|
400
|
+
}, "dayOfWeek"],
|
|
401
|
+
["HH", (t) => pad(t.hour, 2), "hour"],
|
|
402
|
+
["H", (t) => String(t.hour), "hour"],
|
|
403
|
+
["hh", (t) => pad(t.hour % 12 || 12, 2), "hour"],
|
|
404
|
+
["h", (t) => String(t.hour % 12 || 12), "hour"],
|
|
405
|
+
["mm", (t) => pad(t.minute, 2), "minute"],
|
|
406
|
+
["m", (t) => String(t.minute), "minute"],
|
|
407
|
+
["ss", (t) => pad(t.second, 2), "second"],
|
|
408
|
+
["s", (t) => String(t.second), "second"],
|
|
409
|
+
// Fractional-second tokens, S through SSSSSSSSS (1-9 digits). Each token
|
|
410
|
+
// formats a slice of the same underlying nanosecond-of-second value —
|
|
411
|
+
// combining millisecond/microsecond/nanosecond into one 9-digit number
|
|
412
|
+
// and truncating to the token's width — so "SSS" keeps meaning exactly
|
|
413
|
+
// what it always meant (3-digit milliseconds) while wider tokens expose
|
|
414
|
+
// the precision Temporal actually carries. formatFraction below is the
|
|
415
|
+
// shared implementation; see its comment for the truncate-not-round
|
|
416
|
+
// rule and why.
|
|
417
|
+
["SSSSSSSSS", (t) => pad.fraction(t, 9), "millisecond"],
|
|
418
|
+
["SSSSSSSS", (t) => pad.fraction(t, 8), "millisecond"],
|
|
419
|
+
["SSSSSSS", (t) => pad.fraction(t, 7), "millisecond"],
|
|
420
|
+
["SSSSSS", (t) => pad.fraction(t, 6), "millisecond"],
|
|
421
|
+
["SSSSS", (t) => pad.fraction(t, 5), "millisecond"],
|
|
422
|
+
["SSSS", (t) => pad.fraction(t, 4), "millisecond"],
|
|
423
|
+
["SSS", (t) => pad.fraction(t, 3), "millisecond"],
|
|
424
|
+
["SS", (t) => pad.fraction(t, 2), "millisecond"],
|
|
425
|
+
["S", (t) => pad.fraction(t, 1), "millisecond"],
|
|
426
|
+
// dayPeriod text is locale-specific (AM/PM in en-US, م/ص in ar-EG) but
|
|
427
|
+
// still needs .hour on the input to compute which period it is
|
|
428
|
+
["a", (t, locale) => dayPeriodPart(t.hour, locale), "hour"],
|
|
429
|
+
["zzz", (t) => t.timeZoneId, "timeZoneId"],
|
|
430
|
+
// Numeric UTC offset tokens (date-fns/Unicode-LDML family). Only
|
|
431
|
+
// ZonedDateTime carries an offset, so the field check in format() throws
|
|
432
|
+
// the same "requires offset, which this Temporal object doesn't have"
|
|
433
|
+
// error zzz throws on PlainDate/PlainTime/PlainDateTime — same
|
|
434
|
+
// validation path, just a different field name. See formatOffset above
|
|
435
|
+
// for the per-variant width and Z/numeric distinction.
|
|
436
|
+
["xxx", (t) => formatOffset(t.offset, "xxx"), "offset"],
|
|
437
|
+
["xx", (t) => formatOffset(t.offset, "xx"), "offset"],
|
|
438
|
+
["X", (t) => formatOffset(t.offset, "X"), "offset"],
|
|
439
|
+
["XX", (t) => formatOffset(t.offset, "XX"), "offset"],
|
|
440
|
+
["XXX", (t) => formatOffset(t.offset, "XXX"), "offset"],
|
|
441
|
+
["x", (t) => formatOffset(t.offset, "x"), "offset"],
|
|
442
|
+
// Ordinal day (1st, 2nd, 3rd, ... 21st). English suffix rules only —
|
|
443
|
+
// locale-aware ordinals ("2." in de-DE, "2日" in ja-JP) are out of scope,
|
|
444
|
+
// since the rest of this library routes locale-specific names through
|
|
445
|
+
// Intl.DateTimeFormat, and Intl has no part type for ordinals. Format-only:
|
|
446
|
+
// the suffix isn't structurally distinguishable from a literal in a parse
|
|
447
|
+
// context (a "st"/"nd"/"rd"/"th" suffix isn't a digit and would collide
|
|
448
|
+
// with any adjacent literal text), so there's no good way to read it back.
|
|
449
|
+
["do", (t) => {
|
|
450
|
+
const day = t.day;
|
|
451
|
+
const lastDigit = day % 10;
|
|
452
|
+
const lastTwoDigits = day % 100;
|
|
453
|
+
if (lastTwoDigits >= 11 && lastTwoDigits <= 13) {
|
|
454
|
+
return day + "th";
|
|
455
|
+
}
|
|
456
|
+
if (lastDigit === 1) return day + "st";
|
|
457
|
+
if (lastDigit === 2) return day + "nd";
|
|
458
|
+
if (lastDigit === 3) return day + "rd";
|
|
459
|
+
return day + "th";
|
|
460
|
+
}, "day"],
|
|
461
|
+
// Quarter computed from month: 1-3=Q1, 4-6=Q2, 7-9=Q3, 10-12=Q4.
|
|
462
|
+
// `Q` is plain numeric, `QQQ` renders as "Q3" — same convention as
|
|
463
|
+
// date-fns's `Q` and `QQQ` for parity with the most common prior art.
|
|
464
|
+
// Both format and parse; parse() cross-checks Q/QQQ against the parsed
|
|
465
|
+
// month in the same spirit as the EEEE-vs-date cross-check.
|
|
466
|
+
["Q", (t) => String(Math.ceil(t.month / 3)), "month"],
|
|
467
|
+
["QQQ", (t) => "Q" + Math.ceil(t.month / 3), "month"],
|
|
468
|
+
// ISO 8601 week and week-numbering year. Both are format-only — parsing
|
|
469
|
+
// "ww"/"RRRR" back into a real date requires resolving an ISO week + a
|
|
470
|
+
// weekday (or some other disambiguator) to a specific date, which is a
|
|
471
|
+
// different parsing surface than the token-based parse() here. The
|
|
472
|
+
// ISO-week year (RRRR) can differ from the calendar year at the boundary:
|
|
473
|
+
// Dec 29-31 often belong to week 1 of the *next* year; Jan 1-3 often
|
|
474
|
+
// belong to week 52/53 of the *previous* year. See isoWeekYearAndWeek().
|
|
475
|
+
["ww", (t) => {
|
|
476
|
+
const { week } = isoWeekYearAndWeek(t.year, t.month, t.day, t.dayOfWeek);
|
|
477
|
+
return pad(week, 2);
|
|
478
|
+
}, "dayOfWeek"],
|
|
479
|
+
["RRRR", (t) => {
|
|
480
|
+
const { isoYear } = isoWeekYearAndWeek(t.year, t.month, t.day, t.dayOfWeek);
|
|
481
|
+
return pad(isoYear, 4);
|
|
482
|
+
}, "dayOfWeek"],
|
|
483
|
+
// Day of year — number of days since Jan 1 (1-366). Three widths:
|
|
484
|
+
// D — unpadded (1, 2, 366)
|
|
485
|
+
// DD — 2-digit minimum (zero-padded if <100)
|
|
486
|
+
// DDD — 3-digit zero-padded (001, 002, 366)
|
|
487
|
+
// Format-only — parsing day-of-year requires resolving against a year,
|
|
488
|
+
// which is a different shape from the token-based parse() surface.
|
|
489
|
+
// The dayOfYearHelper() in calendarUtils.ts covers the same field for
|
|
490
|
+
// callers who need the numeric value.
|
|
491
|
+
["D", (t) => String(dayOfYear(t.year, t.month, t.day)), "day"],
|
|
492
|
+
["DD", (t) => pad(dayOfYear(t.year, t.month, t.day), 2), "day"],
|
|
493
|
+
["DDD", (t) => pad(dayOfYear(t.year, t.month, t.day), 3), "day"],
|
|
494
|
+
// Stand-alone month — uses Intl's stand-alone form. In most locales
|
|
495
|
+
// (en, fr, de, es) this is identical to MMMM/MMM. In Slavic/Baltic
|
|
496
|
+
// locales (cs, sk, pl, ru) the stand-alone form differs from the
|
|
497
|
+
// format form (nominative vs genitive case). LLLL = long, LLL = short.
|
|
498
|
+
["LLLL", (t, locale) => {
|
|
499
|
+
const custom = getCustomVocab(locale);
|
|
500
|
+
return localeAwareName(t, locale, { month: "long" }, "month", custom?.monthLong, t.month - 1);
|
|
501
|
+
}, "month"],
|
|
502
|
+
["LLL", (t, locale) => {
|
|
503
|
+
const custom = getCustomVocab(locale);
|
|
504
|
+
return localeAwareName(t, locale, { month: "short" }, "month", custom?.monthShort, t.month - 1);
|
|
505
|
+
}, "month"],
|
|
506
|
+
// Stand-alone weekday — same pattern as stand-alone month but for
|
|
507
|
+
// weekday names. cccc = long, ccc = short.
|
|
508
|
+
["cccc", (t, locale) => {
|
|
509
|
+
const custom = getCustomVocab(locale);
|
|
510
|
+
return localeAwareName(t, locale, { weekday: "long" }, "weekday", custom?.weekdayLong, t.dayOfWeek - 1);
|
|
511
|
+
}, "dayOfWeek"],
|
|
512
|
+
["ccc", (t, locale) => {
|
|
513
|
+
const custom = getCustomVocab(locale);
|
|
514
|
+
return localeAwareName(t, locale, { weekday: "short" }, "weekday", custom?.weekdayShort, t.dayOfWeek - 1);
|
|
515
|
+
}, "dayOfWeek"],
|
|
516
|
+
// Era — locale-aware ("AD"/"BC" in en, "ap. J.-C."/"av. J.-C." in fr).
|
|
517
|
+
// GGGG = long, G = short. Format-only.
|
|
518
|
+
["GGGG", (t, locale) => {
|
|
519
|
+
return intlPart(t, locale, { era: "long" }, "era");
|
|
520
|
+
}, "year"],
|
|
521
|
+
["G", (t, locale) => {
|
|
522
|
+
return intlPart(t, locale, { era: "short" }, "era");
|
|
523
|
+
}, "year"],
|
|
524
|
+
// Localized timezone name — uses Intl's longLocalized/short timezone
|
|
525
|
+
// name option. Format-only — these names are locale-dependent and
|
|
526
|
+
// vary by season (EST vs EDT), so parsing them back requires a
|
|
527
|
+
// lookup table that isn't practical to ship.
|
|
528
|
+
["zzzz", (t, locale) => {
|
|
529
|
+
return intlPart(t, locale, { timeZoneName: "longGeneric" }, "timeZoneName");
|
|
530
|
+
}, "timeZoneId"],
|
|
531
|
+
["z", (t, locale) => {
|
|
532
|
+
return intlPart(t, locale, { timeZoneName: "short" }, "timeZoneName");
|
|
533
|
+
}, "timeZoneId"]
|
|
534
|
+
];
|
|
535
|
+
|
|
536
|
+
// src/tokenize.ts
|
|
537
|
+
var SORTED_TOKEN_STRINGS = TOKENS.map(([tok]) => tok).sort((a, b) => b.length - a.length);
|
|
538
|
+
function tokenize(format2) {
|
|
539
|
+
const pieces = [];
|
|
540
|
+
let i = 0;
|
|
541
|
+
while (i < format2.length) {
|
|
542
|
+
const ch = format2[i];
|
|
543
|
+
if (ch === "'") {
|
|
544
|
+
if (format2[i + 1] === "'") {
|
|
545
|
+
appendLiteral(pieces, "'");
|
|
546
|
+
i += 2;
|
|
547
|
+
continue;
|
|
548
|
+
}
|
|
549
|
+
let j = i + 1;
|
|
550
|
+
let literal = "";
|
|
551
|
+
let closed = false;
|
|
552
|
+
while (j < format2.length) {
|
|
553
|
+
if (format2[j] === "'") {
|
|
554
|
+
if (format2[j + 1] === "'") {
|
|
555
|
+
literal += "'";
|
|
556
|
+
j += 2;
|
|
557
|
+
continue;
|
|
558
|
+
}
|
|
559
|
+
closed = true;
|
|
560
|
+
j += 1;
|
|
561
|
+
break;
|
|
562
|
+
}
|
|
563
|
+
literal += format2[j];
|
|
564
|
+
j += 1;
|
|
565
|
+
}
|
|
566
|
+
if (!closed) {
|
|
567
|
+
throw new FormatSyntaxError({
|
|
568
|
+
format: format2,
|
|
569
|
+
message: `temporal-fmt: unterminated quote in format string "${format2}"`
|
|
570
|
+
});
|
|
571
|
+
}
|
|
572
|
+
appendLiteral(pieces, literal);
|
|
573
|
+
i = j;
|
|
574
|
+
continue;
|
|
575
|
+
}
|
|
576
|
+
const match = SORTED_TOKEN_STRINGS.find((tok) => format2.startsWith(tok, i));
|
|
577
|
+
if (match) {
|
|
578
|
+
const runChar = match[match.length - 1];
|
|
579
|
+
if (format2[i + match.length] === runChar) {
|
|
580
|
+
let end = i + match.length;
|
|
581
|
+
while (format2[end] === runChar) end += 1;
|
|
582
|
+
throw new UnknownTokenError({
|
|
583
|
+
format: format2,
|
|
584
|
+
token: format2.slice(i, end),
|
|
585
|
+
message: `temporal-fmt: "${format2.slice(i, end)}" in format string "${format2}" isn't a recognized token \u2014 did you mean "${match}"?`
|
|
586
|
+
});
|
|
587
|
+
}
|
|
588
|
+
pieces.push({ kind: "token", value: match });
|
|
589
|
+
i += match.length;
|
|
590
|
+
continue;
|
|
591
|
+
}
|
|
592
|
+
appendLiteral(pieces, ch);
|
|
593
|
+
i += 1;
|
|
594
|
+
}
|
|
595
|
+
return pieces;
|
|
596
|
+
}
|
|
597
|
+
function appendLiteral(pieces, value) {
|
|
598
|
+
const last = pieces[pieces.length - 1];
|
|
599
|
+
if (last && last.kind === "literal") {
|
|
600
|
+
last.value += value;
|
|
601
|
+
} else {
|
|
602
|
+
pieces.push({ kind: "literal", value });
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
// src/constants.ts
|
|
607
|
+
var MAX_FORMAT_LENGTH = 1e3;
|
|
608
|
+
|
|
609
|
+
// src/numbering.ts
|
|
610
|
+
var SUPPORTED_NUMBERING_SYSTEMS = /* @__PURE__ */ new Set([
|
|
611
|
+
"latn",
|
|
612
|
+
"arab",
|
|
613
|
+
"deva",
|
|
614
|
+
"beng",
|
|
615
|
+
"guru",
|
|
616
|
+
"gujr",
|
|
617
|
+
"orya",
|
|
618
|
+
"tamldec",
|
|
619
|
+
"telu",
|
|
620
|
+
"knda",
|
|
621
|
+
"mlym",
|
|
622
|
+
"fullwide",
|
|
623
|
+
"hanidec"
|
|
624
|
+
]);
|
|
625
|
+
var digitMapCache = /* @__PURE__ */ new Map();
|
|
626
|
+
function getDigitMap(system) {
|
|
627
|
+
let map = digitMapCache.get(system);
|
|
628
|
+
if (map) return map;
|
|
629
|
+
/* c8 ignore start @preserve -- this branch is dead by construction, not
|
|
630
|
+
just untested: both callers (convertDigits, convertDigitsToAscii)
|
|
631
|
+
already bail out early on system === 'latn' before ever calling
|
|
632
|
+
getDigitMap, so it never actually gets invoked with 'latn'. keeping
|
|
633
|
+
it anyway as a defensive fallback rather than betting that stays
|
|
634
|
+
true forever */
|
|
635
|
+
if (system === "latn") {
|
|
636
|
+
map = {};
|
|
637
|
+
for (let i = 0; i < 10; i++) map[String(i)] = String(i);
|
|
638
|
+
} else {
|
|
639
|
+
/* c8 ignore stop @preserve */
|
|
640
|
+
const fmt = new Intl.NumberFormat("en-US-u-nu-" + system, { useGrouping: false });
|
|
641
|
+
map = {};
|
|
642
|
+
for (let i = 0; i < 10; i++) {
|
|
643
|
+
map[String(i)] = fmt.format(i);
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
digitMapCache.set(system, map);
|
|
647
|
+
return map;
|
|
648
|
+
}
|
|
649
|
+
function convertDigits(s, system) {
|
|
650
|
+
if (system === "latn") return s;
|
|
651
|
+
if (!SUPPORTED_NUMBERING_SYSTEMS.has(system)) {
|
|
652
|
+
throw new InvalidLocaleError({ actual: system, reason: `numbering system "${system}" is not supported. Supported: ${[...SUPPORTED_NUMBERING_SYSTEMS].join(", ")}.` });
|
|
653
|
+
}
|
|
654
|
+
const map = getDigitMap(system);
|
|
655
|
+
let result = "";
|
|
656
|
+
for (const ch of s) {
|
|
657
|
+
if (ch >= "0" && ch <= "9") {
|
|
658
|
+
result += map[ch] ?? ch;
|
|
659
|
+
} else {
|
|
660
|
+
result += ch;
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
return result;
|
|
664
|
+
}
|
|
665
|
+
function applyNumbering(s, options) {
|
|
666
|
+
const system = options.numberingSystem ?? "latn";
|
|
667
|
+
if (system === "latn") return s;
|
|
668
|
+
return convertDigits(s, system);
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
// src/format.ts
|
|
672
|
+
var HANDLER_BY_TOKEN = new Map(TOKENS.map(([tok, fn, field]) => [tok, { fn, field }]));
|
|
673
|
+
var tokenizeCache = /* @__PURE__ */ new Map();
|
|
674
|
+
var MAX_TOKENIZE_CACHE_SIZE = 500;
|
|
675
|
+
function getPieces(formatStr) {
|
|
676
|
+
let pieces = tokenizeCache.get(formatStr);
|
|
677
|
+
if (pieces) return pieces;
|
|
678
|
+
if (tokenizeCache.size >= MAX_TOKENIZE_CACHE_SIZE) {
|
|
679
|
+
const oldestKey = tokenizeCache.keys().next().value;
|
|
680
|
+
if (oldestKey !== void 0) tokenizeCache.delete(oldestKey);
|
|
681
|
+
}
|
|
682
|
+
pieces = tokenize(formatStr);
|
|
683
|
+
tokenizeCache.set(formatStr, pieces);
|
|
684
|
+
return pieces;
|
|
685
|
+
}
|
|
686
|
+
function format(temporal, formatStr, options = {}) {
|
|
687
|
+
if (formatStr.length > MAX_FORMAT_LENGTH) {
|
|
688
|
+
throw new FormatSyntaxError({
|
|
689
|
+
format: formatStr,
|
|
690
|
+
message: `temporal-fmt: format string exceeds maximum length of ${MAX_FORMAT_LENGTH} characters (got ${formatStr.length}).`
|
|
691
|
+
});
|
|
692
|
+
}
|
|
693
|
+
const locale = options.locale ?? DEFAULT_LOCALE;
|
|
694
|
+
const pieces = getPieces(formatStr);
|
|
695
|
+
let result = "";
|
|
696
|
+
for (const piece of pieces) {
|
|
697
|
+
if (piece.kind === "literal") {
|
|
698
|
+
result += piece.value;
|
|
699
|
+
continue;
|
|
700
|
+
}
|
|
701
|
+
const handler = HANDLER_BY_TOKEN.get(piece.value);
|
|
702
|
+
/* c8 ignore start @preserve -- unreachable: tokenize() only ever emits
|
|
703
|
+
tokens present in TOKENS, and HANDLER_BY_TOKEN is built from that
|
|
704
|
+
same list, so every token piece.value can hold already has an entry
|
|
705
|
+
here. Same defensive-guard category as the twin checks in
|
|
706
|
+
analyze.ts/formatDuration.ts. */
|
|
707
|
+
if (!handler) {
|
|
708
|
+
throw new UnknownTokenError({ token: piece.value, format: formatStr, message: `temporal-fmt: unknown token "${piece.value}"` });
|
|
709
|
+
}
|
|
710
|
+
/* c8 ignore stop @preserve */
|
|
711
|
+
if (temporal[handler.field] === void 0) {
|
|
712
|
+
throw new FormatSyntaxError({
|
|
713
|
+
format: formatStr,
|
|
714
|
+
token: piece.value,
|
|
715
|
+
message: `temporal-fmt: token "${piece.value}" requires "${handler.field}", which this Temporal object doesn't have. (e.g. PlainDate has no time fields, PlainTime has no date fields)`
|
|
716
|
+
});
|
|
717
|
+
}
|
|
718
|
+
result += handler.fn(temporal, locale);
|
|
719
|
+
}
|
|
720
|
+
return applyNumbering(result, options);
|
|
721
|
+
}
|
|
722
|
+
function formatToParts(temporal, formatStr, options = {}) {
|
|
723
|
+
if (formatStr.length > MAX_FORMAT_LENGTH) {
|
|
724
|
+
throw new FormatSyntaxError({
|
|
725
|
+
format: formatStr,
|
|
726
|
+
message: `temporal-fmt: format string exceeds maximum length of ${MAX_FORMAT_LENGTH} characters (got ${formatStr.length}).`
|
|
727
|
+
});
|
|
728
|
+
}
|
|
729
|
+
const locale = options.locale ?? DEFAULT_LOCALE;
|
|
730
|
+
const pieces = getPieces(formatStr);
|
|
731
|
+
const result = [];
|
|
732
|
+
for (const piece of pieces) {
|
|
733
|
+
if (piece.kind === "literal") {
|
|
734
|
+
const last = result[result.length - 1];
|
|
735
|
+
/* c8 ignore start @preserve -- unreachable through tokenize()'s own
|
|
736
|
+
output: appendLiteral() in tokenize.ts already merges every
|
|
737
|
+
adjacent literal character before pieces ever reaches here, so
|
|
738
|
+
two 'literal' pieces never sit back-to-back in the array this
|
|
739
|
+
loop walks. Kept as defense-in-depth in case that invariant ever
|
|
740
|
+
changes upstream. */
|
|
741
|
+
if (last && last.type === "literal") {
|
|
742
|
+
last.value += piece.value;
|
|
743
|
+
} else {
|
|
744
|
+
/* c8 ignore stop @preserve */
|
|
745
|
+
result.push({ type: "literal", value: piece.value });
|
|
746
|
+
}
|
|
747
|
+
continue;
|
|
748
|
+
}
|
|
749
|
+
const handler = HANDLER_BY_TOKEN.get(piece.value);
|
|
750
|
+
/* c8 ignore start @preserve -- unreachable, see the note in format() above */
|
|
751
|
+
if (!handler) {
|
|
752
|
+
throw new UnknownTokenError({ token: piece.value, format: formatStr, message: `temporal-fmt: unknown token "${piece.value}"` });
|
|
753
|
+
}
|
|
754
|
+
/* c8 ignore stop @preserve */
|
|
755
|
+
if (temporal[handler.field] === void 0) {
|
|
756
|
+
throw new FormatSyntaxError({
|
|
757
|
+
format: formatStr,
|
|
758
|
+
token: piece.value,
|
|
759
|
+
message: `temporal-fmt: token "${piece.value}" requires "${handler.field}", which this Temporal object doesn't have. (e.g. PlainDate has no time fields, PlainTime has no date fields)`
|
|
760
|
+
});
|
|
761
|
+
}
|
|
762
|
+
result.push({ type: "token", value: applyNumbering(handler.fn(temporal, locale), options), token: piece.value });
|
|
763
|
+
}
|
|
764
|
+
return result;
|
|
765
|
+
}
|
|
766
|
+
function compileFormat(formatStr) {
|
|
767
|
+
if (formatStr.length > MAX_FORMAT_LENGTH) {
|
|
768
|
+
throw new FormatSyntaxError({
|
|
769
|
+
format: formatStr,
|
|
770
|
+
message: `temporal-fmt: format string exceeds maximum length of ${MAX_FORMAT_LENGTH} characters (got ${formatStr.length}).`
|
|
771
|
+
});
|
|
772
|
+
}
|
|
773
|
+
const pieces = getPieces(formatStr);
|
|
774
|
+
return {
|
|
775
|
+
formatStr,
|
|
776
|
+
pieces,
|
|
777
|
+
format(temporal, options = {}) {
|
|
778
|
+
const locale = options.locale ?? DEFAULT_LOCALE;
|
|
779
|
+
let result = "";
|
|
780
|
+
for (const piece of pieces) {
|
|
781
|
+
if (piece.kind === "literal") {
|
|
782
|
+
result += piece.value;
|
|
783
|
+
continue;
|
|
784
|
+
}
|
|
785
|
+
const handler = HANDLER_BY_TOKEN.get(piece.value);
|
|
786
|
+
if (!handler) throw new UnknownTokenError({ token: piece.value, format: formatStr, message: `temporal-fmt: unknown token "${piece.value}"` });
|
|
787
|
+
if (temporal[handler.field] === void 0) {
|
|
788
|
+
throw new FormatSyntaxError({
|
|
789
|
+
format: formatStr,
|
|
790
|
+
token: piece.value,
|
|
791
|
+
message: `temporal-fmt: token "${piece.value}" requires "${handler.field}", which this Temporal object doesn't have. (e.g. PlainDate has no time fields, PlainTime has no date fields)`
|
|
792
|
+
});
|
|
793
|
+
}
|
|
794
|
+
result += handler.fn(temporal, locale);
|
|
795
|
+
}
|
|
796
|
+
return applyNumbering(result, options);
|
|
797
|
+
},
|
|
798
|
+
formatToParts(temporal, options = {}) {
|
|
799
|
+
const locale = options.locale ?? DEFAULT_LOCALE;
|
|
800
|
+
const out = [];
|
|
801
|
+
for (const piece of pieces) {
|
|
802
|
+
if (piece.kind === "literal") {
|
|
803
|
+
const last = out[out.length - 1];
|
|
804
|
+
if (last && last.type === "literal") last.value += piece.value;
|
|
805
|
+
else out.push({ type: "literal", value: piece.value });
|
|
806
|
+
continue;
|
|
807
|
+
}
|
|
808
|
+
const handler = HANDLER_BY_TOKEN.get(piece.value);
|
|
809
|
+
if (!handler) throw new UnknownTokenError({ token: piece.value, format: formatStr, message: `temporal-fmt: unknown token "${piece.value}"` });
|
|
810
|
+
if (temporal[handler.field] === void 0) {
|
|
811
|
+
throw new FormatSyntaxError({
|
|
812
|
+
format: formatStr,
|
|
813
|
+
token: piece.value,
|
|
814
|
+
message: `temporal-fmt: token "${piece.value}" requires "${handler.field}", which this Temporal object doesn't have. (e.g. PlainDate has no time fields, PlainTime has no date fields)`
|
|
815
|
+
});
|
|
816
|
+
}
|
|
817
|
+
out.push({ type: "token", value: applyNumbering(handler.fn(temporal, locale), options), token: piece.value });
|
|
818
|
+
}
|
|
819
|
+
return out;
|
|
820
|
+
}
|
|
821
|
+
};
|
|
822
|
+
}
|
|
823
|
+
function _getPieces(formatStr) {
|
|
824
|
+
return getPieces(formatStr);
|
|
825
|
+
}
|
|
826
|
+
function _handlerFor(token) {
|
|
827
|
+
return HANDLER_BY_TOKEN.get(token);
|
|
828
|
+
}
|
|
829
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
830
|
+
0 && (module.exports = {
|
|
831
|
+
_getPieces,
|
|
832
|
+
_handlerFor,
|
|
833
|
+
compileFormat,
|
|
834
|
+
format,
|
|
835
|
+
formatToParts
|
|
836
|
+
});
|
|
39
837
|
//# sourceMappingURL=format.cjs.map
|