@mezzanine-ui/core 1.0.4 → 1.1.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.
@@ -0,0 +1,912 @@
1
+ import range from 'lodash/range';
2
+ import chunk from 'lodash/chunk';
3
+ import { isISOWeekLocale } from '../calendar/calendar.js';
4
+ import { buildParseRegex, applyParseRegex, formatTokens } from './tokens.js';
5
+
6
+ /**
7
+ * CalendarMethodsTemporal — JS-native Temporal implementation of CalendarMethods.
8
+ *
9
+ * Wire format: ISO 8601 string (matches the moment/dayjs adapters, NOT the
10
+ * `Temporal.PlainDateTime#toString()` form). The internal representation is
11
+ * `Temporal.ZonedDateTime` in the system time zone, mirroring how dayjs/moment
12
+ * default to local time when reading/writing ISO strings.
13
+ *
14
+ * Locale week support: uses `Intl.Locale(locale).weekInfo` (Stage 3 / widely
15
+ * available) to determine the first day of week and minimal days in week 1.
16
+ * Falls back to the static ISO_WEEK_LOCALES set when `weekInfo` is missing
17
+ * from the runtime.
18
+ *
19
+ * Polyfill: this module relies on `globalThis.Temporal`. Apps that need to
20
+ * support Safari or Node SSR must install `@js-temporal/polyfill` and
21
+ * register it on `globalThis` from a Client Component (or equivalent client
22
+ * entry point) BEFORE this module is imported:
23
+ *
24
+ * import { Temporal } from '@js-temporal/polyfill';
25
+ * (globalThis as { Temporal?: unknown }).Temporal = Temporal;
26
+ *
27
+ * If Temporal is not present at the time any method is invoked, the call
28
+ * throws with the same installation guide.
29
+ */
30
+ /**
31
+ * Resolve the active Temporal namespace. Throws a helpful installation
32
+ * message if the polyfill (or native API) is unavailable.
33
+ */
34
+ function getTemporal() {
35
+ const globalRef = globalThis;
36
+ if (!globalRef.Temporal) {
37
+ throw new Error('[@mezzanine-ui/core] CalendarMethodsTemporal requires the JS Temporal API. ' +
38
+ 'Install `@js-temporal/polyfill` and register it on globalThis at your app entry, ' +
39
+ 'BEFORE any code imports CalendarMethodsTemporal:\n' +
40
+ ' import { Temporal } from "@js-temporal/polyfill";\n' +
41
+ ' (globalThis as { Temporal?: unknown }).Temporal = Temporal;\n' +
42
+ 'Or run on a runtime with native Temporal support ' +
43
+ '(Chrome 144+, Firefox 139+, Node 24+ with --harmony-temporal).');
44
+ }
45
+ return globalRef.Temporal;
46
+ }
47
+ const weekInfoCache = new Map();
48
+ /**
49
+ * Best-effort default for `minimalDays` on runtimes whose
50
+ * `Intl.Locale#weekInfo` exposes `firstDay`/`weekend` but not `minimalDays`
51
+ * (older V8 versions, Safari incomplete impls). The static
52
+ * `ISO_WEEK_LOCALES` set is the same source the other adapters use, so
53
+ * deferring to it here keeps Temporal's ISO detection consistent with
54
+ * dayjs/moment when the runtime is partially equipped.
55
+ *
56
+ * Variant-aware: the static set lists country variants like `de-de` and
57
+ * the language code `de`, but not every variant. For `de-AT` / `fr-CH`
58
+ * etc. we fall back to the language-only lookup so ISO inheritance is
59
+ * preserved when CLDR `minimalDays` is missing from the runtime.
60
+ */
61
+ function defaultMinimalDays(locale) {
62
+ if (isISOWeekLocale(locale))
63
+ return 4;
64
+ const language = locale.split('-')[0].toLowerCase();
65
+ if (language &&
66
+ language !== locale.toLowerCase() &&
67
+ isISOWeekLocale(language)) {
68
+ return 4;
69
+ }
70
+ return 1;
71
+ }
72
+ /**
73
+ * Read locale week info from `Intl.Locale#weekInfo`. Falls back to the
74
+ * mezzanine ISO_WEEK_LOCALES set when the runtime lacks weekInfo support
75
+ * entirely OR when the returned object is missing `minimalDays`.
76
+ */
77
+ function getWeekInfo(locale) {
78
+ var _a, _b, _c;
79
+ const cached = weekInfoCache.get(locale);
80
+ if (cached)
81
+ return cached;
82
+ let info;
83
+ try {
84
+ const intlLocale = new Intl.Locale(locale);
85
+ const raw = (_a = intlLocale.weekInfo) !== null && _a !== void 0 ? _a : (_b = intlLocale.getWeekInfo) === null || _b === void 0 ? void 0 : _b.call(intlLocale);
86
+ if (raw && typeof raw.firstDay === 'number') {
87
+ info = {
88
+ firstDay: raw.firstDay,
89
+ // Trust the runtime's firstDay (fresher than the static set and
90
+ // covers all 7 weekdays), but fall back to the static set for
91
+ // minimalDays so ISO locales stay classified as ISO when the
92
+ // runtime omits the field.
93
+ minimalDays: typeof raw.minimalDays === 'number'
94
+ ? raw.minimalDays
95
+ : defaultMinimalDays(locale),
96
+ weekend: (_c = raw.weekend) !== null && _c !== void 0 ? _c : [6, 7],
97
+ };
98
+ }
99
+ else {
100
+ throw new Error('weekInfo unavailable');
101
+ }
102
+ }
103
+ catch (_d) {
104
+ info = isISOWeekLocale(locale)
105
+ ? { firstDay: 1, minimalDays: 4, weekend: [6, 7] }
106
+ : { firstDay: 7, minimalDays: 1, weekend: [6, 7] };
107
+ }
108
+ weekInfoCache.set(locale, info);
109
+ return info;
110
+ }
111
+ /**
112
+ * Whether a locale follows ISO 8601 week rules (Monday-first AND week 1
113
+ * contains at least 4 days of the new year).
114
+ *
115
+ * Used as the fast-path predicate for picking Temporal's native
116
+ * `weekOfYear` / `yearOfWeek` (which are ISO-defined). For non-ISO Monday-
117
+ * first locales such as `en-AU`, `en-NZ`, `ar-AE`, `zh-CN` (firstDay=1 but
118
+ * minimalDays=1), the locale algorithm in `getLocaleWeekInfo` is required —
119
+ * Temporal's native week math would yield wrong results around year ends.
120
+ */
121
+ function usesISOWeekRules(locale) {
122
+ const info = getWeekInfo(locale);
123
+ return info.firstDay === 1 && info.minimalDays === 4;
124
+ }
125
+ const systemTimeZone = () => getTemporal().Now.zonedDateTimeISO().timeZoneId;
126
+ /**
127
+ * Parse an ISO 8601 string into a ZonedDateTime in the system timezone.
128
+ * Accepts inputs with `Z` suffix, with offset, or naked PlainDateTime form.
129
+ */
130
+ function parseISO(value) {
131
+ const Temporal = getTemporal();
132
+ const tz = systemTimeZone();
133
+ try {
134
+ return Temporal.Instant.from(value).toZonedDateTimeISO(tz);
135
+ }
136
+ catch (_a) {
137
+ // Naked datetime / partial form — fall back to PlainDateTime semantics.
138
+ }
139
+ try {
140
+ return Temporal.PlainDateTime.from(value).toZonedDateTime(tz);
141
+ }
142
+ catch (_b) {
143
+ return Temporal.PlainDate.from(value)
144
+ .toPlainDateTime(Temporal.PlainTime.from('00:00:00'))
145
+ .toZonedDateTime(tz);
146
+ }
147
+ }
148
+ /** Round-trip a ZonedDateTime back to UTC ISO string (matches dayjs/moment wire format). */
149
+ function toISO(value) {
150
+ return value.toInstant().toString();
151
+ }
152
+ const isoDow = (zdt) => zdt.dayOfWeek;
153
+ /**
154
+ * Move the given date back to the start of the week as defined by the locale.
155
+ * `firstDay` follows ISO numbering (1=Mon..7=Sun).
156
+ */
157
+ function startOfWeekDate(date, firstDay) {
158
+ const offset = (date.dayOfWeek - firstDay + 7) % 7;
159
+ return date.subtract({ days: offset });
160
+ }
161
+ /**
162
+ * Compute the locale-aware week number for a given date using the standard
163
+ * CLDR/ICU algorithm: week 1 of year Y is the week containing Jan `minimalDays` of Y.
164
+ */
165
+ function getLocaleWeekInfo(zdt, locale) {
166
+ const Temporal = getTemporal();
167
+ const { firstDay, minimalDays } = getWeekInfo(locale);
168
+ const date = zdt.toPlainDate();
169
+ const thisWeekStart = startOfWeekDate(date, firstDay);
170
+ let year = date.year;
171
+ const week1StartFor = (y) => startOfWeekDate(Temporal.PlainDate.from({
172
+ year: y,
173
+ month: 1,
174
+ day: minimalDays,
175
+ }), firstDay);
176
+ let week1Start = week1StartFor(year);
177
+ if (Temporal.PlainDate.compare(thisWeekStart, week1Start) < 0) {
178
+ year -= 1;
179
+ week1Start = week1StartFor(year);
180
+ }
181
+ else {
182
+ const nextYearWeek1 = week1StartFor(year + 1);
183
+ if (Temporal.PlainDate.compare(thisWeekStart, nextYearWeek1) >= 0) {
184
+ year += 1;
185
+ week1Start = nextYearWeek1;
186
+ }
187
+ }
188
+ const daysDiff = thisWeekStart.since(week1Start, {
189
+ largestUnit: 'days',
190
+ }).days;
191
+ return {
192
+ week: Math.floor(daysDiff / 7) + 1,
193
+ weekYear: year,
194
+ };
195
+ }
196
+ /** Cached Intl formatters keyed by `${locale}|${kind}`. */
197
+ const formatterCache = new Map();
198
+ function getFormatter(locale, options) {
199
+ const key = `${locale}|${JSON.stringify(options)}`;
200
+ let fmt = formatterCache.get(key);
201
+ if (!fmt) {
202
+ fmt = new Intl.DateTimeFormat(locale, options);
203
+ formatterCache.set(key, fmt);
204
+ }
205
+ return fmt;
206
+ }
207
+ function intlMonthNames(locale, width) {
208
+ // Force Gregorian — locales whose default Intl calendar is non-Gregorian
209
+ // (fa-IR → Persian solar Hijri, others may be Buddhist / Hijri / Japanese)
210
+ // would otherwise emit month names from a different calendar system while
211
+ // our Temporal date math is ISO/Gregorian, producing wrong labels.
212
+ const fmt = getFormatter(locale, { month: width, calendar: 'gregory' });
213
+ return range(0, 12).map((m) => {
214
+ const date = new Date(Date.UTC(2024, m, 15));
215
+ return fmt.format(date);
216
+ });
217
+ }
218
+ function intlWeekdayNames(locale, width) {
219
+ // 2024-01-07 is Sunday; subsequent days run through Saturday.
220
+ const fmt = getFormatter(locale, {
221
+ weekday: width,
222
+ timeZone: 'UTC',
223
+ calendar: 'gregory',
224
+ });
225
+ return range(0, 7).map((d) => {
226
+ const date = new Date(Date.UTC(2024, 0, 7 + d));
227
+ return fmt.format(date);
228
+ });
229
+ }
230
+ function startOf(zdt, granularity) {
231
+ const lower = granularity.toLowerCase();
232
+ if (lower === 'year' || lower === 'years' || lower === 'y') {
233
+ return zdt.with({
234
+ month: 1,
235
+ day: 1,
236
+ hour: 0,
237
+ minute: 0,
238
+ second: 0,
239
+ millisecond: 0,
240
+ microsecond: 0,
241
+ nanosecond: 0,
242
+ });
243
+ }
244
+ if (lower === 'month' || lower === 'months') {
245
+ return zdt.with({
246
+ day: 1,
247
+ hour: 0,
248
+ minute: 0,
249
+ second: 0,
250
+ millisecond: 0,
251
+ microsecond: 0,
252
+ nanosecond: 0,
253
+ });
254
+ }
255
+ if (lower === 'quarter' || lower === 'quarters' || lower === 'q') {
256
+ const quarterStartMonth = Math.floor((zdt.month - 1) / 3) * 3 + 1;
257
+ return zdt.with({
258
+ month: quarterStartMonth,
259
+ day: 1,
260
+ hour: 0,
261
+ minute: 0,
262
+ second: 0,
263
+ millisecond: 0,
264
+ microsecond: 0,
265
+ nanosecond: 0,
266
+ });
267
+ }
268
+ if (lower === 'week' ||
269
+ lower === 'weeks' ||
270
+ lower === 'isoweek' ||
271
+ lower === 'isoweeks') {
272
+ // Match dayjs/moment defaults when no locale is in scope:
273
+ // `isoWeek` → always Monday-first (firstDay = 1)
274
+ // `week` → Sunday-first by default (firstDay = 7)
275
+ // Locale-aware week bucketing happens through getCurrentWeekFirstDate,
276
+ // which is the path mezzanine components actually use.
277
+ const isoWeekMode = lower.startsWith('isoweek');
278
+ const firstDay = isoWeekMode ? 1 : 7;
279
+ const offset = (zdt.dayOfWeek - firstDay + 7) % 7;
280
+ return zdt.subtract({ days: offset }).with({
281
+ hour: 0,
282
+ minute: 0,
283
+ second: 0,
284
+ millisecond: 0,
285
+ microsecond: 0,
286
+ nanosecond: 0,
287
+ });
288
+ }
289
+ if (lower === 'day' ||
290
+ lower === 'days' ||
291
+ lower === 'd' ||
292
+ lower === 'date') {
293
+ return zdt.with({
294
+ hour: 0,
295
+ minute: 0,
296
+ second: 0,
297
+ millisecond: 0,
298
+ microsecond: 0,
299
+ nanosecond: 0,
300
+ });
301
+ }
302
+ if (lower === 'hour' || lower === 'hours' || lower === 'h') {
303
+ return zdt.with({
304
+ minute: 0,
305
+ second: 0,
306
+ millisecond: 0,
307
+ microsecond: 0,
308
+ nanosecond: 0,
309
+ });
310
+ }
311
+ if (lower === 'minute' || lower === 'minutes') {
312
+ return zdt.with({
313
+ second: 0,
314
+ millisecond: 0,
315
+ microsecond: 0,
316
+ nanosecond: 0,
317
+ });
318
+ }
319
+ if (lower === 'second' || lower === 'seconds' || lower === 's') {
320
+ return zdt.with({
321
+ millisecond: 0,
322
+ microsecond: 0,
323
+ nanosecond: 0,
324
+ });
325
+ }
326
+ // Unknown granularity — return as-is (matches dayjs/moment behavior).
327
+ return zdt;
328
+ }
329
+ function dayOfYear(zdt) {
330
+ return zdt.dayOfYear;
331
+ }
332
+ const CalendarMethodsTemporal = {
333
+ getFirstDayOfWeek: (locale) => {
334
+ const fd = getWeekInfo(locale).firstDay;
335
+ // ISO 1..7 → mezzanine 0..6 (0=Sun, 1=Mon ..)
336
+ return fd === 7 ? 0 : fd;
337
+ },
338
+ // Note: this differs from the global `isISOWeekLocale` (a static set in
339
+ // calendar/calendar.ts shared with the dayjs/moment adapters). The static
340
+ // set can disagree with CLDR `weekInfo` (e.g. it lists ar-SA as ISO, but
341
+ // ar-SA is Sunday-first per CLDR). Inside the Temporal adapter, this
342
+ // method reflects the same source of truth used everywhere else.
343
+ isISOWeekLocale: usesISOWeekRules,
344
+ getNow: () => toISO(getTemporal().Now.zonedDateTimeISO()),
345
+ getSecond: (date) => parseISO(date).second,
346
+ getMinute: (date) => parseISO(date).minute,
347
+ getHour: (date) => parseISO(date).hour,
348
+ getDate: (date) => parseISO(date).day,
349
+ getWeek: (date, locale) => {
350
+ var _a;
351
+ if (usesISOWeekRules(locale)) {
352
+ return (_a = parseISO(date).weekOfYear) !== null && _a !== void 0 ? _a : 1;
353
+ }
354
+ return getLocaleWeekInfo(parseISO(date), locale).week;
355
+ },
356
+ getWeekYear: (date, locale) => {
357
+ var _a;
358
+ if (usesISOWeekRules(locale)) {
359
+ return (_a = parseISO(date).yearOfWeek) !== null && _a !== void 0 ? _a : parseISO(date).year;
360
+ }
361
+ return getLocaleWeekInfo(parseISO(date), locale).weekYear;
362
+ },
363
+ getWeekDay: (date) => {
364
+ const dow = isoDow(parseISO(date));
365
+ return dow === 7 ? 0 : dow;
366
+ },
367
+ getMonth: (date) => parseISO(date).month - 1,
368
+ getYear: (date) => parseISO(date).year,
369
+ getQuarter: (date) => Math.floor((parseISO(date).month - 1) / 3) + 1,
370
+ getHalfYear: (date) => Math.floor((parseISO(date).month - 1) / 6) + 1,
371
+ getWeekDayNames: (locale) => {
372
+ // Intl returns Sunday-indexed names. Rotate left by `firstDay % 7` so the
373
+ // array is ordered starting from the locale's first-day-of-week.
374
+ const sundayIndexed = intlWeekdayNames(locale, 'narrow');
375
+ const offset = getWeekInfo(locale).firstDay % 7;
376
+ return [...sundayIndexed.slice(offset), ...sundayIndexed.slice(0, offset)];
377
+ },
378
+ getMonthShortName: (month, locale) => {
379
+ return intlMonthNames(locale, 'short')[month];
380
+ },
381
+ getMonthShortNames: (locale) => intlMonthNames(locale, 'short'),
382
+ getWeekends: (locale) => {
383
+ // weekInfo.weekend lists the weekend days using ISO numbering (1=Mon..7=Sun).
384
+ // Project that set onto the locale's first-day-rotated week positions.
385
+ const { firstDay, weekend } = getWeekInfo(locale);
386
+ const weekendSet = new Set(weekend);
387
+ const result = [];
388
+ for (let p = 0; p < 7; p += 1) {
389
+ const isoDay = ((p + firstDay - 1) % 7) + 1;
390
+ result.push(weekendSet.has(isoDay));
391
+ }
392
+ return result;
393
+ },
394
+ addHour: (date, diff) => toISO(parseISO(date).add({ hours: diff })),
395
+ addMinute: (date, diff) => toISO(parseISO(date).add({ minutes: diff })),
396
+ addSecond: (date, diff) => toISO(parseISO(date).add({ seconds: diff })),
397
+ addDay: (date, diff) => toISO(parseISO(date).add({ days: diff })),
398
+ addYear: (date, diff) => toISO(parseISO(date).add({ years: diff })),
399
+ addMonth: (date, diff) => toISO(parseISO(date).add({ months: diff })),
400
+ setMillisecond: (date, millisecond) => toISO(parseISO(date).with({ millisecond })),
401
+ setSecond: (date, second) => toISO(parseISO(date).with({ second })),
402
+ setMinute: (date, minute) => toISO(parseISO(date).with({ minute })),
403
+ setHour: (date, hour) => toISO(parseISO(date).with({ hour })),
404
+ // Out-of-range setters: moment/dayjs normalize values like `setMonth(d, -1)`
405
+ // (→ previous year's December) or `setDate(d, 0)` (→ last day of previous
406
+ // month). Temporal's `with()` rejects month=0 and clamps day overflow, so
407
+ // we route through `add()` to preserve cross-boundary semantics.
408
+ setMonth: (date, month) => {
409
+ const current = parseISO(date);
410
+ return toISO(current.add({ months: month - (current.month - 1) }));
411
+ },
412
+ setYear: (date, year) => {
413
+ const current = parseISO(date);
414
+ return toISO(current.add({ years: year - current.year }));
415
+ },
416
+ setDate: (date, target) => {
417
+ const current = parseISO(date);
418
+ return toISO(current.add({ days: target - current.day }));
419
+ },
420
+ startOf: (target, granularity) => toISO(startOf(parseISO(target), String(granularity))),
421
+ getCurrentWeekFirstDate: (value, locale) => {
422
+ const zdt = parseISO(value);
423
+ const { firstDay } = getWeekInfo(locale);
424
+ const offset = (zdt.dayOfWeek - firstDay + 7) % 7;
425
+ return toISO(zdt.subtract({ days: offset }).with({
426
+ hour: 0,
427
+ minute: 0,
428
+ second: 0,
429
+ millisecond: 0,
430
+ microsecond: 0,
431
+ nanosecond: 0,
432
+ }));
433
+ },
434
+ getCurrentMonthFirstDate: (value) => toISO(parseISO(value).with({
435
+ day: 1,
436
+ hour: 0,
437
+ minute: 0,
438
+ second: 0,
439
+ millisecond: 0,
440
+ microsecond: 0,
441
+ nanosecond: 0,
442
+ })),
443
+ getCurrentYearFirstDate: (value) => toISO(parseISO(value).with({
444
+ month: 1,
445
+ day: 1,
446
+ hour: 0,
447
+ minute: 0,
448
+ second: 0,
449
+ millisecond: 0,
450
+ microsecond: 0,
451
+ nanosecond: 0,
452
+ })),
453
+ getCurrentQuarterFirstDate: (value) => {
454
+ const zdt = parseISO(value);
455
+ const quarterStartMonth = Math.floor((zdt.month - 1) / 3) * 3 + 1;
456
+ return toISO(zdt.with({
457
+ month: quarterStartMonth,
458
+ day: 1,
459
+ hour: 0,
460
+ minute: 0,
461
+ second: 0,
462
+ millisecond: 0,
463
+ microsecond: 0,
464
+ nanosecond: 0,
465
+ }));
466
+ },
467
+ getCurrentHalfYearFirstDate: (value) => {
468
+ const zdt = parseISO(value);
469
+ const halfYearStartMonth = Math.floor((zdt.month - 1) / 6) * 6 + 1;
470
+ return toISO(zdt.with({
471
+ month: halfYearStartMonth,
472
+ day: 1,
473
+ hour: 0,
474
+ minute: 0,
475
+ second: 0,
476
+ millisecond: 0,
477
+ microsecond: 0,
478
+ nanosecond: 0,
479
+ }));
480
+ },
481
+ getCalendarGrid: (target, locale) => {
482
+ const Temporal = getTemporal();
483
+ const zdt = parseISO(target);
484
+ const { firstDay } = getWeekInfo(locale);
485
+ const firstOfMonth = zdt.with({ day: 1 });
486
+ const firstDayWeekdayIso = firstOfMonth.dayOfWeek; // 1..7
487
+ const lastDateOfPrevMonth = firstOfMonth.subtract({ days: 1 }).day;
488
+ const lastDateOfCurrentMonth = Temporal.PlainYearMonth.from({
489
+ year: zdt.year,
490
+ month: zdt.month,
491
+ }).daysInMonth;
492
+ // How many cells from the previous month appear before day-1: the offset
493
+ // between the first day of the month and the locale's first-day-of-week.
494
+ const daysFromPrevMonth = (firstDayWeekdayIso - firstDay + 7) % 7;
495
+ const totalDaysInGrid = 42;
496
+ const daysFromNextMonth = totalDaysInGrid - daysFromPrevMonth - lastDateOfCurrentMonth;
497
+ return chunk([
498
+ ...range(lastDateOfPrevMonth - daysFromPrevMonth + 1, lastDateOfPrevMonth + 1),
499
+ ...range(1, lastDateOfCurrentMonth + 1),
500
+ ...range(1, daysFromNextMonth + 1),
501
+ ], 7);
502
+ },
503
+ isValid: (date) => {
504
+ // Trigger the polyfill guard up-front: a missing Temporal API is an
505
+ // environmental error that callers must see, not a "this date string
506
+ // is invalid" answer. Only the actual parsing step is wrapped.
507
+ getTemporal();
508
+ try {
509
+ parseISO(date);
510
+ return true;
511
+ }
512
+ catch (_a) {
513
+ return false;
514
+ }
515
+ },
516
+ isBefore: (target, comparison) => {
517
+ const Temporal = getTemporal();
518
+ return (Temporal.ZonedDateTime.compare(parseISO(target), parseISO(comparison)) < 0);
519
+ },
520
+ isBetween: (value, target1, target2, granularity) => {
521
+ const Temporal = getTemporal();
522
+ const a = parseISO(target1);
523
+ const b = parseISO(target2);
524
+ const v = parseISO(value);
525
+ // Truncate to the requested granularity so that values within the same
526
+ // bucket compare equal (e.g. `'day'` ignores time-of-day differences).
527
+ // Default granularity matches dayjs/moment default of `'milliseconds'`,
528
+ // which is effectively no truncation.
529
+ const granularityKey = typeof granularity === 'string' && granularity.length > 0
530
+ ? granularity
531
+ : 'milliseconds';
532
+ const aT = startOf(a, granularityKey);
533
+ const bT = startOf(b, granularityKey);
534
+ const vT = startOf(v, granularityKey);
535
+ const lo = Temporal.ZonedDateTime.compare(aT, bT) <= 0 ? aT : bT;
536
+ const hi = Temporal.ZonedDateTime.compare(aT, bT) <= 0 ? bT : aT;
537
+ // Match dayjs/moment default inclusivity `'()'` — both bounds exclusive.
538
+ return (Temporal.ZonedDateTime.compare(vT, lo) > 0 &&
539
+ Temporal.ZonedDateTime.compare(vT, hi) < 0);
540
+ },
541
+ isSameDate: (dateOne, dateTwo) => {
542
+ const a = parseISO(dateOne);
543
+ const b = parseISO(dateTwo);
544
+ return a.year === b.year && a.month === b.month && a.day === b.day;
545
+ },
546
+ isSameWeek: (dateOne, dateTwo, locale) => {
547
+ var _a, _b, _c, _d;
548
+ if (usesISOWeekRules(locale)) {
549
+ const a = parseISO(dateOne);
550
+ const b = parseISO(dateTwo);
551
+ return (((_a = a.yearOfWeek) !== null && _a !== void 0 ? _a : a.year) === ((_b = b.yearOfWeek) !== null && _b !== void 0 ? _b : b.year) &&
552
+ ((_c = a.weekOfYear) !== null && _c !== void 0 ? _c : 1) === ((_d = b.weekOfYear) !== null && _d !== void 0 ? _d : 1));
553
+ }
554
+ const a = getLocaleWeekInfo(parseISO(dateOne), locale);
555
+ const b = getLocaleWeekInfo(parseISO(dateTwo), locale);
556
+ return a.week === b.week && a.weekYear === b.weekYear;
557
+ },
558
+ isInMonth: (target, month) => parseISO(target).month === month + 1,
559
+ isDateIncluded: (date, targets) => targets.some((target) => CalendarMethodsTemporal.isSameDate(date, target)),
560
+ isWeekIncluded: (firstDateOfWeek, targets, locale) => targets.some((target) => CalendarMethodsTemporal.isSameWeek(firstDateOfWeek, target, locale)),
561
+ isMonthIncluded: (date, targets) => {
562
+ const a = parseISO(date);
563
+ return targets.some((target) => {
564
+ const t = parseISO(target);
565
+ return a.year === t.year && a.month === t.month;
566
+ });
567
+ },
568
+ isYearIncluded: (date, targets) => {
569
+ const y = parseISO(date).year;
570
+ return targets.some((target) => parseISO(target).year === y);
571
+ },
572
+ isQuarterIncluded: (date, targets) => {
573
+ const a = parseISO(date);
574
+ const q = Math.floor((a.month - 1) / 3);
575
+ return targets.some((target) => {
576
+ const t = parseISO(target);
577
+ return t.year === a.year && Math.floor((t.month - 1) / 3) === q;
578
+ });
579
+ },
580
+ isHalfYearIncluded: (date, targets) => {
581
+ const a = parseISO(date);
582
+ const h = Math.floor((a.month - 1) / 6);
583
+ return targets.some((target) => {
584
+ const t = parseISO(target);
585
+ return t.year === a.year && Math.floor((t.month - 1) / 6) === h;
586
+ });
587
+ },
588
+ formatToString: (locale, date, format) => {
589
+ var _a, _b;
590
+ const isoInput = typeof date === 'string' ? date : date.toISOString();
591
+ const zdt = parseISO(isoInput);
592
+ const isoWeek = (_a = zdt.weekOfYear) !== null && _a !== void 0 ? _a : 1;
593
+ const isoWeekYear = (_b = zdt.yearOfWeek) !== null && _b !== void 0 ? _b : zdt.year;
594
+ const localeInfo = usesISOWeekRules(locale)
595
+ ? { week: isoWeek, weekYear: isoWeekYear }
596
+ : getLocaleWeekInfo(zdt, locale);
597
+ const monthShorts = intlMonthNames(locale, 'short');
598
+ const monthLongs = intlMonthNames(locale, 'long');
599
+ const weekdayShorts = intlWeekdayNames(locale, 'short');
600
+ const weekdayLongs = intlWeekdayNames(locale, 'long');
601
+ const weekdayNarrows = intlWeekdayNames(locale, 'narrow');
602
+ const sundayIndex = zdt.dayOfWeek === 7 ? 0 : zdt.dayOfWeek;
603
+ const { firstDay } = getWeekInfo(locale);
604
+ const localeWeekDay = (zdt.dayOfWeek - firstDay + 7) % 7;
605
+ return formatTokens(format, {
606
+ year: zdt.year,
607
+ month: zdt.month,
608
+ day: zdt.day,
609
+ hour: zdt.hour,
610
+ minute: zdt.minute,
611
+ second: zdt.second,
612
+ millisecond: zdt.millisecond,
613
+ dayOfWeek: zdt.dayOfWeek,
614
+ localeWeekDay,
615
+ isoWeek,
616
+ isoWeekYear,
617
+ localeWeek: localeInfo.week,
618
+ localeWeekYear: localeInfo.weekYear,
619
+ quarter: Math.floor((zdt.month - 1) / 3) + 1,
620
+ halfYear: zdt.month <= 6 ? 1 : 2,
621
+ monthShort: monthShorts[zdt.month - 1],
622
+ monthLong: monthLongs[zdt.month - 1],
623
+ weekdayShort: weekdayShorts[sundayIndex],
624
+ weekdayLong: weekdayLongs[sundayIndex],
625
+ weekdayNarrow: weekdayNarrows[sundayIndex],
626
+ dayOfYear: dayOfYear(zdt),
627
+ unixMs: Number(zdt.epochMilliseconds),
628
+ });
629
+ },
630
+ formatToISOString: (date) => toISO(parseISO(date)),
631
+ parseFormattedValue: (text, format, locale) => {
632
+ var _a, _b, _c, _d, _e, _f;
633
+ const Temporal = getTemporal();
634
+ const localeNames = {
635
+ monthShort: intlMonthNames(locale, 'short'),
636
+ monthLong: intlMonthNames(locale, 'long'),
637
+ weekdayShort: intlWeekdayNames(locale, 'short'),
638
+ weekdayLong: intlWeekdayNames(locale, 'long'),
639
+ weekdayNarrow: intlWeekdayNames(locale, 'narrow'),
640
+ };
641
+ const { regex, captures, tokens } = buildParseRegex(format, localeNames);
642
+ const fields = applyParseRegex(text, regex, captures, localeNames);
643
+ if (!fields)
644
+ return undefined;
645
+ if (!validateFields(fields))
646
+ return undefined;
647
+ // If a token was present but couldn't be parsed (e.g. half-year out of range),
648
+ // treat the whole parse as failed instead of silently dropping the field.
649
+ if (tokens.has('[H]n') && fields.halfYear === undefined)
650
+ return undefined;
651
+ if (tokens.has('Q') && fields.quarter === undefined)
652
+ return undefined;
653
+ const tz = systemTimeZone();
654
+ // ISO mode is determined by the *weekYear* token (GGGG), not the week
655
+ // number tokens. Mixed formats like `YYYY-WW` (calendar year + ISO week)
656
+ // would otherwise enable isoMode but lack `fields.isoWeekYear`, silently
657
+ // dropping the parsed week number and falling through to the year-only
658
+ // branch.
659
+ const isoMode = tokens.has('GGGG');
660
+ const localeMode = tokens.has('gggg') || tokens.has('ww') || tokens.has('w');
661
+ const hasYear = fields.year !== undefined;
662
+ const hasMonth = fields.month !== undefined;
663
+ const hasDay = fields.day !== undefined;
664
+ const hasQuarter = fields.quarter !== undefined;
665
+ const hasHalfYear = fields.halfYear !== undefined;
666
+ const hasWeek = isoMode ||
667
+ localeMode ||
668
+ fields.localeWeek !== undefined ||
669
+ fields.isoWeek !== undefined;
670
+ const hasUnixMs = fields.unixMs !== undefined;
671
+ const hasDayOfYear = fields.dayOfYear !== undefined;
672
+ // Unix epoch — standalone enough to derive a full datetime regardless
673
+ // of whether other date tokens were present in the format.
674
+ if (hasUnixMs) {
675
+ try {
676
+ return Temporal.Instant.fromEpochMilliseconds(fields.unixMs).toString();
677
+ }
678
+ catch (_g) {
679
+ return undefined;
680
+ }
681
+ }
682
+ // Day-of-year + year — derive the date by adding (doy-1) days to Jan 1.
683
+ if (hasDayOfYear && hasYear && !hasMonth && !hasDay) {
684
+ const year = fields.year;
685
+ const doy = fields.dayOfYear;
686
+ const jan1 = Temporal.PlainDate.from({ year, month: 1, day: 1 });
687
+ const daysInYear = Temporal.PlainDate.from({
688
+ year: year + 1,
689
+ month: 1,
690
+ day: 1,
691
+ }).since(jan1, { largestUnit: 'days' }).days;
692
+ if (doy < 1 || doy > daysInYear)
693
+ return undefined;
694
+ return toISO(jan1
695
+ .add({ days: doy - 1 })
696
+ .toPlainDateTime(Temporal.PlainTime.from('00:00:00'))
697
+ .toZonedDateTime(tz));
698
+ }
699
+ // Half-year: re-route to quarter-based normalization.
700
+ if (hasHalfYear && hasYear && !hasMonth && !hasDay) {
701
+ const quarter = fields.halfYear === 1 ? 1 : 3;
702
+ return CalendarMethodsTemporal.getCurrentQuarterFirstDate(toISO(Temporal.PlainDateTime.from({
703
+ year: fields.year,
704
+ month: (quarter - 1) * 3 + 1,
705
+ day: 1,
706
+ }).toZonedDateTime(tz)));
707
+ }
708
+ // Week format
709
+ if (hasWeek && (isoMode ? fields.isoWeekYear : fields.localeWeekYear)) {
710
+ const weekYear = (isoMode ? fields.isoWeekYear : fields.localeWeekYear);
711
+ const weekNum = isoMode ? fields.isoWeek : fields.localeWeek;
712
+ // Reject when the weekYear token was present but the week-number
713
+ // token was missing or unparsed. Otherwise a `gggg`-only or `GGGG`-only
714
+ // format would propagate `undefined` into Temporal's `add({days:NaN})`
715
+ // and throw RangeError instead of returning undefined per contract.
716
+ if (weekNum === undefined || weekNum < 1)
717
+ return undefined;
718
+ const { firstDay, minimalDays } = isoMode
719
+ ? { firstDay: 1, minimalDays: 4 }
720
+ : getWeekInfo(locale);
721
+ // Compute the actual max-week of `weekYear` instead of trusting the
722
+ // hard-coded ceiling of 53. Dec 28 always falls into the LAST week of
723
+ // its weekYear under ISO/CLDR rules, so its weekOfYear gives the max.
724
+ const maxWeeks = computeMaxWeeksOfYear(weekYear, firstDay, minimalDays, isoMode);
725
+ if (weekNum > maxWeeks)
726
+ return undefined;
727
+ // Anchor: week 1 contains Jan minimalDays (or Jan 4 for ISO).
728
+ const anchor = Temporal.PlainDate.from({
729
+ year: weekYear,
730
+ month: 1,
731
+ day: minimalDays,
732
+ });
733
+ const week1Start = startOfWeekDate(anchor, firstDay);
734
+ const target = week1Start.add({ days: (weekNum - 1) * 7 });
735
+ return toISO(target
736
+ .toPlainDateTime(Temporal.PlainTime.from('00:00:00'))
737
+ .toZonedDateTime(tz));
738
+ }
739
+ // Quarter format
740
+ if (hasQuarter && hasYear && !hasMonth && !hasDay) {
741
+ const quarter = fields.quarter;
742
+ if (quarter < 1 || quarter > 4)
743
+ return undefined;
744
+ return toISO(Temporal.PlainDateTime.from({
745
+ year: fields.year,
746
+ month: (quarter - 1) * 3 + 1,
747
+ day: 1,
748
+ }).toZonedDateTime(tz));
749
+ }
750
+ // Month-only — first of month.
751
+ if (hasYear && hasMonth && !hasDay && !hasWeek && !hasQuarter) {
752
+ const month = fields.month;
753
+ if (month < 1 || month > 12)
754
+ return undefined;
755
+ return toISO(Temporal.PlainDateTime.from({
756
+ year: fields.year,
757
+ month,
758
+ day: 1,
759
+ }).toZonedDateTime(tz));
760
+ }
761
+ // Year-only — first of year.
762
+ if (hasYear && !hasMonth && !hasDay && !hasWeek && !hasQuarter) {
763
+ return toISO(Temporal.PlainDateTime.from({
764
+ year: fields.year,
765
+ month: 1,
766
+ day: 1,
767
+ }).toZonedDateTime(tz));
768
+ }
769
+ // Full date (with optional time).
770
+ if (hasYear && hasMonth && hasDay) {
771
+ const month = fields.month;
772
+ const day = fields.day;
773
+ if (month < 1 || month > 12)
774
+ return undefined;
775
+ if (day < 1 || day > 31)
776
+ return undefined;
777
+ const hour = resolveHour(fields);
778
+ try {
779
+ return toISO(Temporal.PlainDateTime.from({
780
+ year: fields.year,
781
+ month,
782
+ day,
783
+ hour,
784
+ minute: (_a = fields.minute) !== null && _a !== void 0 ? _a : 0,
785
+ second: (_b = fields.second) !== null && _b !== void 0 ? _b : 0,
786
+ millisecond: (_c = fields.millisecond) !== null && _c !== void 0 ? _c : 0,
787
+ }, { overflow: 'reject' }).toZonedDateTime(tz));
788
+ }
789
+ catch (_h) {
790
+ return undefined;
791
+ }
792
+ }
793
+ // Time-only (HH:mm, HH:mm:ss, hh:mm A, …) — anchor to today's date.
794
+ // Used by TimePicker / TimeRangePicker, which round-trip values through
795
+ // `parseFormattedValue` with formats that contain no date tokens.
796
+ const hasTime = fields.hour !== undefined ||
797
+ fields.hour12 !== undefined ||
798
+ fields.minute !== undefined ||
799
+ fields.second !== undefined ||
800
+ fields.millisecond !== undefined;
801
+ if (hasTime &&
802
+ !hasYear &&
803
+ !hasMonth &&
804
+ !hasDay &&
805
+ !hasWeek &&
806
+ !hasQuarter &&
807
+ !hasHalfYear) {
808
+ const hour = resolveHour(fields);
809
+ const today = Temporal.Now.zonedDateTimeISO(tz);
810
+ try {
811
+ return toISO(today.with({
812
+ hour,
813
+ minute: (_d = fields.minute) !== null && _d !== void 0 ? _d : 0,
814
+ second: (_e = fields.second) !== null && _e !== void 0 ? _e : 0,
815
+ millisecond: (_f = fields.millisecond) !== null && _f !== void 0 ? _f : 0,
816
+ microsecond: 0,
817
+ nanosecond: 0,
818
+ }));
819
+ }
820
+ catch (_j) {
821
+ return undefined;
822
+ }
823
+ }
824
+ return undefined;
825
+ },
826
+ };
827
+ function resolveHour(fields) {
828
+ var _a, _b;
829
+ if (fields.hour12 !== undefined) {
830
+ const base = fields.hour12 % 12;
831
+ return ((_a = fields.meridiem) !== null && _a !== void 0 ? _a : 'am') === 'pm' ? base + 12 : base;
832
+ }
833
+ return (_b = fields.hour) !== null && _b !== void 0 ? _b : 0;
834
+ }
835
+ /**
836
+ * Compute the actual maximum week number of `weekYear` for the supplied
837
+ * locale week rule.
838
+ *
839
+ * For ISO 8601 (minimalDays=4), Dec 28 is guaranteed to fall in the last
840
+ * week of its own weekYear, so Temporal's native `weekOfYear` is correct.
841
+ *
842
+ * For other locale rules (e.g. minimalDays=1 in en-US), Dec 28 may already
843
+ * belong to the NEXT weekYear (en-US 2025-12-28 → 2026-W01). Instead we
844
+ * derive the last day of `weekYear` as `week1StartFor(weekYear + 1) - 1`,
845
+ * then count how many weeks elapse between `week1Start` of `weekYear` and
846
+ * that last day's week start.
847
+ */
848
+ function computeMaxWeeksOfYear(weekYear, firstDay, minimalDays, isoMode) {
849
+ var _a;
850
+ const Temporal = getTemporal();
851
+ if (isoMode) {
852
+ return ((_a = Temporal.PlainDate.from({
853
+ year: weekYear,
854
+ month: 12,
855
+ day: 28,
856
+ }).weekOfYear) !== null && _a !== void 0 ? _a : 52);
857
+ }
858
+ const week1Start = startOfWeekDate(Temporal.PlainDate.from({
859
+ year: weekYear,
860
+ month: 1,
861
+ day: minimalDays,
862
+ }), firstDay);
863
+ const nextYearWeek1Start = startOfWeekDate(Temporal.PlainDate.from({
864
+ year: weekYear + 1,
865
+ month: 1,
866
+ day: minimalDays,
867
+ }), firstDay);
868
+ // The last day belonging to `weekYear` sits one day before next year's
869
+ // week 1. Count the week-buckets between week 1 of `weekYear` and the
870
+ // bucket containing that last day.
871
+ const lastDayOfWeekYear = nextYearWeek1Start.subtract({ days: 1 });
872
+ const lastDayWeekStart = startOfWeekDate(lastDayOfWeekYear, firstDay);
873
+ const daysDiff = lastDayWeekStart.since(week1Start, {
874
+ largestUnit: 'days',
875
+ }).days;
876
+ return Math.floor(daysDiff / 7) + 1;
877
+ }
878
+ function validateFields(fields, tokens) {
879
+ if (fields.month !== undefined && (fields.month < 1 || fields.month > 12)) {
880
+ return false;
881
+ }
882
+ if (fields.day !== undefined && (fields.day < 1 || fields.day > 31)) {
883
+ return false;
884
+ }
885
+ if (fields.hour !== undefined && (fields.hour < 0 || fields.hour > 23)) {
886
+ return false;
887
+ }
888
+ if (fields.hour12 !== undefined &&
889
+ (fields.hour12 < 1 || fields.hour12 > 12)) {
890
+ return false;
891
+ }
892
+ if (fields.minute !== undefined &&
893
+ (fields.minute < 0 || fields.minute > 59)) {
894
+ return false;
895
+ }
896
+ if (fields.second !== undefined &&
897
+ (fields.second < 0 || fields.second > 59)) {
898
+ return false;
899
+ }
900
+ if (fields.quarter !== undefined &&
901
+ (fields.quarter < 1 || fields.quarter > 4)) {
902
+ return false;
903
+ }
904
+ if (fields.halfYear !== undefined &&
905
+ fields.halfYear !== 1 &&
906
+ fields.halfYear !== 2) {
907
+ return false;
908
+ }
909
+ return true;
910
+ }
911
+
912
+ export { CalendarMethodsTemporal as default };