@kalyx/core 0.2.2 → 0.4.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/dist/index.cjs +225 -43
- package/dist/index.d.cts +148 -3
- package/dist/index.d.ts +148 -3
- package/dist/index.js +207 -37
- package/package.json +5 -2
package/dist/index.cjs
CHANGED
|
@@ -20,8 +20,14 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/index.ts
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
|
+
DEFAULT_DATEPICKER_LABELS: () => DEFAULT_DATEPICKER_LABELS,
|
|
24
|
+
DEFAULT_DATETIMEPICKER_LABELS: () => DEFAULT_DATETIMEPICKER_LABELS,
|
|
25
|
+
DEFAULT_RANGEPICKER_LABELS: () => DEFAULT_RANGEPICKER_LABELS,
|
|
26
|
+
DEFAULT_TIMEPICKER_LABELS: () => DEFAULT_TIMEPICKER_LABELS,
|
|
23
27
|
DateFnsAdapter: () => DateFnsAdapter,
|
|
28
|
+
civilMidnightFromUtcDay: () => civilMidnightFromUtcDay,
|
|
24
29
|
formatFullDate: () => formatFullDate,
|
|
30
|
+
formatInTimezone: () => formatInTimezone,
|
|
25
31
|
formatMonthYear: () => formatMonthYear,
|
|
26
32
|
formatTimeFromISO: () => formatTimeFromISO,
|
|
27
33
|
formatTimeString: () => formatTimeString,
|
|
@@ -30,8 +36,11 @@ __export(index_exports, {
|
|
|
30
36
|
getCalendarDays: () => getCalendarDays,
|
|
31
37
|
getMonthName: () => getMonthName,
|
|
32
38
|
getTime: () => getTime,
|
|
39
|
+
getTimeInTimezone: () => getTimeInTimezone,
|
|
40
|
+
getTimezoneOffsetMinutes: () => getTimezoneOffsetMinutes,
|
|
33
41
|
getWeekdayNames: () => getWeekdayNames,
|
|
34
42
|
isDateDisabled: () => isDateDisabled,
|
|
43
|
+
isSameDayInTimezone: () => isSameDayInTimezone,
|
|
35
44
|
isSameTime: () => isSameTime,
|
|
36
45
|
maxDate: () => maxDate,
|
|
37
46
|
minDate: () => minDate,
|
|
@@ -39,15 +48,125 @@ __export(index_exports, {
|
|
|
39
48
|
parseInputValue: () => parseInputValue,
|
|
40
49
|
parseTimeString: () => parseTimeString,
|
|
41
50
|
setTime: () => setTime,
|
|
51
|
+
setTimeInTimezone: () => setTimeInTimezone,
|
|
52
|
+
startOfDayInTimezone: () => startOfDayInTimezone,
|
|
42
53
|
to12Hour: () => to12Hour,
|
|
43
|
-
to24Hour: () => to24Hour
|
|
54
|
+
to24Hour: () => to24Hour,
|
|
55
|
+
todayInTimezone: () => todayInTimezone
|
|
44
56
|
});
|
|
45
57
|
module.exports = __toCommonJS(index_exports);
|
|
46
58
|
|
|
47
59
|
// src/adapters/date-fns.ts
|
|
60
|
+
var import_date_fns2 = require("date-fns");
|
|
61
|
+
|
|
62
|
+
// src/utils/timezone.ts
|
|
48
63
|
var import_date_fns = require("date-fns");
|
|
64
|
+
var formatterCache = /* @__PURE__ */ new Map();
|
|
65
|
+
function getCachedPartsFormatter(timeZone) {
|
|
66
|
+
const key = `parts:${timeZone}`;
|
|
67
|
+
let fmt = formatterCache.get(key);
|
|
68
|
+
if (!fmt) {
|
|
69
|
+
fmt = new Intl.DateTimeFormat("en-US", {
|
|
70
|
+
timeZone,
|
|
71
|
+
year: "numeric",
|
|
72
|
+
month: "2-digit",
|
|
73
|
+
day: "2-digit",
|
|
74
|
+
hour: "2-digit",
|
|
75
|
+
minute: "2-digit",
|
|
76
|
+
second: "2-digit",
|
|
77
|
+
hourCycle: "h23"
|
|
78
|
+
});
|
|
79
|
+
formatterCache.set(key, fmt);
|
|
80
|
+
}
|
|
81
|
+
return fmt;
|
|
82
|
+
}
|
|
83
|
+
function partsInTimezone(utc, timeZone) {
|
|
84
|
+
const dtf = getCachedPartsFormatter(timeZone);
|
|
85
|
+
const parts = Object.fromEntries(
|
|
86
|
+
dtf.formatToParts(utc).map((p) => [p.type, p.value])
|
|
87
|
+
);
|
|
88
|
+
return {
|
|
89
|
+
year: Number(parts.year),
|
|
90
|
+
month: Number(parts.month),
|
|
91
|
+
day: Number(parts.day),
|
|
92
|
+
// Some locales/engines return '24' instead of '0' at midnight
|
|
93
|
+
hour: parts.hour === "24" ? 0 : Number(parts.hour),
|
|
94
|
+
minute: Number(parts.minute),
|
|
95
|
+
second: Number(parts.second)
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
function formatInTimezone(iso, formatStr, timeZone) {
|
|
99
|
+
const p = partsInTimezone((0, import_date_fns.parseISO)(iso), timeZone);
|
|
100
|
+
const tokens = {
|
|
101
|
+
yyyy: String(p.year),
|
|
102
|
+
MM: String(p.month).padStart(2, "0"),
|
|
103
|
+
dd: String(p.day).padStart(2, "0"),
|
|
104
|
+
HH: String(p.hour).padStart(2, "0"),
|
|
105
|
+
mm: String(p.minute).padStart(2, "0"),
|
|
106
|
+
ss: String(p.second).padStart(2, "0")
|
|
107
|
+
};
|
|
108
|
+
let result = formatStr;
|
|
109
|
+
for (const [token, value] of Object.entries(tokens).sort((a, b) => b[0].length - a[0].length)) {
|
|
110
|
+
result = result.split(token).join(value);
|
|
111
|
+
}
|
|
112
|
+
return result;
|
|
113
|
+
}
|
|
114
|
+
function getTimezoneOffsetMinutes(iso, timeZone) {
|
|
115
|
+
const utc = (0, import_date_fns.parseISO)(iso);
|
|
116
|
+
const p = partsInTimezone(utc, timeZone);
|
|
117
|
+
const asUtcEpoch = Date.UTC(p.year, p.month - 1, p.day, p.hour, p.minute, p.second);
|
|
118
|
+
return Math.round((asUtcEpoch - utc.getTime()) / 6e4);
|
|
119
|
+
}
|
|
120
|
+
function startOfDayInTimezone(iso, timeZone) {
|
|
121
|
+
const utc = (0, import_date_fns.parseISO)(iso);
|
|
122
|
+
const p = partsInTimezone(utc, timeZone);
|
|
123
|
+
const civilMidnightUtc = Date.UTC(p.year, p.month - 1, p.day, 0, 0, 0);
|
|
124
|
+
const midnightProbe = new Date(civilMidnightUtc).toISOString();
|
|
125
|
+
const offsetMinutes = getTimezoneOffsetMinutes(midnightProbe, timeZone);
|
|
126
|
+
return new Date(civilMidnightUtc - offsetMinutes * 6e4).toISOString();
|
|
127
|
+
}
|
|
128
|
+
function isSameDayInTimezone(a, b, timeZone) {
|
|
129
|
+
const pa = partsInTimezone((0, import_date_fns.parseISO)(a), timeZone);
|
|
130
|
+
const pb = partsInTimezone((0, import_date_fns.parseISO)(b), timeZone);
|
|
131
|
+
return pa.year === pb.year && pa.month === pb.month && pa.day === pb.day;
|
|
132
|
+
}
|
|
133
|
+
function todayInTimezone(timeZone) {
|
|
134
|
+
return startOfDayInTimezone((/* @__PURE__ */ new Date()).toISOString(), timeZone);
|
|
135
|
+
}
|
|
136
|
+
function civilMidnightFromUtcDay(gridUtcIso, timeZone) {
|
|
137
|
+
const utc = (0, import_date_fns.parseISO)(gridUtcIso);
|
|
138
|
+
const probe = new Date(Date.UTC(
|
|
139
|
+
utc.getUTCFullYear(),
|
|
140
|
+
utc.getUTCMonth(),
|
|
141
|
+
utc.getUTCDate(),
|
|
142
|
+
12,
|
|
143
|
+
0,
|
|
144
|
+
0
|
|
145
|
+
)).toISOString();
|
|
146
|
+
return startOfDayInTimezone(probe, timeZone);
|
|
147
|
+
}
|
|
148
|
+
function getTimeInTimezone(iso, timeZone) {
|
|
149
|
+
const p = partsInTimezone((0, import_date_fns.parseISO)(iso), timeZone);
|
|
150
|
+
return { hours: p.hour, minutes: p.minute, seconds: p.second };
|
|
151
|
+
}
|
|
152
|
+
function setTimeInTimezone(iso, partial, timeZone) {
|
|
153
|
+
const p = partsInTimezone((0, import_date_fns.parseISO)(iso), timeZone);
|
|
154
|
+
const targetHours = partial.hours ?? p.hour;
|
|
155
|
+
const targetMinutes = partial.minutes ?? p.minute;
|
|
156
|
+
const targetSeconds = partial.seconds ?? p.second;
|
|
157
|
+
const civilEpoch = Date.UTC(p.year, p.month - 1, p.day, targetHours, targetMinutes, targetSeconds);
|
|
158
|
+
const probe1 = new Date(civilEpoch).toISOString();
|
|
159
|
+
const offset1 = getTimezoneOffsetMinutes(probe1, timeZone);
|
|
160
|
+
const realEpoch1 = civilEpoch - offset1 * 6e4;
|
|
161
|
+
const probe2 = new Date(realEpoch1).toISOString();
|
|
162
|
+
const offset2 = getTimezoneOffsetMinutes(probe2, timeZone);
|
|
163
|
+
const realEpoch2 = civilEpoch - offset2 * 6e4;
|
|
164
|
+
return new Date(realEpoch2).toISOString();
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// src/adapters/date-fns.ts
|
|
49
168
|
function toDate(iso) {
|
|
50
|
-
return (0,
|
|
169
|
+
return (0, import_date_fns2.parseISO)(iso);
|
|
51
170
|
}
|
|
52
171
|
function toISO(date) {
|
|
53
172
|
return date.toISOString();
|
|
@@ -89,7 +208,10 @@ var DateFnsAdapter = {
|
|
|
89
208
|
if (!value) return "";
|
|
90
209
|
return normalize(value);
|
|
91
210
|
},
|
|
92
|
-
format(iso, formatStr) {
|
|
211
|
+
format(iso, formatStr, timezone) {
|
|
212
|
+
if (timezone) {
|
|
213
|
+
return formatInTimezone(iso, formatStr, timezone);
|
|
214
|
+
}
|
|
93
215
|
const d = toDate(iso);
|
|
94
216
|
const tokens = {
|
|
95
217
|
yyyy: String(d.getUTCFullYear()),
|
|
@@ -108,22 +230,25 @@ var DateFnsAdapter = {
|
|
|
108
230
|
return result;
|
|
109
231
|
},
|
|
110
232
|
addDays(iso, n) {
|
|
111
|
-
return toISO((0,
|
|
233
|
+
return toISO((0, import_date_fns2.addDays)(toDate(iso), n));
|
|
112
234
|
},
|
|
113
235
|
addMonths(iso, n) {
|
|
114
|
-
return toISO((0,
|
|
236
|
+
return toISO((0, import_date_fns2.addMonths)(toDate(iso), n));
|
|
115
237
|
},
|
|
116
238
|
addYears(iso, n) {
|
|
117
|
-
return toISO((0,
|
|
239
|
+
return toISO((0, import_date_fns2.addYears)(toDate(iso), n));
|
|
118
240
|
},
|
|
119
241
|
isBefore(a, b) {
|
|
120
|
-
return (0,
|
|
242
|
+
return (0, import_date_fns2.isBefore)(toDate(a), toDate(b));
|
|
121
243
|
},
|
|
122
244
|
isAfter(a, b) {
|
|
123
|
-
return (0,
|
|
245
|
+
return (0, import_date_fns2.isAfter)(toDate(a), toDate(b));
|
|
124
246
|
},
|
|
125
|
-
isSameDay(a, b) {
|
|
247
|
+
isSameDay(a, b, timezone) {
|
|
126
248
|
if (!a || !b) return false;
|
|
249
|
+
if (timezone) {
|
|
250
|
+
return isSameDayInTimezone(a, b, timezone);
|
|
251
|
+
}
|
|
127
252
|
const da = toDate(a);
|
|
128
253
|
const db = toDate(b);
|
|
129
254
|
return da.getUTCFullYear() === db.getUTCFullYear() && da.getUTCMonth() === db.getUTCMonth() && da.getUTCDate() === db.getUTCDate();
|
|
@@ -133,7 +258,10 @@ var DateFnsAdapter = {
|
|
|
133
258
|
const db = toDate(b);
|
|
134
259
|
return da.getUTCFullYear() === db.getUTCFullYear() && da.getUTCMonth() === db.getUTCMonth();
|
|
135
260
|
},
|
|
136
|
-
startOfDay(iso) {
|
|
261
|
+
startOfDay(iso, timezone) {
|
|
262
|
+
if (timezone) {
|
|
263
|
+
return startOfDayInTimezone(iso, timezone);
|
|
264
|
+
}
|
|
137
265
|
return toISO(utcStartOfDay(toDate(iso)));
|
|
138
266
|
},
|
|
139
267
|
startOfMonth(iso) {
|
|
@@ -151,14 +279,17 @@ var DateFnsAdapter = {
|
|
|
151
279
|
now() {
|
|
152
280
|
return (/* @__PURE__ */ new Date()).toISOString();
|
|
153
281
|
},
|
|
154
|
-
today() {
|
|
282
|
+
today(timezone) {
|
|
283
|
+
if (timezone) {
|
|
284
|
+
return todayInTimezone(timezone);
|
|
285
|
+
}
|
|
155
286
|
return toISO(utcStartOfDay(/* @__PURE__ */ new Date()));
|
|
156
287
|
},
|
|
157
288
|
isValid(value) {
|
|
158
289
|
if (!value) return false;
|
|
159
290
|
const normalized = normalize(value);
|
|
160
|
-
const date = (0,
|
|
161
|
-
return (0,
|
|
291
|
+
const date = (0, import_date_fns2.parseISO)(normalized);
|
|
292
|
+
return (0, import_date_fns2.isValid)(date);
|
|
162
293
|
},
|
|
163
294
|
getYear(iso) {
|
|
164
295
|
return toDate(iso).getUTCFullYear();
|
|
@@ -183,23 +314,24 @@ function getCalendarDays(monthISO, adapter, options = {}) {
|
|
|
183
314
|
focusedDate,
|
|
184
315
|
disabled = [],
|
|
185
316
|
range,
|
|
186
|
-
rangeHover
|
|
317
|
+
rangeHover,
|
|
318
|
+
timezone
|
|
187
319
|
} = options;
|
|
188
|
-
const todayISO = today ?? adapter.today();
|
|
320
|
+
const todayISO = today ?? adapter.today(timezone);
|
|
189
321
|
const monthStart = adapter.startOfMonth(monthISO);
|
|
190
322
|
const gridStart = adapter.startOfWeek(monthStart, weekStartsOn);
|
|
191
|
-
const normalizedRange = normalizeRangeForDisplay(range, rangeHover, adapter);
|
|
323
|
+
const normalizedRange = normalizeRangeForDisplay(range, rangeHover, adapter, timezone);
|
|
192
324
|
const weeks = [];
|
|
193
325
|
let current = gridStart;
|
|
194
326
|
for (let week = 0; week < 6; week++) {
|
|
195
327
|
const days = [];
|
|
196
328
|
for (let day = 0; day < 7; day++) {
|
|
197
329
|
const isCurrentMonth = adapter.isSameMonth(current, monthISO);
|
|
198
|
-
const isTodayDate = adapter.isSameDay(current, todayISO);
|
|
199
|
-
const isSelected_ = selected ? adapter.isSameDay(current, selected) : false;
|
|
200
|
-
const isFocused_ = focusedDate ? adapter.isSameDay(current, focusedDate) : false;
|
|
330
|
+
const isTodayDate = adapter.isSameDay(current, todayISO, timezone);
|
|
331
|
+
const isSelected_ = selected ? adapter.isSameDay(current, selected, timezone) : false;
|
|
332
|
+
const isFocused_ = focusedDate ? adapter.isSameDay(current, focusedDate, timezone) : false;
|
|
201
333
|
const isDisabled_ = isDateDisabled(current, disabled, adapter);
|
|
202
|
-
const rangeFlags = computeRangeFlags(current, normalizedRange, adapter);
|
|
334
|
+
const rangeFlags = computeRangeFlags(current, normalizedRange, adapter, timezone);
|
|
203
335
|
days.push({
|
|
204
336
|
isoString: current,
|
|
205
337
|
dayNumber: adapter.getDate(current),
|
|
@@ -219,7 +351,7 @@ function getCalendarDays(monthISO, adapter, options = {}) {
|
|
|
219
351
|
}
|
|
220
352
|
return weeks;
|
|
221
353
|
}
|
|
222
|
-
function normalizeRangeForDisplay(range, hover, adapter) {
|
|
354
|
+
function normalizeRangeForDisplay(range, hover, adapter, _timezone) {
|
|
223
355
|
if (!range) return { start: null, end: null };
|
|
224
356
|
const { start, end } = range;
|
|
225
357
|
if (start && end) {
|
|
@@ -239,16 +371,16 @@ function normalizeRangeForDisplay(range, hover, adapter) {
|
|
|
239
371
|
}
|
|
240
372
|
return { start: null, end: null };
|
|
241
373
|
}
|
|
242
|
-
function computeRangeFlags(iso, range, adapter) {
|
|
374
|
+
function computeRangeFlags(iso, range, adapter, timezone) {
|
|
243
375
|
const { start, end } = range;
|
|
244
376
|
if (!start) {
|
|
245
377
|
return { isRangeStart: false, isRangeEnd: false, isInRange: false };
|
|
246
378
|
}
|
|
247
|
-
const isRangeStart = adapter.isSameDay(iso, start);
|
|
379
|
+
const isRangeStart = adapter.isSameDay(iso, start, timezone);
|
|
248
380
|
if (!end) {
|
|
249
381
|
return { isRangeStart, isRangeEnd: false, isInRange: false };
|
|
250
382
|
}
|
|
251
|
-
const isRangeEnd = adapter.isSameDay(iso, end);
|
|
383
|
+
const isRangeEnd = adapter.isSameDay(iso, end, timezone);
|
|
252
384
|
const isInRange = !isRangeStart && !isRangeEnd && adapter.isAfter(iso, start) && adapter.isBefore(iso, end);
|
|
253
385
|
return { isRangeStart, isRangeEnd, isInRange };
|
|
254
386
|
}
|
|
@@ -275,14 +407,14 @@ function maxDate(a, b, adapter) {
|
|
|
275
407
|
|
|
276
408
|
// src/utils/date.ts
|
|
277
409
|
var ISO_DATE_REGEX = /^\d{4}-\d{2}-\d{2}$/;
|
|
278
|
-
var ISO_DATETIME_REGEX = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}
|
|
410
|
+
var ISO_DATETIME_REGEX = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2})$/;
|
|
279
411
|
function normalizeISO(value) {
|
|
280
412
|
if (!value) return "";
|
|
281
413
|
if (ISO_DATETIME_REGEX.test(value)) return value;
|
|
282
414
|
if (ISO_DATE_REGEX.test(value)) return `${value}T00:00:00.000Z`;
|
|
283
415
|
return value;
|
|
284
416
|
}
|
|
285
|
-
function parseInputValue(input,
|
|
417
|
+
function parseInputValue(input, adapter) {
|
|
286
418
|
if (!input.trim()) return null;
|
|
287
419
|
const cleaned = input.replace(/\//g, "-").trim();
|
|
288
420
|
if (ISO_DATE_REGEX.test(cleaned)) {
|
|
@@ -357,8 +489,8 @@ function generateHours(format = "24h") {
|
|
|
357
489
|
return Array.from({ length: 24 }, (_, i) => i);
|
|
358
490
|
}
|
|
359
491
|
function generateMinutes(step = 1) {
|
|
360
|
-
if (step < 1 || step >
|
|
361
|
-
throw new Error(`[generateMinutes] step must be between 1 and
|
|
492
|
+
if (step < 1 || step > 30) {
|
|
493
|
+
throw new Error(`[generateMinutes] step must be between 1 and 30, got ${step}`);
|
|
362
494
|
}
|
|
363
495
|
const result = [];
|
|
364
496
|
for (let i = 0; i < 60; i += step) {
|
|
@@ -369,7 +501,7 @@ function generateMinutes(step = 1) {
|
|
|
369
501
|
function isSameTime(a, b) {
|
|
370
502
|
return a.hours === b.hours && a.minutes === b.minutes && a.seconds === b.seconds;
|
|
371
503
|
}
|
|
372
|
-
function formatTimeFromISO(iso, format
|
|
504
|
+
function formatTimeFromISO(iso, format) {
|
|
373
505
|
const time = getTime(iso);
|
|
374
506
|
if (format === "h:mm a" || format === "h:mm:ss a") {
|
|
375
507
|
const { hours12, period } = to12Hour(time.hours);
|
|
@@ -384,30 +516,35 @@ function formatTimeFromISO(iso, format, _adapter) {
|
|
|
384
516
|
}
|
|
385
517
|
|
|
386
518
|
// src/utils/locale.ts
|
|
519
|
+
var formatterCache2 = /* @__PURE__ */ new Map();
|
|
520
|
+
function getCachedFormatter(locale, options) {
|
|
521
|
+
const key = `${locale}:${JSON.stringify(options)}`;
|
|
522
|
+
let fmt = formatterCache2.get(key);
|
|
523
|
+
if (!fmt) {
|
|
524
|
+
fmt = new Intl.DateTimeFormat(locale, options);
|
|
525
|
+
formatterCache2.set(key, fmt);
|
|
526
|
+
}
|
|
527
|
+
return fmt;
|
|
528
|
+
}
|
|
529
|
+
var REFERENCE_YEAR = 2026;
|
|
387
530
|
function getMonthName(month, locale = "en-US") {
|
|
388
|
-
const date = new Date(Date.UTC(
|
|
389
|
-
return
|
|
531
|
+
const date = new Date(Date.UTC(REFERENCE_YEAR, month, 1));
|
|
532
|
+
return getCachedFormatter(locale, { month: "long", timeZone: "UTC" }).format(date);
|
|
390
533
|
}
|
|
391
534
|
function formatMonthYear(year, month, locale = "en-US") {
|
|
392
535
|
const date = new Date(Date.UTC(year, month, 1));
|
|
393
|
-
return
|
|
536
|
+
return getCachedFormatter(locale, {
|
|
394
537
|
year: "numeric",
|
|
395
538
|
month: "long",
|
|
396
539
|
timeZone: "UTC"
|
|
397
540
|
}).format(date);
|
|
398
541
|
}
|
|
399
542
|
function getWeekdayNames(locale = "en-US", weekStartsOn = 0) {
|
|
400
|
-
const shortFormatter =
|
|
401
|
-
|
|
402
|
-
timeZone: "UTC"
|
|
403
|
-
});
|
|
404
|
-
const fullFormatter = new Intl.DateTimeFormat(locale, {
|
|
405
|
-
weekday: "long",
|
|
406
|
-
timeZone: "UTC"
|
|
407
|
-
});
|
|
543
|
+
const shortFormatter = getCachedFormatter(locale, { weekday: "short", timeZone: "UTC" });
|
|
544
|
+
const fullFormatter = getCachedFormatter(locale, { weekday: "long", timeZone: "UTC" });
|
|
408
545
|
const days = [];
|
|
409
546
|
for (let i = 0; i < 7; i++) {
|
|
410
|
-
const date = new Date(Date.UTC(
|
|
547
|
+
const date = new Date(Date.UTC(REFERENCE_YEAR, 0, 4 + i));
|
|
411
548
|
days.push({
|
|
412
549
|
short: shortFormatter.format(date),
|
|
413
550
|
full: fullFormatter.format(date)
|
|
@@ -421,7 +558,7 @@ function getWeekdayNames(locale = "en-US", weekStartsOn = 0) {
|
|
|
421
558
|
}
|
|
422
559
|
function formatFullDate(iso, locale = "en-US") {
|
|
423
560
|
const date = new Date(iso);
|
|
424
|
-
return
|
|
561
|
+
return getCachedFormatter(locale, {
|
|
425
562
|
year: "numeric",
|
|
426
563
|
month: "long",
|
|
427
564
|
day: "numeric",
|
|
@@ -429,10 +566,49 @@ function formatFullDate(iso, locale = "en-US") {
|
|
|
429
566
|
timeZone: "UTC"
|
|
430
567
|
}).format(date);
|
|
431
568
|
}
|
|
569
|
+
|
|
570
|
+
// src/utils/labels.ts
|
|
571
|
+
var DEFAULT_DATEPICKER_LABELS = {
|
|
572
|
+
triggerOpen: "Open calendar",
|
|
573
|
+
triggerClose: "Close calendar",
|
|
574
|
+
popoverLabel: "Choose date",
|
|
575
|
+
prevMonth: "Previous month",
|
|
576
|
+
nextMonth: "Next month",
|
|
577
|
+
prevYear: "Previous year",
|
|
578
|
+
nextYear: "Next year",
|
|
579
|
+
prevDecade: "Previous decade",
|
|
580
|
+
nextDecade: "Next decade"
|
|
581
|
+
};
|
|
582
|
+
var DEFAULT_RANGEPICKER_LABELS = {
|
|
583
|
+
...DEFAULT_DATEPICKER_LABELS,
|
|
584
|
+
popoverLabel: "Choose date range",
|
|
585
|
+
startInput: "Start date",
|
|
586
|
+
endInput: "End date",
|
|
587
|
+
presetsGroup: "Date range presets"
|
|
588
|
+
};
|
|
589
|
+
var DEFAULT_TIMEPICKER_LABELS = {
|
|
590
|
+
timeInput: "Time",
|
|
591
|
+
hourList: "Hour",
|
|
592
|
+
minuteList: "Minute",
|
|
593
|
+
amPmToggle: "AM/PM",
|
|
594
|
+
hourOption: (hour) => `${hour} hours`,
|
|
595
|
+
minuteOption: (minute) => `${minute} minutes`
|
|
596
|
+
};
|
|
597
|
+
var DEFAULT_DATETIMEPICKER_LABELS = {
|
|
598
|
+
...DEFAULT_DATEPICKER_LABELS,
|
|
599
|
+
...DEFAULT_TIMEPICKER_LABELS,
|
|
600
|
+
dateTimeInput: "Date and time"
|
|
601
|
+
};
|
|
432
602
|
// Annotate the CommonJS export names for ESM import in node:
|
|
433
603
|
0 && (module.exports = {
|
|
604
|
+
DEFAULT_DATEPICKER_LABELS,
|
|
605
|
+
DEFAULT_DATETIMEPICKER_LABELS,
|
|
606
|
+
DEFAULT_RANGEPICKER_LABELS,
|
|
607
|
+
DEFAULT_TIMEPICKER_LABELS,
|
|
434
608
|
DateFnsAdapter,
|
|
609
|
+
civilMidnightFromUtcDay,
|
|
435
610
|
formatFullDate,
|
|
611
|
+
formatInTimezone,
|
|
436
612
|
formatMonthYear,
|
|
437
613
|
formatTimeFromISO,
|
|
438
614
|
formatTimeString,
|
|
@@ -441,8 +617,11 @@ function formatFullDate(iso, locale = "en-US") {
|
|
|
441
617
|
getCalendarDays,
|
|
442
618
|
getMonthName,
|
|
443
619
|
getTime,
|
|
620
|
+
getTimeInTimezone,
|
|
621
|
+
getTimezoneOffsetMinutes,
|
|
444
622
|
getWeekdayNames,
|
|
445
623
|
isDateDisabled,
|
|
624
|
+
isSameDayInTimezone,
|
|
446
625
|
isSameTime,
|
|
447
626
|
maxDate,
|
|
448
627
|
minDate,
|
|
@@ -450,6 +629,9 @@ function formatFullDate(iso, locale = "en-US") {
|
|
|
450
629
|
parseInputValue,
|
|
451
630
|
parseTimeString,
|
|
452
631
|
setTime,
|
|
632
|
+
setTimeInTimezone,
|
|
633
|
+
startOfDayInTimezone,
|
|
453
634
|
to12Hour,
|
|
454
|
-
to24Hour
|
|
635
|
+
to24Hour,
|
|
636
|
+
todayInTimezone
|
|
455
637
|
});
|
package/dist/index.d.cts
CHANGED
|
@@ -102,6 +102,12 @@ interface CalendarOptions {
|
|
|
102
102
|
range?: DateRange | null;
|
|
103
103
|
/** Currently hovered date (for RangePicker preview) */
|
|
104
104
|
rangeHover?: ISODateString | null;
|
|
105
|
+
/**
|
|
106
|
+
* IANA timezone. When set, `isSameDay` comparisons for today/selected/range flags are
|
|
107
|
+
* evaluated in this zone — required when the picker stores values in display-tz civil
|
|
108
|
+
* midnight form while the grid iterates in UTC.
|
|
109
|
+
*/
|
|
110
|
+
timezone?: string;
|
|
105
111
|
}
|
|
106
112
|
|
|
107
113
|
/**
|
|
@@ -156,13 +162,16 @@ declare function maxDate(a: ISODateString, b: ISODateString, adapter: DateAdapte
|
|
|
156
162
|
/**
|
|
157
163
|
* Normalizes a date string to ISO 8601 UTC form.
|
|
158
164
|
* "2026-01-15" → "2026-01-15T00:00:00.000Z"
|
|
165
|
+
*
|
|
166
|
+
* Full datetime strings must include a timezone suffix (Z or ±HH:MM).
|
|
167
|
+
* Strings without a timezone suffix are treated as-is (not matched as datetime).
|
|
159
168
|
*/
|
|
160
169
|
declare function normalizeISO(value: string): string;
|
|
161
170
|
/**
|
|
162
171
|
* Parses user input text into an ISO string.
|
|
163
172
|
* Returns null on failure.
|
|
164
173
|
*/
|
|
165
|
-
declare function parseInputValue(input: string,
|
|
174
|
+
declare function parseInputValue(input: string, adapter: DateAdapter): string | null;
|
|
166
175
|
|
|
167
176
|
/** Time-of-day value (24-hour clock) */
|
|
168
177
|
interface TimeValue {
|
|
@@ -219,7 +228,7 @@ declare function isSameTime(a: TimeValue, b: TimeValue): boolean;
|
|
|
219
228
|
* Formats an ISO datetime as a time string (UTC based).
|
|
220
229
|
* Accepts an adapter for API consistency.
|
|
221
230
|
*/
|
|
222
|
-
declare function formatTimeFromISO(iso: ISODateString, format: 'HH:mm' | 'HH:mm:ss' | 'h:mm a' | 'h:mm:ss a'
|
|
231
|
+
declare function formatTimeFromISO(iso: ISODateString, format: 'HH:mm' | 'HH:mm:ss' | 'h:mm a' | 'h:mm:ss a'): string;
|
|
223
232
|
|
|
224
233
|
interface WeekdayInfo {
|
|
225
234
|
/** Short name (e.g. "Su", "일") */
|
|
@@ -252,4 +261,140 @@ declare function getWeekdayNames(locale?: string, weekStartsOn?: WeekStartsOn):
|
|
|
252
261
|
*/
|
|
253
262
|
declare function formatFullDate(iso: string, locale?: string): string;
|
|
254
263
|
|
|
255
|
-
|
|
264
|
+
/**
|
|
265
|
+
* Format a UTC ISO string for display in a specific IANA timezone.
|
|
266
|
+
* Handles DST transitions correctly. Supports a small set of tokens: `yyyy MM dd HH mm ss`.
|
|
267
|
+
*
|
|
268
|
+
* @param iso - UTC ISO 8601 string
|
|
269
|
+
* @param formatStr - token string (e.g. `"yyyy-MM-dd HH:mm"`)
|
|
270
|
+
* @param timeZone - IANA zone (e.g. `"America/New_York"`)
|
|
271
|
+
*
|
|
272
|
+
* @example
|
|
273
|
+
* formatInTimezone('2026-03-08T07:30:00.000Z', 'yyyy-MM-dd HH:mm', 'America/New_York');
|
|
274
|
+
* // → '2026-03-08 03:30' (post spring-forward EDT)
|
|
275
|
+
*/
|
|
276
|
+
declare function formatInTimezone(iso: ISODateString, formatStr: string, timeZone: string): string;
|
|
277
|
+
/**
|
|
278
|
+
* UTC offset (minutes east of UTC) at a given UTC instant, as applied by the given timezone.
|
|
279
|
+
* The offset may differ on either side of a DST transition.
|
|
280
|
+
*
|
|
281
|
+
* @example
|
|
282
|
+
* getTimezoneOffsetMinutes('2026-03-08T06:00:00.000Z', 'America/New_York'); // -300 (EST, UTC-5)
|
|
283
|
+
* getTimezoneOffsetMinutes('2026-03-08T07:00:00.000Z', 'America/New_York'); // -240 (EDT, UTC-4)
|
|
284
|
+
* getTimezoneOffsetMinutes('2026-01-15T12:00:00.000Z', 'Asia/Seoul'); // 540 (UTC+9)
|
|
285
|
+
*/
|
|
286
|
+
declare function getTimezoneOffsetMinutes(iso: ISODateString, timeZone: string): number;
|
|
287
|
+
/**
|
|
288
|
+
* Midnight of the civil date (as observed in `timeZone`) returned as a UTC ISO string.
|
|
289
|
+
*
|
|
290
|
+
* Across DST transitions this is the correct way to compute "start of day" — the offset
|
|
291
|
+
* changes, so the UTC instant of midnight differs before and after the transition.
|
|
292
|
+
*
|
|
293
|
+
* @example
|
|
294
|
+
* startOfDayInTimezone('2026-03-08T12:00:00.000Z', 'America/New_York'); // '2026-03-08T05:00:00.000Z' (EST)
|
|
295
|
+
* startOfDayInTimezone('2026-03-09T12:00:00.000Z', 'America/New_York'); // '2026-03-09T04:00:00.000Z' (EDT)
|
|
296
|
+
*/
|
|
297
|
+
declare function startOfDayInTimezone(iso: ISODateString, timeZone: string): ISODateString;
|
|
298
|
+
/**
|
|
299
|
+
* Whether two UTC instants fall on the same civil day in the given timezone.
|
|
300
|
+
* Timezone-safe alternative to comparing `iso.slice(0, 10)`.
|
|
301
|
+
*
|
|
302
|
+
* @example
|
|
303
|
+
* // Seoul is UTC+9, so 03:00 UTC and 14:00 UTC are both on 2026-01-15 KST
|
|
304
|
+
* isSameDayInTimezone('2026-01-15T03:00:00.000Z', '2026-01-15T14:00:00.000Z', 'Asia/Seoul'); // true
|
|
305
|
+
* // But 17:00 UTC is 02:00 KST on 2026-01-16
|
|
306
|
+
* isSameDayInTimezone('2026-01-15T03:00:00.000Z', '2026-01-15T17:00:00.000Z', 'Asia/Seoul'); // false
|
|
307
|
+
*/
|
|
308
|
+
declare function isSameDayInTimezone(a: ISODateString, b: ISODateString, timeZone: string): boolean;
|
|
309
|
+
/**
|
|
310
|
+
* "Today" in the given timezone, returned as the UTC ISO string representing that day's midnight.
|
|
311
|
+
* Prefer this over `new Date()` when the notion of "today" should follow the user's displayed
|
|
312
|
+
* timezone rather than the server's local clock.
|
|
313
|
+
*/
|
|
314
|
+
declare function todayInTimezone(timeZone: string): ISODateString;
|
|
315
|
+
/**
|
|
316
|
+
* Converts a UTC-midnight ISO (as produced by the default calendar grid iteration) into the
|
|
317
|
+
* civil midnight of the same calendar day in `timeZone`.
|
|
318
|
+
*
|
|
319
|
+
* This is the bridge used by DatePicker/RangePicker when `displayTimezone` is set: the grid
|
|
320
|
+
* iterates in UTC, so a cell's `isoString` is `YYYY-MM-DDT00:00:00.000Z`. When the user clicks
|
|
321
|
+
* that cell we want to emit an ISO representing the same civil day's midnight in the display
|
|
322
|
+
* timezone. A probe at noon UTC is used so the target civil day is unambiguous across any zone.
|
|
323
|
+
*
|
|
324
|
+
* @example
|
|
325
|
+
* civilMidnightFromUtcDay('2026-01-15T00:00:00.000Z', 'Asia/Seoul');
|
|
326
|
+
* // → '2026-01-14T15:00:00.000Z' (Seoul Jan 15 00:00 = UTC Jan 14 15:00)
|
|
327
|
+
*/
|
|
328
|
+
declare function civilMidnightFromUtcDay(gridUtcIso: ISODateString, timeZone: string): ISODateString;
|
|
329
|
+
/**
|
|
330
|
+
* Extracts the time-of-day (hours / minutes / seconds) of a UTC instant as observed in
|
|
331
|
+
* `timeZone`. The result differs from reading UTC hours when the zone has a non-zero offset.
|
|
332
|
+
*
|
|
333
|
+
* @example
|
|
334
|
+
* getTimeInTimezone('2026-01-15T00:00:00.000Z', 'Asia/Seoul');
|
|
335
|
+
* // → { hours: 9, minutes: 0, seconds: 0 } (Seoul is UTC+9)
|
|
336
|
+
*/
|
|
337
|
+
declare function getTimeInTimezone(iso: ISODateString, timeZone: string): {
|
|
338
|
+
hours: number;
|
|
339
|
+
minutes: number;
|
|
340
|
+
seconds: number;
|
|
341
|
+
};
|
|
342
|
+
/**
|
|
343
|
+
* Returns a new ISO UTC string where the civil date in `timeZone` is preserved and the time
|
|
344
|
+
* portion is replaced according to `partial` (as observed in that same timezone). Undefined
|
|
345
|
+
* fields keep their current value.
|
|
346
|
+
*
|
|
347
|
+
* Implementation note: the civil target (Y,M,D,H,m,s) is first mapped to a UTC epoch as if
|
|
348
|
+
* the wall-clock reading lived in UTC; then we subtract the timezone offset at that instant
|
|
349
|
+
* to recover the real UTC instant. The offset is refined once to absorb DST transitions.
|
|
350
|
+
*
|
|
351
|
+
* @example
|
|
352
|
+
* // In Asia/Seoul (UTC+9): set the hour to 10
|
|
353
|
+
* setTimeInTimezone('2026-01-15T00:00:00.000Z', { hours: 10 }, 'Asia/Seoul');
|
|
354
|
+
* // → '2026-01-15T01:00:00.000Z' (Seoul Jan 15 10:00 = UTC 01:00)
|
|
355
|
+
*/
|
|
356
|
+
declare function setTimeInTimezone(iso: ISODateString, partial: {
|
|
357
|
+
hours?: number;
|
|
358
|
+
minutes?: number;
|
|
359
|
+
seconds?: number;
|
|
360
|
+
}, timeZone: string): ISODateString;
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* Default English labels for ARIA attributes and accessible text.
|
|
364
|
+
* Override via the `labels` prop on Root components.
|
|
365
|
+
*/
|
|
366
|
+
interface DatePickerLabels {
|
|
367
|
+
triggerOpen: string;
|
|
368
|
+
triggerClose: string;
|
|
369
|
+
popoverLabel: string;
|
|
370
|
+
prevMonth: string;
|
|
371
|
+
nextMonth: string;
|
|
372
|
+
prevYear: string;
|
|
373
|
+
nextYear: string;
|
|
374
|
+
prevDecade: string;
|
|
375
|
+
nextDecade: string;
|
|
376
|
+
/** Used by DateTimePicker.Input (present only when DatePickerContext is provided by DateTimePickerRoot) */
|
|
377
|
+
dateTimeInput?: string;
|
|
378
|
+
}
|
|
379
|
+
interface RangePickerLabels extends DatePickerLabels {
|
|
380
|
+
startInput: string;
|
|
381
|
+
endInput: string;
|
|
382
|
+
presetsGroup: string;
|
|
383
|
+
}
|
|
384
|
+
interface TimePickerLabels {
|
|
385
|
+
timeInput: string;
|
|
386
|
+
hourList: string;
|
|
387
|
+
minuteList: string;
|
|
388
|
+
amPmToggle: string;
|
|
389
|
+
hourOption: (hour: number) => string;
|
|
390
|
+
minuteOption: (minute: number) => string;
|
|
391
|
+
}
|
|
392
|
+
interface DateTimePickerLabels extends DatePickerLabels, TimePickerLabels {
|
|
393
|
+
dateTimeInput: string;
|
|
394
|
+
}
|
|
395
|
+
declare const DEFAULT_DATEPICKER_LABELS: DatePickerLabels;
|
|
396
|
+
declare const DEFAULT_RANGEPICKER_LABELS: RangePickerLabels;
|
|
397
|
+
declare const DEFAULT_TIMEPICKER_LABELS: TimePickerLabels;
|
|
398
|
+
declare const DEFAULT_DATETIMEPICKER_LABELS: DateTimePickerLabels;
|
|
399
|
+
|
|
400
|
+
export { type CalendarDay, type CalendarGrid, type CalendarOptions, type CalendarWeek, DEFAULT_DATEPICKER_LABELS, DEFAULT_DATETIMEPICKER_LABELS, DEFAULT_RANGEPICKER_LABELS, DEFAULT_TIMEPICKER_LABELS, type DateAdapter, DateFnsAdapter, type DatePickerLabels, type DateRange, type DateTimePickerLabels, type DisabledRule, type ISODateString, type RangePickerLabels, type TimePickerLabels, type TimeValue, type WeekStartsOn, type WeekdayInfo, civilMidnightFromUtcDay, formatFullDate, formatInTimezone, formatMonthYear, formatTimeFromISO, formatTimeString, generateHours, generateMinutes, getCalendarDays, getMonthName, getTime, getTimeInTimezone, getTimezoneOffsetMinutes, getWeekdayNames, isDateDisabled, isSameDayInTimezone, isSameTime, maxDate, minDate, normalizeISO, parseInputValue, parseTimeString, setTime, setTimeInTimezone, startOfDayInTimezone, to12Hour, to24Hour, todayInTimezone };
|
package/dist/index.d.ts
CHANGED
|
@@ -102,6 +102,12 @@ interface CalendarOptions {
|
|
|
102
102
|
range?: DateRange | null;
|
|
103
103
|
/** Currently hovered date (for RangePicker preview) */
|
|
104
104
|
rangeHover?: ISODateString | null;
|
|
105
|
+
/**
|
|
106
|
+
* IANA timezone. When set, `isSameDay` comparisons for today/selected/range flags are
|
|
107
|
+
* evaluated in this zone — required when the picker stores values in display-tz civil
|
|
108
|
+
* midnight form while the grid iterates in UTC.
|
|
109
|
+
*/
|
|
110
|
+
timezone?: string;
|
|
105
111
|
}
|
|
106
112
|
|
|
107
113
|
/**
|
|
@@ -156,13 +162,16 @@ declare function maxDate(a: ISODateString, b: ISODateString, adapter: DateAdapte
|
|
|
156
162
|
/**
|
|
157
163
|
* Normalizes a date string to ISO 8601 UTC form.
|
|
158
164
|
* "2026-01-15" → "2026-01-15T00:00:00.000Z"
|
|
165
|
+
*
|
|
166
|
+
* Full datetime strings must include a timezone suffix (Z or ±HH:MM).
|
|
167
|
+
* Strings without a timezone suffix are treated as-is (not matched as datetime).
|
|
159
168
|
*/
|
|
160
169
|
declare function normalizeISO(value: string): string;
|
|
161
170
|
/**
|
|
162
171
|
* Parses user input text into an ISO string.
|
|
163
172
|
* Returns null on failure.
|
|
164
173
|
*/
|
|
165
|
-
declare function parseInputValue(input: string,
|
|
174
|
+
declare function parseInputValue(input: string, adapter: DateAdapter): string | null;
|
|
166
175
|
|
|
167
176
|
/** Time-of-day value (24-hour clock) */
|
|
168
177
|
interface TimeValue {
|
|
@@ -219,7 +228,7 @@ declare function isSameTime(a: TimeValue, b: TimeValue): boolean;
|
|
|
219
228
|
* Formats an ISO datetime as a time string (UTC based).
|
|
220
229
|
* Accepts an adapter for API consistency.
|
|
221
230
|
*/
|
|
222
|
-
declare function formatTimeFromISO(iso: ISODateString, format: 'HH:mm' | 'HH:mm:ss' | 'h:mm a' | 'h:mm:ss a'
|
|
231
|
+
declare function formatTimeFromISO(iso: ISODateString, format: 'HH:mm' | 'HH:mm:ss' | 'h:mm a' | 'h:mm:ss a'): string;
|
|
223
232
|
|
|
224
233
|
interface WeekdayInfo {
|
|
225
234
|
/** Short name (e.g. "Su", "일") */
|
|
@@ -252,4 +261,140 @@ declare function getWeekdayNames(locale?: string, weekStartsOn?: WeekStartsOn):
|
|
|
252
261
|
*/
|
|
253
262
|
declare function formatFullDate(iso: string, locale?: string): string;
|
|
254
263
|
|
|
255
|
-
|
|
264
|
+
/**
|
|
265
|
+
* Format a UTC ISO string for display in a specific IANA timezone.
|
|
266
|
+
* Handles DST transitions correctly. Supports a small set of tokens: `yyyy MM dd HH mm ss`.
|
|
267
|
+
*
|
|
268
|
+
* @param iso - UTC ISO 8601 string
|
|
269
|
+
* @param formatStr - token string (e.g. `"yyyy-MM-dd HH:mm"`)
|
|
270
|
+
* @param timeZone - IANA zone (e.g. `"America/New_York"`)
|
|
271
|
+
*
|
|
272
|
+
* @example
|
|
273
|
+
* formatInTimezone('2026-03-08T07:30:00.000Z', 'yyyy-MM-dd HH:mm', 'America/New_York');
|
|
274
|
+
* // → '2026-03-08 03:30' (post spring-forward EDT)
|
|
275
|
+
*/
|
|
276
|
+
declare function formatInTimezone(iso: ISODateString, formatStr: string, timeZone: string): string;
|
|
277
|
+
/**
|
|
278
|
+
* UTC offset (minutes east of UTC) at a given UTC instant, as applied by the given timezone.
|
|
279
|
+
* The offset may differ on either side of a DST transition.
|
|
280
|
+
*
|
|
281
|
+
* @example
|
|
282
|
+
* getTimezoneOffsetMinutes('2026-03-08T06:00:00.000Z', 'America/New_York'); // -300 (EST, UTC-5)
|
|
283
|
+
* getTimezoneOffsetMinutes('2026-03-08T07:00:00.000Z', 'America/New_York'); // -240 (EDT, UTC-4)
|
|
284
|
+
* getTimezoneOffsetMinutes('2026-01-15T12:00:00.000Z', 'Asia/Seoul'); // 540 (UTC+9)
|
|
285
|
+
*/
|
|
286
|
+
declare function getTimezoneOffsetMinutes(iso: ISODateString, timeZone: string): number;
|
|
287
|
+
/**
|
|
288
|
+
* Midnight of the civil date (as observed in `timeZone`) returned as a UTC ISO string.
|
|
289
|
+
*
|
|
290
|
+
* Across DST transitions this is the correct way to compute "start of day" — the offset
|
|
291
|
+
* changes, so the UTC instant of midnight differs before and after the transition.
|
|
292
|
+
*
|
|
293
|
+
* @example
|
|
294
|
+
* startOfDayInTimezone('2026-03-08T12:00:00.000Z', 'America/New_York'); // '2026-03-08T05:00:00.000Z' (EST)
|
|
295
|
+
* startOfDayInTimezone('2026-03-09T12:00:00.000Z', 'America/New_York'); // '2026-03-09T04:00:00.000Z' (EDT)
|
|
296
|
+
*/
|
|
297
|
+
declare function startOfDayInTimezone(iso: ISODateString, timeZone: string): ISODateString;
|
|
298
|
+
/**
|
|
299
|
+
* Whether two UTC instants fall on the same civil day in the given timezone.
|
|
300
|
+
* Timezone-safe alternative to comparing `iso.slice(0, 10)`.
|
|
301
|
+
*
|
|
302
|
+
* @example
|
|
303
|
+
* // Seoul is UTC+9, so 03:00 UTC and 14:00 UTC are both on 2026-01-15 KST
|
|
304
|
+
* isSameDayInTimezone('2026-01-15T03:00:00.000Z', '2026-01-15T14:00:00.000Z', 'Asia/Seoul'); // true
|
|
305
|
+
* // But 17:00 UTC is 02:00 KST on 2026-01-16
|
|
306
|
+
* isSameDayInTimezone('2026-01-15T03:00:00.000Z', '2026-01-15T17:00:00.000Z', 'Asia/Seoul'); // false
|
|
307
|
+
*/
|
|
308
|
+
declare function isSameDayInTimezone(a: ISODateString, b: ISODateString, timeZone: string): boolean;
|
|
309
|
+
/**
|
|
310
|
+
* "Today" in the given timezone, returned as the UTC ISO string representing that day's midnight.
|
|
311
|
+
* Prefer this over `new Date()` when the notion of "today" should follow the user's displayed
|
|
312
|
+
* timezone rather than the server's local clock.
|
|
313
|
+
*/
|
|
314
|
+
declare function todayInTimezone(timeZone: string): ISODateString;
|
|
315
|
+
/**
|
|
316
|
+
* Converts a UTC-midnight ISO (as produced by the default calendar grid iteration) into the
|
|
317
|
+
* civil midnight of the same calendar day in `timeZone`.
|
|
318
|
+
*
|
|
319
|
+
* This is the bridge used by DatePicker/RangePicker when `displayTimezone` is set: the grid
|
|
320
|
+
* iterates in UTC, so a cell's `isoString` is `YYYY-MM-DDT00:00:00.000Z`. When the user clicks
|
|
321
|
+
* that cell we want to emit an ISO representing the same civil day's midnight in the display
|
|
322
|
+
* timezone. A probe at noon UTC is used so the target civil day is unambiguous across any zone.
|
|
323
|
+
*
|
|
324
|
+
* @example
|
|
325
|
+
* civilMidnightFromUtcDay('2026-01-15T00:00:00.000Z', 'Asia/Seoul');
|
|
326
|
+
* // → '2026-01-14T15:00:00.000Z' (Seoul Jan 15 00:00 = UTC Jan 14 15:00)
|
|
327
|
+
*/
|
|
328
|
+
declare function civilMidnightFromUtcDay(gridUtcIso: ISODateString, timeZone: string): ISODateString;
|
|
329
|
+
/**
|
|
330
|
+
* Extracts the time-of-day (hours / minutes / seconds) of a UTC instant as observed in
|
|
331
|
+
* `timeZone`. The result differs from reading UTC hours when the zone has a non-zero offset.
|
|
332
|
+
*
|
|
333
|
+
* @example
|
|
334
|
+
* getTimeInTimezone('2026-01-15T00:00:00.000Z', 'Asia/Seoul');
|
|
335
|
+
* // → { hours: 9, minutes: 0, seconds: 0 } (Seoul is UTC+9)
|
|
336
|
+
*/
|
|
337
|
+
declare function getTimeInTimezone(iso: ISODateString, timeZone: string): {
|
|
338
|
+
hours: number;
|
|
339
|
+
minutes: number;
|
|
340
|
+
seconds: number;
|
|
341
|
+
};
|
|
342
|
+
/**
|
|
343
|
+
* Returns a new ISO UTC string where the civil date in `timeZone` is preserved and the time
|
|
344
|
+
* portion is replaced according to `partial` (as observed in that same timezone). Undefined
|
|
345
|
+
* fields keep their current value.
|
|
346
|
+
*
|
|
347
|
+
* Implementation note: the civil target (Y,M,D,H,m,s) is first mapped to a UTC epoch as if
|
|
348
|
+
* the wall-clock reading lived in UTC; then we subtract the timezone offset at that instant
|
|
349
|
+
* to recover the real UTC instant. The offset is refined once to absorb DST transitions.
|
|
350
|
+
*
|
|
351
|
+
* @example
|
|
352
|
+
* // In Asia/Seoul (UTC+9): set the hour to 10
|
|
353
|
+
* setTimeInTimezone('2026-01-15T00:00:00.000Z', { hours: 10 }, 'Asia/Seoul');
|
|
354
|
+
* // → '2026-01-15T01:00:00.000Z' (Seoul Jan 15 10:00 = UTC 01:00)
|
|
355
|
+
*/
|
|
356
|
+
declare function setTimeInTimezone(iso: ISODateString, partial: {
|
|
357
|
+
hours?: number;
|
|
358
|
+
minutes?: number;
|
|
359
|
+
seconds?: number;
|
|
360
|
+
}, timeZone: string): ISODateString;
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* Default English labels for ARIA attributes and accessible text.
|
|
364
|
+
* Override via the `labels` prop on Root components.
|
|
365
|
+
*/
|
|
366
|
+
interface DatePickerLabels {
|
|
367
|
+
triggerOpen: string;
|
|
368
|
+
triggerClose: string;
|
|
369
|
+
popoverLabel: string;
|
|
370
|
+
prevMonth: string;
|
|
371
|
+
nextMonth: string;
|
|
372
|
+
prevYear: string;
|
|
373
|
+
nextYear: string;
|
|
374
|
+
prevDecade: string;
|
|
375
|
+
nextDecade: string;
|
|
376
|
+
/** Used by DateTimePicker.Input (present only when DatePickerContext is provided by DateTimePickerRoot) */
|
|
377
|
+
dateTimeInput?: string;
|
|
378
|
+
}
|
|
379
|
+
interface RangePickerLabels extends DatePickerLabels {
|
|
380
|
+
startInput: string;
|
|
381
|
+
endInput: string;
|
|
382
|
+
presetsGroup: string;
|
|
383
|
+
}
|
|
384
|
+
interface TimePickerLabels {
|
|
385
|
+
timeInput: string;
|
|
386
|
+
hourList: string;
|
|
387
|
+
minuteList: string;
|
|
388
|
+
amPmToggle: string;
|
|
389
|
+
hourOption: (hour: number) => string;
|
|
390
|
+
minuteOption: (minute: number) => string;
|
|
391
|
+
}
|
|
392
|
+
interface DateTimePickerLabels extends DatePickerLabels, TimePickerLabels {
|
|
393
|
+
dateTimeInput: string;
|
|
394
|
+
}
|
|
395
|
+
declare const DEFAULT_DATEPICKER_LABELS: DatePickerLabels;
|
|
396
|
+
declare const DEFAULT_RANGEPICKER_LABELS: RangePickerLabels;
|
|
397
|
+
declare const DEFAULT_TIMEPICKER_LABELS: TimePickerLabels;
|
|
398
|
+
declare const DEFAULT_DATETIMEPICKER_LABELS: DateTimePickerLabels;
|
|
399
|
+
|
|
400
|
+
export { type CalendarDay, type CalendarGrid, type CalendarOptions, type CalendarWeek, DEFAULT_DATEPICKER_LABELS, DEFAULT_DATETIMEPICKER_LABELS, DEFAULT_RANGEPICKER_LABELS, DEFAULT_TIMEPICKER_LABELS, type DateAdapter, DateFnsAdapter, type DatePickerLabels, type DateRange, type DateTimePickerLabels, type DisabledRule, type ISODateString, type RangePickerLabels, type TimePickerLabels, type TimeValue, type WeekStartsOn, type WeekdayInfo, civilMidnightFromUtcDay, formatFullDate, formatInTimezone, formatMonthYear, formatTimeFromISO, formatTimeString, generateHours, generateMinutes, getCalendarDays, getMonthName, getTime, getTimeInTimezone, getTimezoneOffsetMinutes, getWeekdayNames, isDateDisabled, isSameDayInTimezone, isSameTime, maxDate, minDate, normalizeISO, parseInputValue, parseTimeString, setTime, setTimeInTimezone, startOfDayInTimezone, to12Hour, to24Hour, todayInTimezone };
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// src/adapters/date-fns.ts
|
|
2
2
|
import {
|
|
3
|
-
parseISO,
|
|
3
|
+
parseISO as parseISO2,
|
|
4
4
|
addDays as dfAddDays,
|
|
5
5
|
addMonths as dfAddMonths,
|
|
6
6
|
addYears as dfAddYears,
|
|
@@ -8,8 +8,115 @@ import {
|
|
|
8
8
|
isAfter as dfIsAfter,
|
|
9
9
|
isValid as dfIsValid
|
|
10
10
|
} from "date-fns";
|
|
11
|
+
|
|
12
|
+
// src/utils/timezone.ts
|
|
13
|
+
import { parseISO } from "date-fns";
|
|
14
|
+
var formatterCache = /* @__PURE__ */ new Map();
|
|
15
|
+
function getCachedPartsFormatter(timeZone) {
|
|
16
|
+
const key = `parts:${timeZone}`;
|
|
17
|
+
let fmt = formatterCache.get(key);
|
|
18
|
+
if (!fmt) {
|
|
19
|
+
fmt = new Intl.DateTimeFormat("en-US", {
|
|
20
|
+
timeZone,
|
|
21
|
+
year: "numeric",
|
|
22
|
+
month: "2-digit",
|
|
23
|
+
day: "2-digit",
|
|
24
|
+
hour: "2-digit",
|
|
25
|
+
minute: "2-digit",
|
|
26
|
+
second: "2-digit",
|
|
27
|
+
hourCycle: "h23"
|
|
28
|
+
});
|
|
29
|
+
formatterCache.set(key, fmt);
|
|
30
|
+
}
|
|
31
|
+
return fmt;
|
|
32
|
+
}
|
|
33
|
+
function partsInTimezone(utc, timeZone) {
|
|
34
|
+
const dtf = getCachedPartsFormatter(timeZone);
|
|
35
|
+
const parts = Object.fromEntries(
|
|
36
|
+
dtf.formatToParts(utc).map((p) => [p.type, p.value])
|
|
37
|
+
);
|
|
38
|
+
return {
|
|
39
|
+
year: Number(parts.year),
|
|
40
|
+
month: Number(parts.month),
|
|
41
|
+
day: Number(parts.day),
|
|
42
|
+
// Some locales/engines return '24' instead of '0' at midnight
|
|
43
|
+
hour: parts.hour === "24" ? 0 : Number(parts.hour),
|
|
44
|
+
minute: Number(parts.minute),
|
|
45
|
+
second: Number(parts.second)
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
function formatInTimezone(iso, formatStr, timeZone) {
|
|
49
|
+
const p = partsInTimezone(parseISO(iso), timeZone);
|
|
50
|
+
const tokens = {
|
|
51
|
+
yyyy: String(p.year),
|
|
52
|
+
MM: String(p.month).padStart(2, "0"),
|
|
53
|
+
dd: String(p.day).padStart(2, "0"),
|
|
54
|
+
HH: String(p.hour).padStart(2, "0"),
|
|
55
|
+
mm: String(p.minute).padStart(2, "0"),
|
|
56
|
+
ss: String(p.second).padStart(2, "0")
|
|
57
|
+
};
|
|
58
|
+
let result = formatStr;
|
|
59
|
+
for (const [token, value] of Object.entries(tokens).sort((a, b) => b[0].length - a[0].length)) {
|
|
60
|
+
result = result.split(token).join(value);
|
|
61
|
+
}
|
|
62
|
+
return result;
|
|
63
|
+
}
|
|
64
|
+
function getTimezoneOffsetMinutes(iso, timeZone) {
|
|
65
|
+
const utc = parseISO(iso);
|
|
66
|
+
const p = partsInTimezone(utc, timeZone);
|
|
67
|
+
const asUtcEpoch = Date.UTC(p.year, p.month - 1, p.day, p.hour, p.minute, p.second);
|
|
68
|
+
return Math.round((asUtcEpoch - utc.getTime()) / 6e4);
|
|
69
|
+
}
|
|
70
|
+
function startOfDayInTimezone(iso, timeZone) {
|
|
71
|
+
const utc = parseISO(iso);
|
|
72
|
+
const p = partsInTimezone(utc, timeZone);
|
|
73
|
+
const civilMidnightUtc = Date.UTC(p.year, p.month - 1, p.day, 0, 0, 0);
|
|
74
|
+
const midnightProbe = new Date(civilMidnightUtc).toISOString();
|
|
75
|
+
const offsetMinutes = getTimezoneOffsetMinutes(midnightProbe, timeZone);
|
|
76
|
+
return new Date(civilMidnightUtc - offsetMinutes * 6e4).toISOString();
|
|
77
|
+
}
|
|
78
|
+
function isSameDayInTimezone(a, b, timeZone) {
|
|
79
|
+
const pa = partsInTimezone(parseISO(a), timeZone);
|
|
80
|
+
const pb = partsInTimezone(parseISO(b), timeZone);
|
|
81
|
+
return pa.year === pb.year && pa.month === pb.month && pa.day === pb.day;
|
|
82
|
+
}
|
|
83
|
+
function todayInTimezone(timeZone) {
|
|
84
|
+
return startOfDayInTimezone((/* @__PURE__ */ new Date()).toISOString(), timeZone);
|
|
85
|
+
}
|
|
86
|
+
function civilMidnightFromUtcDay(gridUtcIso, timeZone) {
|
|
87
|
+
const utc = parseISO(gridUtcIso);
|
|
88
|
+
const probe = new Date(Date.UTC(
|
|
89
|
+
utc.getUTCFullYear(),
|
|
90
|
+
utc.getUTCMonth(),
|
|
91
|
+
utc.getUTCDate(),
|
|
92
|
+
12,
|
|
93
|
+
0,
|
|
94
|
+
0
|
|
95
|
+
)).toISOString();
|
|
96
|
+
return startOfDayInTimezone(probe, timeZone);
|
|
97
|
+
}
|
|
98
|
+
function getTimeInTimezone(iso, timeZone) {
|
|
99
|
+
const p = partsInTimezone(parseISO(iso), timeZone);
|
|
100
|
+
return { hours: p.hour, minutes: p.minute, seconds: p.second };
|
|
101
|
+
}
|
|
102
|
+
function setTimeInTimezone(iso, partial, timeZone) {
|
|
103
|
+
const p = partsInTimezone(parseISO(iso), timeZone);
|
|
104
|
+
const targetHours = partial.hours ?? p.hour;
|
|
105
|
+
const targetMinutes = partial.minutes ?? p.minute;
|
|
106
|
+
const targetSeconds = partial.seconds ?? p.second;
|
|
107
|
+
const civilEpoch = Date.UTC(p.year, p.month - 1, p.day, targetHours, targetMinutes, targetSeconds);
|
|
108
|
+
const probe1 = new Date(civilEpoch).toISOString();
|
|
109
|
+
const offset1 = getTimezoneOffsetMinutes(probe1, timeZone);
|
|
110
|
+
const realEpoch1 = civilEpoch - offset1 * 6e4;
|
|
111
|
+
const probe2 = new Date(realEpoch1).toISOString();
|
|
112
|
+
const offset2 = getTimezoneOffsetMinutes(probe2, timeZone);
|
|
113
|
+
const realEpoch2 = civilEpoch - offset2 * 6e4;
|
|
114
|
+
return new Date(realEpoch2).toISOString();
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// src/adapters/date-fns.ts
|
|
11
118
|
function toDate(iso) {
|
|
12
|
-
return
|
|
119
|
+
return parseISO2(iso);
|
|
13
120
|
}
|
|
14
121
|
function toISO(date) {
|
|
15
122
|
return date.toISOString();
|
|
@@ -51,7 +158,10 @@ var DateFnsAdapter = {
|
|
|
51
158
|
if (!value) return "";
|
|
52
159
|
return normalize(value);
|
|
53
160
|
},
|
|
54
|
-
format(iso, formatStr) {
|
|
161
|
+
format(iso, formatStr, timezone) {
|
|
162
|
+
if (timezone) {
|
|
163
|
+
return formatInTimezone(iso, formatStr, timezone);
|
|
164
|
+
}
|
|
55
165
|
const d = toDate(iso);
|
|
56
166
|
const tokens = {
|
|
57
167
|
yyyy: String(d.getUTCFullYear()),
|
|
@@ -84,8 +194,11 @@ var DateFnsAdapter = {
|
|
|
84
194
|
isAfter(a, b) {
|
|
85
195
|
return dfIsAfter(toDate(a), toDate(b));
|
|
86
196
|
},
|
|
87
|
-
isSameDay(a, b) {
|
|
197
|
+
isSameDay(a, b, timezone) {
|
|
88
198
|
if (!a || !b) return false;
|
|
199
|
+
if (timezone) {
|
|
200
|
+
return isSameDayInTimezone(a, b, timezone);
|
|
201
|
+
}
|
|
89
202
|
const da = toDate(a);
|
|
90
203
|
const db = toDate(b);
|
|
91
204
|
return da.getUTCFullYear() === db.getUTCFullYear() && da.getUTCMonth() === db.getUTCMonth() && da.getUTCDate() === db.getUTCDate();
|
|
@@ -95,7 +208,10 @@ var DateFnsAdapter = {
|
|
|
95
208
|
const db = toDate(b);
|
|
96
209
|
return da.getUTCFullYear() === db.getUTCFullYear() && da.getUTCMonth() === db.getUTCMonth();
|
|
97
210
|
},
|
|
98
|
-
startOfDay(iso) {
|
|
211
|
+
startOfDay(iso, timezone) {
|
|
212
|
+
if (timezone) {
|
|
213
|
+
return startOfDayInTimezone(iso, timezone);
|
|
214
|
+
}
|
|
99
215
|
return toISO(utcStartOfDay(toDate(iso)));
|
|
100
216
|
},
|
|
101
217
|
startOfMonth(iso) {
|
|
@@ -113,13 +229,16 @@ var DateFnsAdapter = {
|
|
|
113
229
|
now() {
|
|
114
230
|
return (/* @__PURE__ */ new Date()).toISOString();
|
|
115
231
|
},
|
|
116
|
-
today() {
|
|
232
|
+
today(timezone) {
|
|
233
|
+
if (timezone) {
|
|
234
|
+
return todayInTimezone(timezone);
|
|
235
|
+
}
|
|
117
236
|
return toISO(utcStartOfDay(/* @__PURE__ */ new Date()));
|
|
118
237
|
},
|
|
119
238
|
isValid(value) {
|
|
120
239
|
if (!value) return false;
|
|
121
240
|
const normalized = normalize(value);
|
|
122
|
-
const date =
|
|
241
|
+
const date = parseISO2(normalized);
|
|
123
242
|
return dfIsValid(date);
|
|
124
243
|
},
|
|
125
244
|
getYear(iso) {
|
|
@@ -145,23 +264,24 @@ function getCalendarDays(monthISO, adapter, options = {}) {
|
|
|
145
264
|
focusedDate,
|
|
146
265
|
disabled = [],
|
|
147
266
|
range,
|
|
148
|
-
rangeHover
|
|
267
|
+
rangeHover,
|
|
268
|
+
timezone
|
|
149
269
|
} = options;
|
|
150
|
-
const todayISO = today ?? adapter.today();
|
|
270
|
+
const todayISO = today ?? adapter.today(timezone);
|
|
151
271
|
const monthStart = adapter.startOfMonth(monthISO);
|
|
152
272
|
const gridStart = adapter.startOfWeek(monthStart, weekStartsOn);
|
|
153
|
-
const normalizedRange = normalizeRangeForDisplay(range, rangeHover, adapter);
|
|
273
|
+
const normalizedRange = normalizeRangeForDisplay(range, rangeHover, adapter, timezone);
|
|
154
274
|
const weeks = [];
|
|
155
275
|
let current = gridStart;
|
|
156
276
|
for (let week = 0; week < 6; week++) {
|
|
157
277
|
const days = [];
|
|
158
278
|
for (let day = 0; day < 7; day++) {
|
|
159
279
|
const isCurrentMonth = adapter.isSameMonth(current, monthISO);
|
|
160
|
-
const isTodayDate = adapter.isSameDay(current, todayISO);
|
|
161
|
-
const isSelected_ = selected ? adapter.isSameDay(current, selected) : false;
|
|
162
|
-
const isFocused_ = focusedDate ? adapter.isSameDay(current, focusedDate) : false;
|
|
280
|
+
const isTodayDate = adapter.isSameDay(current, todayISO, timezone);
|
|
281
|
+
const isSelected_ = selected ? adapter.isSameDay(current, selected, timezone) : false;
|
|
282
|
+
const isFocused_ = focusedDate ? adapter.isSameDay(current, focusedDate, timezone) : false;
|
|
163
283
|
const isDisabled_ = isDateDisabled(current, disabled, adapter);
|
|
164
|
-
const rangeFlags = computeRangeFlags(current, normalizedRange, adapter);
|
|
284
|
+
const rangeFlags = computeRangeFlags(current, normalizedRange, adapter, timezone);
|
|
165
285
|
days.push({
|
|
166
286
|
isoString: current,
|
|
167
287
|
dayNumber: adapter.getDate(current),
|
|
@@ -181,7 +301,7 @@ function getCalendarDays(monthISO, adapter, options = {}) {
|
|
|
181
301
|
}
|
|
182
302
|
return weeks;
|
|
183
303
|
}
|
|
184
|
-
function normalizeRangeForDisplay(range, hover, adapter) {
|
|
304
|
+
function normalizeRangeForDisplay(range, hover, adapter, _timezone) {
|
|
185
305
|
if (!range) return { start: null, end: null };
|
|
186
306
|
const { start, end } = range;
|
|
187
307
|
if (start && end) {
|
|
@@ -201,16 +321,16 @@ function normalizeRangeForDisplay(range, hover, adapter) {
|
|
|
201
321
|
}
|
|
202
322
|
return { start: null, end: null };
|
|
203
323
|
}
|
|
204
|
-
function computeRangeFlags(iso, range, adapter) {
|
|
324
|
+
function computeRangeFlags(iso, range, adapter, timezone) {
|
|
205
325
|
const { start, end } = range;
|
|
206
326
|
if (!start) {
|
|
207
327
|
return { isRangeStart: false, isRangeEnd: false, isInRange: false };
|
|
208
328
|
}
|
|
209
|
-
const isRangeStart = adapter.isSameDay(iso, start);
|
|
329
|
+
const isRangeStart = adapter.isSameDay(iso, start, timezone);
|
|
210
330
|
if (!end) {
|
|
211
331
|
return { isRangeStart, isRangeEnd: false, isInRange: false };
|
|
212
332
|
}
|
|
213
|
-
const isRangeEnd = adapter.isSameDay(iso, end);
|
|
333
|
+
const isRangeEnd = adapter.isSameDay(iso, end, timezone);
|
|
214
334
|
const isInRange = !isRangeStart && !isRangeEnd && adapter.isAfter(iso, start) && adapter.isBefore(iso, end);
|
|
215
335
|
return { isRangeStart, isRangeEnd, isInRange };
|
|
216
336
|
}
|
|
@@ -237,14 +357,14 @@ function maxDate(a, b, adapter) {
|
|
|
237
357
|
|
|
238
358
|
// src/utils/date.ts
|
|
239
359
|
var ISO_DATE_REGEX = /^\d{4}-\d{2}-\d{2}$/;
|
|
240
|
-
var ISO_DATETIME_REGEX = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}
|
|
360
|
+
var ISO_DATETIME_REGEX = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2})$/;
|
|
241
361
|
function normalizeISO(value) {
|
|
242
362
|
if (!value) return "";
|
|
243
363
|
if (ISO_DATETIME_REGEX.test(value)) return value;
|
|
244
364
|
if (ISO_DATE_REGEX.test(value)) return `${value}T00:00:00.000Z`;
|
|
245
365
|
return value;
|
|
246
366
|
}
|
|
247
|
-
function parseInputValue(input,
|
|
367
|
+
function parseInputValue(input, adapter) {
|
|
248
368
|
if (!input.trim()) return null;
|
|
249
369
|
const cleaned = input.replace(/\//g, "-").trim();
|
|
250
370
|
if (ISO_DATE_REGEX.test(cleaned)) {
|
|
@@ -319,8 +439,8 @@ function generateHours(format = "24h") {
|
|
|
319
439
|
return Array.from({ length: 24 }, (_, i) => i);
|
|
320
440
|
}
|
|
321
441
|
function generateMinutes(step = 1) {
|
|
322
|
-
if (step < 1 || step >
|
|
323
|
-
throw new Error(`[generateMinutes] step must be between 1 and
|
|
442
|
+
if (step < 1 || step > 30) {
|
|
443
|
+
throw new Error(`[generateMinutes] step must be between 1 and 30, got ${step}`);
|
|
324
444
|
}
|
|
325
445
|
const result = [];
|
|
326
446
|
for (let i = 0; i < 60; i += step) {
|
|
@@ -331,7 +451,7 @@ function generateMinutes(step = 1) {
|
|
|
331
451
|
function isSameTime(a, b) {
|
|
332
452
|
return a.hours === b.hours && a.minutes === b.minutes && a.seconds === b.seconds;
|
|
333
453
|
}
|
|
334
|
-
function formatTimeFromISO(iso, format
|
|
454
|
+
function formatTimeFromISO(iso, format) {
|
|
335
455
|
const time = getTime(iso);
|
|
336
456
|
if (format === "h:mm a" || format === "h:mm:ss a") {
|
|
337
457
|
const { hours12, period } = to12Hour(time.hours);
|
|
@@ -346,30 +466,35 @@ function formatTimeFromISO(iso, format, _adapter) {
|
|
|
346
466
|
}
|
|
347
467
|
|
|
348
468
|
// src/utils/locale.ts
|
|
469
|
+
var formatterCache2 = /* @__PURE__ */ new Map();
|
|
470
|
+
function getCachedFormatter(locale, options) {
|
|
471
|
+
const key = `${locale}:${JSON.stringify(options)}`;
|
|
472
|
+
let fmt = formatterCache2.get(key);
|
|
473
|
+
if (!fmt) {
|
|
474
|
+
fmt = new Intl.DateTimeFormat(locale, options);
|
|
475
|
+
formatterCache2.set(key, fmt);
|
|
476
|
+
}
|
|
477
|
+
return fmt;
|
|
478
|
+
}
|
|
479
|
+
var REFERENCE_YEAR = 2026;
|
|
349
480
|
function getMonthName(month, locale = "en-US") {
|
|
350
|
-
const date = new Date(Date.UTC(
|
|
351
|
-
return
|
|
481
|
+
const date = new Date(Date.UTC(REFERENCE_YEAR, month, 1));
|
|
482
|
+
return getCachedFormatter(locale, { month: "long", timeZone: "UTC" }).format(date);
|
|
352
483
|
}
|
|
353
484
|
function formatMonthYear(year, month, locale = "en-US") {
|
|
354
485
|
const date = new Date(Date.UTC(year, month, 1));
|
|
355
|
-
return
|
|
486
|
+
return getCachedFormatter(locale, {
|
|
356
487
|
year: "numeric",
|
|
357
488
|
month: "long",
|
|
358
489
|
timeZone: "UTC"
|
|
359
490
|
}).format(date);
|
|
360
491
|
}
|
|
361
492
|
function getWeekdayNames(locale = "en-US", weekStartsOn = 0) {
|
|
362
|
-
const shortFormatter =
|
|
363
|
-
|
|
364
|
-
timeZone: "UTC"
|
|
365
|
-
});
|
|
366
|
-
const fullFormatter = new Intl.DateTimeFormat(locale, {
|
|
367
|
-
weekday: "long",
|
|
368
|
-
timeZone: "UTC"
|
|
369
|
-
});
|
|
493
|
+
const shortFormatter = getCachedFormatter(locale, { weekday: "short", timeZone: "UTC" });
|
|
494
|
+
const fullFormatter = getCachedFormatter(locale, { weekday: "long", timeZone: "UTC" });
|
|
370
495
|
const days = [];
|
|
371
496
|
for (let i = 0; i < 7; i++) {
|
|
372
|
-
const date = new Date(Date.UTC(
|
|
497
|
+
const date = new Date(Date.UTC(REFERENCE_YEAR, 0, 4 + i));
|
|
373
498
|
days.push({
|
|
374
499
|
short: shortFormatter.format(date),
|
|
375
500
|
full: fullFormatter.format(date)
|
|
@@ -383,7 +508,7 @@ function getWeekdayNames(locale = "en-US", weekStartsOn = 0) {
|
|
|
383
508
|
}
|
|
384
509
|
function formatFullDate(iso, locale = "en-US") {
|
|
385
510
|
const date = new Date(iso);
|
|
386
|
-
return
|
|
511
|
+
return getCachedFormatter(locale, {
|
|
387
512
|
year: "numeric",
|
|
388
513
|
month: "long",
|
|
389
514
|
day: "numeric",
|
|
@@ -391,9 +516,48 @@ function formatFullDate(iso, locale = "en-US") {
|
|
|
391
516
|
timeZone: "UTC"
|
|
392
517
|
}).format(date);
|
|
393
518
|
}
|
|
519
|
+
|
|
520
|
+
// src/utils/labels.ts
|
|
521
|
+
var DEFAULT_DATEPICKER_LABELS = {
|
|
522
|
+
triggerOpen: "Open calendar",
|
|
523
|
+
triggerClose: "Close calendar",
|
|
524
|
+
popoverLabel: "Choose date",
|
|
525
|
+
prevMonth: "Previous month",
|
|
526
|
+
nextMonth: "Next month",
|
|
527
|
+
prevYear: "Previous year",
|
|
528
|
+
nextYear: "Next year",
|
|
529
|
+
prevDecade: "Previous decade",
|
|
530
|
+
nextDecade: "Next decade"
|
|
531
|
+
};
|
|
532
|
+
var DEFAULT_RANGEPICKER_LABELS = {
|
|
533
|
+
...DEFAULT_DATEPICKER_LABELS,
|
|
534
|
+
popoverLabel: "Choose date range",
|
|
535
|
+
startInput: "Start date",
|
|
536
|
+
endInput: "End date",
|
|
537
|
+
presetsGroup: "Date range presets"
|
|
538
|
+
};
|
|
539
|
+
var DEFAULT_TIMEPICKER_LABELS = {
|
|
540
|
+
timeInput: "Time",
|
|
541
|
+
hourList: "Hour",
|
|
542
|
+
minuteList: "Minute",
|
|
543
|
+
amPmToggle: "AM/PM",
|
|
544
|
+
hourOption: (hour) => `${hour} hours`,
|
|
545
|
+
minuteOption: (minute) => `${minute} minutes`
|
|
546
|
+
};
|
|
547
|
+
var DEFAULT_DATETIMEPICKER_LABELS = {
|
|
548
|
+
...DEFAULT_DATEPICKER_LABELS,
|
|
549
|
+
...DEFAULT_TIMEPICKER_LABELS,
|
|
550
|
+
dateTimeInput: "Date and time"
|
|
551
|
+
};
|
|
394
552
|
export {
|
|
553
|
+
DEFAULT_DATEPICKER_LABELS,
|
|
554
|
+
DEFAULT_DATETIMEPICKER_LABELS,
|
|
555
|
+
DEFAULT_RANGEPICKER_LABELS,
|
|
556
|
+
DEFAULT_TIMEPICKER_LABELS,
|
|
395
557
|
DateFnsAdapter,
|
|
558
|
+
civilMidnightFromUtcDay,
|
|
396
559
|
formatFullDate,
|
|
560
|
+
formatInTimezone,
|
|
397
561
|
formatMonthYear,
|
|
398
562
|
formatTimeFromISO,
|
|
399
563
|
formatTimeString,
|
|
@@ -402,8 +566,11 @@ export {
|
|
|
402
566
|
getCalendarDays,
|
|
403
567
|
getMonthName,
|
|
404
568
|
getTime,
|
|
569
|
+
getTimeInTimezone,
|
|
570
|
+
getTimezoneOffsetMinutes,
|
|
405
571
|
getWeekdayNames,
|
|
406
572
|
isDateDisabled,
|
|
573
|
+
isSameDayInTimezone,
|
|
407
574
|
isSameTime,
|
|
408
575
|
maxDate,
|
|
409
576
|
minDate,
|
|
@@ -411,6 +578,9 @@ export {
|
|
|
411
578
|
parseInputValue,
|
|
412
579
|
parseTimeString,
|
|
413
580
|
setTime,
|
|
581
|
+
setTimeInTimezone,
|
|
582
|
+
startOfDayInTimezone,
|
|
414
583
|
to12Hour,
|
|
415
|
-
to24Hour
|
|
584
|
+
to24Hour,
|
|
585
|
+
todayInTimezone
|
|
416
586
|
};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kalyx/core",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Kalyx core — platform-agnostic date logic",
|
|
3
|
+
"version": "0.4.0",
|
|
4
|
+
"description": "Kalyx core — platform-agnostic date logic, IANA timezone helpers, and the DateAdapter contract used by @kalyx/react",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "jiji-hoon96",
|
|
7
7
|
"homepage": "https://github.com/jiji-hoon96/kalyx#readme",
|
|
@@ -18,11 +18,14 @@
|
|
|
18
18
|
"datepicker",
|
|
19
19
|
"calendar",
|
|
20
20
|
"timezone",
|
|
21
|
+
"iana",
|
|
22
|
+
"dst",
|
|
21
23
|
"iso8601",
|
|
22
24
|
"utc",
|
|
23
25
|
"date-fns"
|
|
24
26
|
],
|
|
25
27
|
"type": "module",
|
|
28
|
+
"sideEffects": false,
|
|
26
29
|
"main": "./dist/index.cjs",
|
|
27
30
|
"module": "./dist/index.js",
|
|
28
31
|
"types": "./dist/index.d.ts",
|