@kalyx/core 0.3.0 → 1.0.0-rc.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 +152 -101
- package/dist/index.d.cts +53 -1
- package/dist/index.d.ts +53 -1
- package/dist/index.js +144 -96
- package/package.json +5 -2
package/dist/index.cjs
CHANGED
|
@@ -25,6 +25,7 @@ __export(index_exports, {
|
|
|
25
25
|
DEFAULT_RANGEPICKER_LABELS: () => DEFAULT_RANGEPICKER_LABELS,
|
|
26
26
|
DEFAULT_TIMEPICKER_LABELS: () => DEFAULT_TIMEPICKER_LABELS,
|
|
27
27
|
DateFnsAdapter: () => DateFnsAdapter,
|
|
28
|
+
civilMidnightFromUtcDay: () => civilMidnightFromUtcDay,
|
|
28
29
|
formatFullDate: () => formatFullDate,
|
|
29
30
|
formatInTimezone: () => formatInTimezone,
|
|
30
31
|
formatMonthYear: () => formatMonthYear,
|
|
@@ -35,6 +36,7 @@ __export(index_exports, {
|
|
|
35
36
|
getCalendarDays: () => getCalendarDays,
|
|
36
37
|
getMonthName: () => getMonthName,
|
|
37
38
|
getTime: () => getTime,
|
|
39
|
+
getTimeInTimezone: () => getTimeInTimezone,
|
|
38
40
|
getTimezoneOffsetMinutes: () => getTimezoneOffsetMinutes,
|
|
39
41
|
getWeekdayNames: () => getWeekdayNames,
|
|
40
42
|
isDateDisabled: () => isDateDisabled,
|
|
@@ -46,6 +48,7 @@ __export(index_exports, {
|
|
|
46
48
|
parseInputValue: () => parseInputValue,
|
|
47
49
|
parseTimeString: () => parseTimeString,
|
|
48
50
|
setTime: () => setTime,
|
|
51
|
+
setTimeInTimezone: () => setTimeInTimezone,
|
|
49
52
|
startOfDayInTimezone: () => startOfDayInTimezone,
|
|
50
53
|
to12Hour: () => to12Hour,
|
|
51
54
|
to24Hour: () => to24Hour,
|
|
@@ -54,9 +57,116 @@ __export(index_exports, {
|
|
|
54
57
|
module.exports = __toCommonJS(index_exports);
|
|
55
58
|
|
|
56
59
|
// src/adapters/date-fns.ts
|
|
60
|
+
var import_date_fns2 = require("date-fns");
|
|
61
|
+
|
|
62
|
+
// src/utils/timezone.ts
|
|
57
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
|
|
58
168
|
function toDate(iso) {
|
|
59
|
-
return (0,
|
|
169
|
+
return (0, import_date_fns2.parseISO)(iso);
|
|
60
170
|
}
|
|
61
171
|
function toISO(date) {
|
|
62
172
|
return date.toISOString();
|
|
@@ -98,7 +208,10 @@ var DateFnsAdapter = {
|
|
|
98
208
|
if (!value) return "";
|
|
99
209
|
return normalize(value);
|
|
100
210
|
},
|
|
101
|
-
format(iso, formatStr) {
|
|
211
|
+
format(iso, formatStr, timezone) {
|
|
212
|
+
if (timezone) {
|
|
213
|
+
return formatInTimezone(iso, formatStr, timezone);
|
|
214
|
+
}
|
|
102
215
|
const d = toDate(iso);
|
|
103
216
|
const tokens = {
|
|
104
217
|
yyyy: String(d.getUTCFullYear()),
|
|
@@ -117,22 +230,25 @@ var DateFnsAdapter = {
|
|
|
117
230
|
return result;
|
|
118
231
|
},
|
|
119
232
|
addDays(iso, n) {
|
|
120
|
-
return toISO((0,
|
|
233
|
+
return toISO((0, import_date_fns2.addDays)(toDate(iso), n));
|
|
121
234
|
},
|
|
122
235
|
addMonths(iso, n) {
|
|
123
|
-
return toISO((0,
|
|
236
|
+
return toISO((0, import_date_fns2.addMonths)(toDate(iso), n));
|
|
124
237
|
},
|
|
125
238
|
addYears(iso, n) {
|
|
126
|
-
return toISO((0,
|
|
239
|
+
return toISO((0, import_date_fns2.addYears)(toDate(iso), n));
|
|
127
240
|
},
|
|
128
241
|
isBefore(a, b) {
|
|
129
|
-
return (0,
|
|
242
|
+
return (0, import_date_fns2.isBefore)(toDate(a), toDate(b));
|
|
130
243
|
},
|
|
131
244
|
isAfter(a, b) {
|
|
132
|
-
return (0,
|
|
245
|
+
return (0, import_date_fns2.isAfter)(toDate(a), toDate(b));
|
|
133
246
|
},
|
|
134
|
-
isSameDay(a, b) {
|
|
247
|
+
isSameDay(a, b, timezone) {
|
|
135
248
|
if (!a || !b) return false;
|
|
249
|
+
if (timezone) {
|
|
250
|
+
return isSameDayInTimezone(a, b, timezone);
|
|
251
|
+
}
|
|
136
252
|
const da = toDate(a);
|
|
137
253
|
const db = toDate(b);
|
|
138
254
|
return da.getUTCFullYear() === db.getUTCFullYear() && da.getUTCMonth() === db.getUTCMonth() && da.getUTCDate() === db.getUTCDate();
|
|
@@ -142,7 +258,10 @@ var DateFnsAdapter = {
|
|
|
142
258
|
const db = toDate(b);
|
|
143
259
|
return da.getUTCFullYear() === db.getUTCFullYear() && da.getUTCMonth() === db.getUTCMonth();
|
|
144
260
|
},
|
|
145
|
-
startOfDay(iso) {
|
|
261
|
+
startOfDay(iso, timezone) {
|
|
262
|
+
if (timezone) {
|
|
263
|
+
return startOfDayInTimezone(iso, timezone);
|
|
264
|
+
}
|
|
146
265
|
return toISO(utcStartOfDay(toDate(iso)));
|
|
147
266
|
},
|
|
148
267
|
startOfMonth(iso) {
|
|
@@ -160,14 +279,17 @@ var DateFnsAdapter = {
|
|
|
160
279
|
now() {
|
|
161
280
|
return (/* @__PURE__ */ new Date()).toISOString();
|
|
162
281
|
},
|
|
163
|
-
today() {
|
|
282
|
+
today(timezone) {
|
|
283
|
+
if (timezone) {
|
|
284
|
+
return todayInTimezone(timezone);
|
|
285
|
+
}
|
|
164
286
|
return toISO(utcStartOfDay(/* @__PURE__ */ new Date()));
|
|
165
287
|
},
|
|
166
288
|
isValid(value) {
|
|
167
289
|
if (!value) return false;
|
|
168
290
|
const normalized = normalize(value);
|
|
169
|
-
const date = (0,
|
|
170
|
-
return (0,
|
|
291
|
+
const date = (0, import_date_fns2.parseISO)(normalized);
|
|
292
|
+
return (0, import_date_fns2.isValid)(date);
|
|
171
293
|
},
|
|
172
294
|
getYear(iso) {
|
|
173
295
|
return toDate(iso).getUTCFullYear();
|
|
@@ -192,23 +314,24 @@ function getCalendarDays(monthISO, adapter, options = {}) {
|
|
|
192
314
|
focusedDate,
|
|
193
315
|
disabled = [],
|
|
194
316
|
range,
|
|
195
|
-
rangeHover
|
|
317
|
+
rangeHover,
|
|
318
|
+
timezone
|
|
196
319
|
} = options;
|
|
197
|
-
const todayISO = today ?? adapter.today();
|
|
320
|
+
const todayISO = today ?? adapter.today(timezone);
|
|
198
321
|
const monthStart = adapter.startOfMonth(monthISO);
|
|
199
322
|
const gridStart = adapter.startOfWeek(monthStart, weekStartsOn);
|
|
200
|
-
const normalizedRange = normalizeRangeForDisplay(range, rangeHover, adapter);
|
|
323
|
+
const normalizedRange = normalizeRangeForDisplay(range, rangeHover, adapter, timezone);
|
|
201
324
|
const weeks = [];
|
|
202
325
|
let current = gridStart;
|
|
203
326
|
for (let week = 0; week < 6; week++) {
|
|
204
327
|
const days = [];
|
|
205
328
|
for (let day = 0; day < 7; day++) {
|
|
206
329
|
const isCurrentMonth = adapter.isSameMonth(current, monthISO);
|
|
207
|
-
const isTodayDate = adapter.isSameDay(current, todayISO);
|
|
208
|
-
const isSelected_ = selected ? adapter.isSameDay(current, selected) : false;
|
|
209
|
-
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;
|
|
210
333
|
const isDisabled_ = isDateDisabled(current, disabled, adapter);
|
|
211
|
-
const rangeFlags = computeRangeFlags(current, normalizedRange, adapter);
|
|
334
|
+
const rangeFlags = computeRangeFlags(current, normalizedRange, adapter, timezone);
|
|
212
335
|
days.push({
|
|
213
336
|
isoString: current,
|
|
214
337
|
dayNumber: adapter.getDate(current),
|
|
@@ -228,7 +351,7 @@ function getCalendarDays(monthISO, adapter, options = {}) {
|
|
|
228
351
|
}
|
|
229
352
|
return weeks;
|
|
230
353
|
}
|
|
231
|
-
function normalizeRangeForDisplay(range, hover, adapter) {
|
|
354
|
+
function normalizeRangeForDisplay(range, hover, adapter, _timezone) {
|
|
232
355
|
if (!range) return { start: null, end: null };
|
|
233
356
|
const { start, end } = range;
|
|
234
357
|
if (start && end) {
|
|
@@ -248,16 +371,16 @@ function normalizeRangeForDisplay(range, hover, adapter) {
|
|
|
248
371
|
}
|
|
249
372
|
return { start: null, end: null };
|
|
250
373
|
}
|
|
251
|
-
function computeRangeFlags(iso, range, adapter) {
|
|
374
|
+
function computeRangeFlags(iso, range, adapter, timezone) {
|
|
252
375
|
const { start, end } = range;
|
|
253
376
|
if (!start) {
|
|
254
377
|
return { isRangeStart: false, isRangeEnd: false, isInRange: false };
|
|
255
378
|
}
|
|
256
|
-
const isRangeStart = adapter.isSameDay(iso, start);
|
|
379
|
+
const isRangeStart = adapter.isSameDay(iso, start, timezone);
|
|
257
380
|
if (!end) {
|
|
258
381
|
return { isRangeStart, isRangeEnd: false, isInRange: false };
|
|
259
382
|
}
|
|
260
|
-
const isRangeEnd = adapter.isSameDay(iso, end);
|
|
383
|
+
const isRangeEnd = adapter.isSameDay(iso, end, timezone);
|
|
261
384
|
const isInRange = !isRangeStart && !isRangeEnd && adapter.isAfter(iso, start) && adapter.isBefore(iso, end);
|
|
262
385
|
return { isRangeStart, isRangeEnd, isInRange };
|
|
263
386
|
}
|
|
@@ -393,13 +516,13 @@ function formatTimeFromISO(iso, format) {
|
|
|
393
516
|
}
|
|
394
517
|
|
|
395
518
|
// src/utils/locale.ts
|
|
396
|
-
var
|
|
519
|
+
var formatterCache2 = /* @__PURE__ */ new Map();
|
|
397
520
|
function getCachedFormatter(locale, options) {
|
|
398
521
|
const key = `${locale}:${JSON.stringify(options)}`;
|
|
399
|
-
let fmt =
|
|
522
|
+
let fmt = formatterCache2.get(key);
|
|
400
523
|
if (!fmt) {
|
|
401
524
|
fmt = new Intl.DateTimeFormat(locale, options);
|
|
402
|
-
|
|
525
|
+
formatterCache2.set(key, fmt);
|
|
403
526
|
}
|
|
404
527
|
return fmt;
|
|
405
528
|
}
|
|
@@ -444,81 +567,6 @@ function formatFullDate(iso, locale = "en-US") {
|
|
|
444
567
|
}).format(date);
|
|
445
568
|
}
|
|
446
569
|
|
|
447
|
-
// src/utils/timezone.ts
|
|
448
|
-
var import_date_fns2 = require("date-fns");
|
|
449
|
-
var formatterCache2 = /* @__PURE__ */ new Map();
|
|
450
|
-
function getCachedPartsFormatter(timeZone) {
|
|
451
|
-
const key = `parts:${timeZone}`;
|
|
452
|
-
let fmt = formatterCache2.get(key);
|
|
453
|
-
if (!fmt) {
|
|
454
|
-
fmt = new Intl.DateTimeFormat("en-US", {
|
|
455
|
-
timeZone,
|
|
456
|
-
year: "numeric",
|
|
457
|
-
month: "2-digit",
|
|
458
|
-
day: "2-digit",
|
|
459
|
-
hour: "2-digit",
|
|
460
|
-
minute: "2-digit",
|
|
461
|
-
second: "2-digit",
|
|
462
|
-
hourCycle: "h23"
|
|
463
|
-
});
|
|
464
|
-
formatterCache2.set(key, fmt);
|
|
465
|
-
}
|
|
466
|
-
return fmt;
|
|
467
|
-
}
|
|
468
|
-
function partsInTimezone(utc, timeZone) {
|
|
469
|
-
const dtf = getCachedPartsFormatter(timeZone);
|
|
470
|
-
const parts = Object.fromEntries(
|
|
471
|
-
dtf.formatToParts(utc).map((p) => [p.type, p.value])
|
|
472
|
-
);
|
|
473
|
-
return {
|
|
474
|
-
year: Number(parts.year),
|
|
475
|
-
month: Number(parts.month),
|
|
476
|
-
day: Number(parts.day),
|
|
477
|
-
// Some locales/engines return '24' instead of '0' at midnight
|
|
478
|
-
hour: parts.hour === "24" ? 0 : Number(parts.hour),
|
|
479
|
-
minute: Number(parts.minute),
|
|
480
|
-
second: Number(parts.second)
|
|
481
|
-
};
|
|
482
|
-
}
|
|
483
|
-
function formatInTimezone(iso, formatStr, timeZone) {
|
|
484
|
-
const p = partsInTimezone((0, import_date_fns2.parseISO)(iso), timeZone);
|
|
485
|
-
const tokens = {
|
|
486
|
-
yyyy: String(p.year),
|
|
487
|
-
MM: String(p.month).padStart(2, "0"),
|
|
488
|
-
dd: String(p.day).padStart(2, "0"),
|
|
489
|
-
HH: String(p.hour).padStart(2, "0"),
|
|
490
|
-
mm: String(p.minute).padStart(2, "0"),
|
|
491
|
-
ss: String(p.second).padStart(2, "0")
|
|
492
|
-
};
|
|
493
|
-
let result = formatStr;
|
|
494
|
-
for (const [token, value] of Object.entries(tokens).sort((a, b) => b[0].length - a[0].length)) {
|
|
495
|
-
result = result.split(token).join(value);
|
|
496
|
-
}
|
|
497
|
-
return result;
|
|
498
|
-
}
|
|
499
|
-
function getTimezoneOffsetMinutes(iso, timeZone) {
|
|
500
|
-
const utc = (0, import_date_fns2.parseISO)(iso);
|
|
501
|
-
const p = partsInTimezone(utc, timeZone);
|
|
502
|
-
const asUtcEpoch = Date.UTC(p.year, p.month - 1, p.day, p.hour, p.minute, p.second);
|
|
503
|
-
return Math.round((asUtcEpoch - utc.getTime()) / 6e4);
|
|
504
|
-
}
|
|
505
|
-
function startOfDayInTimezone(iso, timeZone) {
|
|
506
|
-
const utc = (0, import_date_fns2.parseISO)(iso);
|
|
507
|
-
const p = partsInTimezone(utc, timeZone);
|
|
508
|
-
const civilMidnightUtc = Date.UTC(p.year, p.month - 1, p.day, 0, 0, 0);
|
|
509
|
-
const midnightProbe = new Date(civilMidnightUtc).toISOString();
|
|
510
|
-
const offsetMinutes = getTimezoneOffsetMinutes(midnightProbe, timeZone);
|
|
511
|
-
return new Date(civilMidnightUtc - offsetMinutes * 6e4).toISOString();
|
|
512
|
-
}
|
|
513
|
-
function isSameDayInTimezone(a, b, timeZone) {
|
|
514
|
-
const pa = partsInTimezone((0, import_date_fns2.parseISO)(a), timeZone);
|
|
515
|
-
const pb = partsInTimezone((0, import_date_fns2.parseISO)(b), timeZone);
|
|
516
|
-
return pa.year === pb.year && pa.month === pb.month && pa.day === pb.day;
|
|
517
|
-
}
|
|
518
|
-
function todayInTimezone(timeZone) {
|
|
519
|
-
return startOfDayInTimezone((/* @__PURE__ */ new Date()).toISOString(), timeZone);
|
|
520
|
-
}
|
|
521
|
-
|
|
522
570
|
// src/utils/labels.ts
|
|
523
571
|
var DEFAULT_DATEPICKER_LABELS = {
|
|
524
572
|
triggerOpen: "Open calendar",
|
|
@@ -558,6 +606,7 @@ var DEFAULT_DATETIMEPICKER_LABELS = {
|
|
|
558
606
|
DEFAULT_RANGEPICKER_LABELS,
|
|
559
607
|
DEFAULT_TIMEPICKER_LABELS,
|
|
560
608
|
DateFnsAdapter,
|
|
609
|
+
civilMidnightFromUtcDay,
|
|
561
610
|
formatFullDate,
|
|
562
611
|
formatInTimezone,
|
|
563
612
|
formatMonthYear,
|
|
@@ -568,6 +617,7 @@ var DEFAULT_DATETIMEPICKER_LABELS = {
|
|
|
568
617
|
getCalendarDays,
|
|
569
618
|
getMonthName,
|
|
570
619
|
getTime,
|
|
620
|
+
getTimeInTimezone,
|
|
571
621
|
getTimezoneOffsetMinutes,
|
|
572
622
|
getWeekdayNames,
|
|
573
623
|
isDateDisabled,
|
|
@@ -579,6 +629,7 @@ var DEFAULT_DATETIMEPICKER_LABELS = {
|
|
|
579
629
|
parseInputValue,
|
|
580
630
|
parseTimeString,
|
|
581
631
|
setTime,
|
|
632
|
+
setTimeInTimezone,
|
|
582
633
|
startOfDayInTimezone,
|
|
583
634
|
to12Hour,
|
|
584
635
|
to24Hour,
|
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
|
/**
|
|
@@ -306,6 +312,52 @@ declare function isSameDayInTimezone(a: ISODateString, b: ISODateString, timeZon
|
|
|
306
312
|
* timezone rather than the server's local clock.
|
|
307
313
|
*/
|
|
308
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;
|
|
309
361
|
|
|
310
362
|
/**
|
|
311
363
|
* Default English labels for ARIA attributes and accessible text.
|
|
@@ -345,4 +397,4 @@ declare const DEFAULT_RANGEPICKER_LABELS: RangePickerLabels;
|
|
|
345
397
|
declare const DEFAULT_TIMEPICKER_LABELS: TimePickerLabels;
|
|
346
398
|
declare const DEFAULT_DATETIMEPICKER_LABELS: DateTimePickerLabels;
|
|
347
399
|
|
|
348
|
-
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, formatFullDate, formatInTimezone, formatMonthYear, formatTimeFromISO, formatTimeString, generateHours, generateMinutes, getCalendarDays, getMonthName, getTime, getTimezoneOffsetMinutes, getWeekdayNames, isDateDisabled, isSameDayInTimezone, isSameTime, maxDate, minDate, normalizeISO, parseInputValue, parseTimeString, setTime, startOfDayInTimezone, to12Hour, to24Hour, todayInTimezone };
|
|
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
|
/**
|
|
@@ -306,6 +312,52 @@ declare function isSameDayInTimezone(a: ISODateString, b: ISODateString, timeZon
|
|
|
306
312
|
* timezone rather than the server's local clock.
|
|
307
313
|
*/
|
|
308
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;
|
|
309
361
|
|
|
310
362
|
/**
|
|
311
363
|
* Default English labels for ARIA attributes and accessible text.
|
|
@@ -345,4 +397,4 @@ declare const DEFAULT_RANGEPICKER_LABELS: RangePickerLabels;
|
|
|
345
397
|
declare const DEFAULT_TIMEPICKER_LABELS: TimePickerLabels;
|
|
346
398
|
declare const DEFAULT_DATETIMEPICKER_LABELS: DateTimePickerLabels;
|
|
347
399
|
|
|
348
|
-
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, formatFullDate, formatInTimezone, formatMonthYear, formatTimeFromISO, formatTimeString, generateHours, generateMinutes, getCalendarDays, getMonthName, getTime, getTimezoneOffsetMinutes, getWeekdayNames, isDateDisabled, isSameDayInTimezone, isSameTime, maxDate, minDate, normalizeISO, parseInputValue, parseTimeString, setTime, startOfDayInTimezone, to12Hour, to24Hour, todayInTimezone };
|
|
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
|
}
|
|
@@ -346,13 +466,13 @@ function formatTimeFromISO(iso, format) {
|
|
|
346
466
|
}
|
|
347
467
|
|
|
348
468
|
// src/utils/locale.ts
|
|
349
|
-
var
|
|
469
|
+
var formatterCache2 = /* @__PURE__ */ new Map();
|
|
350
470
|
function getCachedFormatter(locale, options) {
|
|
351
471
|
const key = `${locale}:${JSON.stringify(options)}`;
|
|
352
|
-
let fmt =
|
|
472
|
+
let fmt = formatterCache2.get(key);
|
|
353
473
|
if (!fmt) {
|
|
354
474
|
fmt = new Intl.DateTimeFormat(locale, options);
|
|
355
|
-
|
|
475
|
+
formatterCache2.set(key, fmt);
|
|
356
476
|
}
|
|
357
477
|
return fmt;
|
|
358
478
|
}
|
|
@@ -397,81 +517,6 @@ function formatFullDate(iso, locale = "en-US") {
|
|
|
397
517
|
}).format(date);
|
|
398
518
|
}
|
|
399
519
|
|
|
400
|
-
// src/utils/timezone.ts
|
|
401
|
-
import { parseISO as parseISO2 } from "date-fns";
|
|
402
|
-
var formatterCache2 = /* @__PURE__ */ new Map();
|
|
403
|
-
function getCachedPartsFormatter(timeZone) {
|
|
404
|
-
const key = `parts:${timeZone}`;
|
|
405
|
-
let fmt = formatterCache2.get(key);
|
|
406
|
-
if (!fmt) {
|
|
407
|
-
fmt = new Intl.DateTimeFormat("en-US", {
|
|
408
|
-
timeZone,
|
|
409
|
-
year: "numeric",
|
|
410
|
-
month: "2-digit",
|
|
411
|
-
day: "2-digit",
|
|
412
|
-
hour: "2-digit",
|
|
413
|
-
minute: "2-digit",
|
|
414
|
-
second: "2-digit",
|
|
415
|
-
hourCycle: "h23"
|
|
416
|
-
});
|
|
417
|
-
formatterCache2.set(key, fmt);
|
|
418
|
-
}
|
|
419
|
-
return fmt;
|
|
420
|
-
}
|
|
421
|
-
function partsInTimezone(utc, timeZone) {
|
|
422
|
-
const dtf = getCachedPartsFormatter(timeZone);
|
|
423
|
-
const parts = Object.fromEntries(
|
|
424
|
-
dtf.formatToParts(utc).map((p) => [p.type, p.value])
|
|
425
|
-
);
|
|
426
|
-
return {
|
|
427
|
-
year: Number(parts.year),
|
|
428
|
-
month: Number(parts.month),
|
|
429
|
-
day: Number(parts.day),
|
|
430
|
-
// Some locales/engines return '24' instead of '0' at midnight
|
|
431
|
-
hour: parts.hour === "24" ? 0 : Number(parts.hour),
|
|
432
|
-
minute: Number(parts.minute),
|
|
433
|
-
second: Number(parts.second)
|
|
434
|
-
};
|
|
435
|
-
}
|
|
436
|
-
function formatInTimezone(iso, formatStr, timeZone) {
|
|
437
|
-
const p = partsInTimezone(parseISO2(iso), timeZone);
|
|
438
|
-
const tokens = {
|
|
439
|
-
yyyy: String(p.year),
|
|
440
|
-
MM: String(p.month).padStart(2, "0"),
|
|
441
|
-
dd: String(p.day).padStart(2, "0"),
|
|
442
|
-
HH: String(p.hour).padStart(2, "0"),
|
|
443
|
-
mm: String(p.minute).padStart(2, "0"),
|
|
444
|
-
ss: String(p.second).padStart(2, "0")
|
|
445
|
-
};
|
|
446
|
-
let result = formatStr;
|
|
447
|
-
for (const [token, value] of Object.entries(tokens).sort((a, b) => b[0].length - a[0].length)) {
|
|
448
|
-
result = result.split(token).join(value);
|
|
449
|
-
}
|
|
450
|
-
return result;
|
|
451
|
-
}
|
|
452
|
-
function getTimezoneOffsetMinutes(iso, timeZone) {
|
|
453
|
-
const utc = parseISO2(iso);
|
|
454
|
-
const p = partsInTimezone(utc, timeZone);
|
|
455
|
-
const asUtcEpoch = Date.UTC(p.year, p.month - 1, p.day, p.hour, p.minute, p.second);
|
|
456
|
-
return Math.round((asUtcEpoch - utc.getTime()) / 6e4);
|
|
457
|
-
}
|
|
458
|
-
function startOfDayInTimezone(iso, timeZone) {
|
|
459
|
-
const utc = parseISO2(iso);
|
|
460
|
-
const p = partsInTimezone(utc, timeZone);
|
|
461
|
-
const civilMidnightUtc = Date.UTC(p.year, p.month - 1, p.day, 0, 0, 0);
|
|
462
|
-
const midnightProbe = new Date(civilMidnightUtc).toISOString();
|
|
463
|
-
const offsetMinutes = getTimezoneOffsetMinutes(midnightProbe, timeZone);
|
|
464
|
-
return new Date(civilMidnightUtc - offsetMinutes * 6e4).toISOString();
|
|
465
|
-
}
|
|
466
|
-
function isSameDayInTimezone(a, b, timeZone) {
|
|
467
|
-
const pa = partsInTimezone(parseISO2(a), timeZone);
|
|
468
|
-
const pb = partsInTimezone(parseISO2(b), timeZone);
|
|
469
|
-
return pa.year === pb.year && pa.month === pb.month && pa.day === pb.day;
|
|
470
|
-
}
|
|
471
|
-
function todayInTimezone(timeZone) {
|
|
472
|
-
return startOfDayInTimezone((/* @__PURE__ */ new Date()).toISOString(), timeZone);
|
|
473
|
-
}
|
|
474
|
-
|
|
475
520
|
// src/utils/labels.ts
|
|
476
521
|
var DEFAULT_DATEPICKER_LABELS = {
|
|
477
522
|
triggerOpen: "Open calendar",
|
|
@@ -510,6 +555,7 @@ export {
|
|
|
510
555
|
DEFAULT_RANGEPICKER_LABELS,
|
|
511
556
|
DEFAULT_TIMEPICKER_LABELS,
|
|
512
557
|
DateFnsAdapter,
|
|
558
|
+
civilMidnightFromUtcDay,
|
|
513
559
|
formatFullDate,
|
|
514
560
|
formatInTimezone,
|
|
515
561
|
formatMonthYear,
|
|
@@ -520,6 +566,7 @@ export {
|
|
|
520
566
|
getCalendarDays,
|
|
521
567
|
getMonthName,
|
|
522
568
|
getTime,
|
|
569
|
+
getTimeInTimezone,
|
|
523
570
|
getTimezoneOffsetMinutes,
|
|
524
571
|
getWeekdayNames,
|
|
525
572
|
isDateDisabled,
|
|
@@ -531,6 +578,7 @@ export {
|
|
|
531
578
|
parseInputValue,
|
|
532
579
|
parseTimeString,
|
|
533
580
|
setTime,
|
|
581
|
+
setTimeInTimezone,
|
|
534
582
|
startOfDayInTimezone,
|
|
535
583
|
to12Hour,
|
|
536
584
|
to24Hour,
|
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": "1.0.0-rc.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",
|