@kalyx/core 1.0.0-rc.11 → 1.0.0-rc.13

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/CHANGELOG.md CHANGED
@@ -1,5 +1,47 @@
1
1
  # @kalyx/core
2
2
 
3
+ ## 1.0.0-rc.13
4
+
5
+ ### Major Changes
6
+
7
+ - 5b6c37f: Extract `@kalyx/adapter-date-fns` and make `@kalyx/core` neutral
8
+
9
+ Step 1 + 2 of the four-step adapter-extraction plan (see `.claude/skills/adapter-extraction.md`). After this change, `@kalyx/core` no longer depends on `date-fns` or `date-fns-tz`; it ships only the platform-agnostic date logic (`getCalendarDays`, `isDateDisabled`, timezone helpers, labels, the `DateAdapter` contract). The DateFnsAdapter implementation now lives in its own publishable package so dayjs / luxon / Temporal adapters can be added later without forcing every Kalyx user to bundle two date libraries.
10
+
11
+ ### What changed
12
+ - **`@kalyx/core`** — `DateFnsAdapter` is no longer exported and `date-fns` / `date-fns-tz` are no longer listed as dependencies. `utils/timezone.ts` was the lone leak and uses native `new Date(string)` now (every caller already routes through `normalizeISO` or `DateAdapter.parse`, so the input subset is fully spec-defined).
13
+ - **`@kalyx/adapter-date-fns`** — new package with the full `DateFnsAdapter` implementation moved verbatim. Same UTC semantics, same timezone-aware paths, same 35 adapter tests.
14
+ - **`@kalyx/react`** — imports `DateFnsAdapter` from `@kalyx/adapter-date-fns` now. The default adapter is still wired up automatically — anyone using `import { DatePicker } from '@kalyx/react'` keeps the previous behaviour with zero changes. The adapter package is a direct dependency so consumers installing just `@kalyx/react` continue to get a working default.
15
+
16
+ ### Migration
17
+
18
+ If you imported `DateFnsAdapter` directly from `@kalyx/core`:
19
+
20
+ ```diff
21
+ - import { DateFnsAdapter } from '@kalyx/core';
22
+ + import { DateFnsAdapter } from '@kalyx/adapter-date-fns';
23
+ ```
24
+
25
+ `@kalyx/react` consumers don't need to change anything — the adapter is still re-exported from `@kalyx/react`.
26
+
27
+ ### Next (separate PR)
28
+
29
+ The `/headless` entry point (`@kalyx/react/headless`) that lets dayjs/luxon users tree-shake date-fns out is a follow-up. The component Roots still default to the date-fns adapter inline; the entry split requires moving that fallback out of each Root and into the entry boundary.
30
+
31
+ ## 1.0.0-rc.12
32
+
33
+ ### Patch Changes
34
+
35
+ - 0556886: fix(core): validate inputs to `to12Hour` and `to24Hour`
36
+
37
+ `to12Hour(hours24)` and `to24Hour(hours12, period)` are public exports from `@kalyx/core` but had no input validation. The previous silent arithmetic mapped invalid inputs onto plausible-looking but wrong outputs and hid caller bugs:
38
+ - `to12Hour(24)` returned `{ hours12: 12, period: 'PM' }` (because `24 % 12 = 0` → mapped to 12)
39
+ - `to12Hour(-1)` returned `{ hours12: -1, period: 'AM' }`
40
+ - `to24Hour(13, 'PM')` returned `25`
41
+ - `to24Hour(0, 'AM')` returned `0` (but `0` is not a valid 12-hour clock value — midnight is `12 AM`)
42
+
43
+ Both functions now throw `RangeError` with a clear message when the input is outside its valid integer range (`[0, 23]` for `to12Hour`, `[1, 12]` for `to24Hour`). `Number.isInteger` guards non-integers and `NaN`. No `@kalyx/react` callers ever passed invalid values, so the internal contracts are unchanged; only direct `@kalyx/core` users who relied on the silent-wrong behaviour see the new exception.
44
+
3
45
  ## 1.0.0-rc.11
4
46
 
5
47
  ### Minor Changes
package/dist/index.cjs CHANGED
@@ -24,7 +24,6 @@ __export(index_exports, {
24
24
  DEFAULT_DATETIMEPICKER_LABELS: () => DEFAULT_DATETIMEPICKER_LABELS,
25
25
  DEFAULT_RANGEPICKER_LABELS: () => DEFAULT_RANGEPICKER_LABELS,
26
26
  DEFAULT_TIMEPICKER_LABELS: () => DEFAULT_TIMEPICKER_LABELS,
27
- DateFnsAdapter: () => DateFnsAdapter,
28
27
  civilMidnightFromUtcDay: () => civilMidnightFromUtcDay,
29
28
  formatFullDate: () => formatFullDate,
30
29
  formatInTimezone: () => formatInTimezone,
@@ -57,249 +56,6 @@ __export(index_exports, {
57
56
  });
58
57
  module.exports = __toCommonJS(index_exports);
59
58
 
60
- // src/adapters/date-fns.ts
61
- var import_date_fns2 = require("date-fns");
62
-
63
- // src/utils/timezone.ts
64
- var import_date_fns = require("date-fns");
65
- var formatterCache = /* @__PURE__ */ new Map();
66
- function getCachedPartsFormatter(timeZone) {
67
- const key = `parts:${timeZone}`;
68
- let fmt = formatterCache.get(key);
69
- if (!fmt) {
70
- fmt = new Intl.DateTimeFormat("en-US", {
71
- timeZone,
72
- year: "numeric",
73
- month: "2-digit",
74
- day: "2-digit",
75
- hour: "2-digit",
76
- minute: "2-digit",
77
- second: "2-digit",
78
- hourCycle: "h23"
79
- });
80
- formatterCache.set(key, fmt);
81
- }
82
- return fmt;
83
- }
84
- function partsInTimezone(utc, timeZone) {
85
- const dtf = getCachedPartsFormatter(timeZone);
86
- const parts = Object.fromEntries(dtf.formatToParts(utc).map((p) => [p.type, p.value]));
87
- return {
88
- year: Number(parts.year),
89
- month: Number(parts.month),
90
- day: Number(parts.day),
91
- // Some locales/engines return '24' instead of '0' at midnight
92
- hour: parts.hour === "24" ? 0 : Number(parts.hour),
93
- minute: Number(parts.minute),
94
- second: Number(parts.second)
95
- };
96
- }
97
- function formatInTimezone(iso, formatStr, timeZone) {
98
- const p = partsInTimezone((0, import_date_fns.parseISO)(iso), timeZone);
99
- const tokens = {
100
- yyyy: String(p.year),
101
- MM: String(p.month).padStart(2, "0"),
102
- dd: String(p.day).padStart(2, "0"),
103
- HH: String(p.hour).padStart(2, "0"),
104
- mm: String(p.minute).padStart(2, "0"),
105
- ss: String(p.second).padStart(2, "0")
106
- };
107
- let result = formatStr;
108
- for (const [token, value] of Object.entries(tokens).sort((a, b) => b[0].length - a[0].length)) {
109
- result = result.split(token).join(value);
110
- }
111
- return result;
112
- }
113
- function getTimezoneOffsetMinutes(iso, timeZone) {
114
- const utc = (0, import_date_fns.parseISO)(iso);
115
- const p = partsInTimezone(utc, timeZone);
116
- const asUtcEpoch = Date.UTC(p.year, p.month - 1, p.day, p.hour, p.minute, p.second);
117
- return Math.round((asUtcEpoch - utc.getTime()) / 6e4);
118
- }
119
- function startOfDayInTimezone(iso, timeZone) {
120
- const utc = (0, import_date_fns.parseISO)(iso);
121
- const p = partsInTimezone(utc, timeZone);
122
- const civilMidnightUtc = Date.UTC(p.year, p.month - 1, p.day, 0, 0, 0);
123
- const midnightProbe = new Date(civilMidnightUtc).toISOString();
124
- const offsetMinutes = getTimezoneOffsetMinutes(midnightProbe, timeZone);
125
- return new Date(civilMidnightUtc - offsetMinutes * 6e4).toISOString();
126
- }
127
- function isSameDayInTimezone(a, b, timeZone) {
128
- const pa = partsInTimezone((0, import_date_fns.parseISO)(a), timeZone);
129
- const pb = partsInTimezone((0, import_date_fns.parseISO)(b), timeZone);
130
- return pa.year === pb.year && pa.month === pb.month && pa.day === pb.day;
131
- }
132
- function todayInTimezone(timeZone) {
133
- return startOfDayInTimezone((/* @__PURE__ */ new Date()).toISOString(), timeZone);
134
- }
135
- function civilMidnightFromUtcDay(gridUtcIso, timeZone) {
136
- const utc = (0, import_date_fns.parseISO)(gridUtcIso);
137
- const probe = new Date(
138
- Date.UTC(utc.getUTCFullYear(), utc.getUTCMonth(), utc.getUTCDate(), 12, 0, 0)
139
- ).toISOString();
140
- return startOfDayInTimezone(probe, timeZone);
141
- }
142
- function getTimeInTimezone(iso, timeZone) {
143
- const p = partsInTimezone((0, import_date_fns.parseISO)(iso), timeZone);
144
- return { hours: p.hour, minutes: p.minute, seconds: p.second };
145
- }
146
- function setTimeInTimezone(iso, partial, timeZone) {
147
- const p = partsInTimezone((0, import_date_fns.parseISO)(iso), timeZone);
148
- const targetHours = partial.hours ?? p.hour;
149
- const targetMinutes = partial.minutes ?? p.minute;
150
- const targetSeconds = partial.seconds ?? p.second;
151
- const civilEpoch = Date.UTC(
152
- p.year,
153
- p.month - 1,
154
- p.day,
155
- targetHours,
156
- targetMinutes,
157
- targetSeconds
158
- );
159
- const probe1 = new Date(civilEpoch).toISOString();
160
- const offset1 = getTimezoneOffsetMinutes(probe1, timeZone);
161
- const realEpoch1 = civilEpoch - offset1 * 6e4;
162
- const probe2 = new Date(realEpoch1).toISOString();
163
- const offset2 = getTimezoneOffsetMinutes(probe2, timeZone);
164
- const realEpoch2 = civilEpoch - offset2 * 6e4;
165
- return new Date(realEpoch2).toISOString();
166
- }
167
-
168
- // src/adapters/date-fns.ts
169
- function toDate(iso) {
170
- return (0, import_date_fns2.parseISO)(iso);
171
- }
172
- function toISO(date) {
173
- return date.toISOString();
174
- }
175
- function normalize(value) {
176
- if (/^\d{4}-\d{2}-\d{2}$/.test(value)) {
177
- return `${value}T00:00:00.000Z`;
178
- }
179
- return value;
180
- }
181
- function utcStartOfDay(d) {
182
- return new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate()));
183
- }
184
- function utcStartOfMonth(d) {
185
- return new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), 1));
186
- }
187
- function utcEndOfMonth(d) {
188
- return new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth() + 1, 0, 23, 59, 59, 999));
189
- }
190
- function utcStartOfWeek(d, weekStartsOn) {
191
- const day = d.getUTCDay();
192
- const diff = (day - weekStartsOn + 7) % 7;
193
- return new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate() - diff));
194
- }
195
- function utcEndOfWeek(d, weekStartsOn) {
196
- const start = utcStartOfWeek(d, weekStartsOn);
197
- return new Date(
198
- Date.UTC(start.getUTCFullYear(), start.getUTCMonth(), start.getUTCDate() + 6, 23, 59, 59, 999)
199
- );
200
- }
201
- var DateFnsAdapter = {
202
- parse(value) {
203
- if (!value) return "";
204
- return normalize(value);
205
- },
206
- format(iso, formatStr, timezone) {
207
- if (timezone) {
208
- return formatInTimezone(iso, formatStr, timezone);
209
- }
210
- const d = toDate(iso);
211
- const tokens = {
212
- yyyy: String(d.getUTCFullYear()),
213
- MM: String(d.getUTCMonth() + 1).padStart(2, "0"),
214
- dd: String(d.getUTCDate()).padStart(2, "0"),
215
- HH: String(d.getUTCHours()).padStart(2, "0"),
216
- mm: String(d.getUTCMinutes()).padStart(2, "0"),
217
- ss: String(d.getUTCSeconds()).padStart(2, "0"),
218
- M: String(d.getUTCMonth() + 1),
219
- d: String(d.getUTCDate())
220
- };
221
- let result = formatStr;
222
- for (const [token, value] of Object.entries(tokens).sort((a, b) => b[0].length - a[0].length)) {
223
- result = result.split(token).join(value);
224
- }
225
- return result;
226
- },
227
- addDays(iso, n) {
228
- return toISO((0, import_date_fns2.addDays)(toDate(iso), n));
229
- },
230
- addMonths(iso, n) {
231
- return toISO((0, import_date_fns2.addMonths)(toDate(iso), n));
232
- },
233
- addYears(iso, n) {
234
- return toISO((0, import_date_fns2.addYears)(toDate(iso), n));
235
- },
236
- isBefore(a, b) {
237
- return (0, import_date_fns2.isBefore)(toDate(a), toDate(b));
238
- },
239
- isAfter(a, b) {
240
- return (0, import_date_fns2.isAfter)(toDate(a), toDate(b));
241
- },
242
- isSameDay(a, b, timezone) {
243
- if (!a || !b) return false;
244
- if (timezone) {
245
- return isSameDayInTimezone(a, b, timezone);
246
- }
247
- const da = toDate(a);
248
- const db = toDate(b);
249
- return da.getUTCFullYear() === db.getUTCFullYear() && da.getUTCMonth() === db.getUTCMonth() && da.getUTCDate() === db.getUTCDate();
250
- },
251
- isSameMonth(a, b) {
252
- const da = toDate(a);
253
- const db = toDate(b);
254
- return da.getUTCFullYear() === db.getUTCFullYear() && da.getUTCMonth() === db.getUTCMonth();
255
- },
256
- startOfDay(iso, timezone) {
257
- if (timezone) {
258
- return startOfDayInTimezone(iso, timezone);
259
- }
260
- return toISO(utcStartOfDay(toDate(iso)));
261
- },
262
- startOfMonth(iso) {
263
- return toISO(utcStartOfMonth(toDate(iso)));
264
- },
265
- endOfMonth(iso) {
266
- return toISO(utcEndOfMonth(toDate(iso)));
267
- },
268
- startOfWeek(iso, weekStartsOn = 0) {
269
- return toISO(utcStartOfWeek(toDate(iso), weekStartsOn));
270
- },
271
- endOfWeek(iso, weekStartsOn = 0) {
272
- return toISO(utcEndOfWeek(toDate(iso), weekStartsOn));
273
- },
274
- now() {
275
- return (/* @__PURE__ */ new Date()).toISOString();
276
- },
277
- today(timezone) {
278
- if (timezone) {
279
- return todayInTimezone(timezone);
280
- }
281
- return toISO(utcStartOfDay(/* @__PURE__ */ new Date()));
282
- },
283
- isValid(value) {
284
- if (!value) return false;
285
- const normalized = normalize(value);
286
- const date = (0, import_date_fns2.parseISO)(normalized);
287
- return (0, import_date_fns2.isValid)(date);
288
- },
289
- getYear(iso) {
290
- return toDate(iso).getUTCFullYear();
291
- },
292
- getMonth(iso) {
293
- return toDate(iso).getUTCMonth();
294
- },
295
- getDate(iso) {
296
- return toDate(iso).getUTCDate();
297
- },
298
- getDay(iso) {
299
- return toDate(iso).getUTCDay();
300
- }
301
- };
302
-
303
59
  // src/utils/calendar.ts
304
60
  function getCalendarDays(monthISO, adapter, options = {}) {
305
61
  const {
@@ -477,12 +233,18 @@ function formatTimeString(time, withSeconds = false) {
477
233
  return `${hh}:${mm}`;
478
234
  }
479
235
  function to12Hour(hours24) {
236
+ if (!Number.isInteger(hours24) || hours24 < 0 || hours24 > 23) {
237
+ throw new RangeError(`[to12Hour] hours24 must be an integer in [0, 23], got ${hours24}`);
238
+ }
480
239
  const period = hours24 >= 12 ? "PM" : "AM";
481
240
  let hours12 = hours24 % 12;
482
241
  if (hours12 === 0) hours12 = 12;
483
242
  return { hours12, period };
484
243
  }
485
244
  function to24Hour(hours12, period) {
245
+ if (!Number.isInteger(hours12) || hours12 < 1 || hours12 > 12) {
246
+ throw new RangeError(`[to24Hour] hours12 must be an integer in [1, 12], got ${hours12}`);
247
+ }
486
248
  if (period === "AM") {
487
249
  return hours12 === 12 ? 0 : hours12;
488
250
  }
@@ -522,13 +284,13 @@ function formatTimeFromISO(iso, format) {
522
284
  }
523
285
 
524
286
  // src/utils/locale.ts
525
- var formatterCache2 = /* @__PURE__ */ new Map();
287
+ var formatterCache = /* @__PURE__ */ new Map();
526
288
  function getCachedFormatter(locale, options) {
527
289
  const key = `${locale}:${JSON.stringify(options)}`;
528
- let fmt = formatterCache2.get(key);
290
+ let fmt = formatterCache.get(key);
529
291
  if (!fmt) {
530
292
  fmt = new Intl.DateTimeFormat(locale, options);
531
- formatterCache2.set(key, fmt);
293
+ formatterCache.set(key, fmt);
532
294
  }
533
295
  return fmt;
534
296
  }
@@ -573,6 +335,110 @@ function formatFullDate(iso, locale = "en-US") {
573
335
  }).format(date);
574
336
  }
575
337
 
338
+ // src/utils/timezone.ts
339
+ var formatterCache2 = /* @__PURE__ */ new Map();
340
+ function getCachedPartsFormatter(timeZone) {
341
+ const key = `parts:${timeZone}`;
342
+ let fmt = formatterCache2.get(key);
343
+ if (!fmt) {
344
+ fmt = new Intl.DateTimeFormat("en-US", {
345
+ timeZone,
346
+ year: "numeric",
347
+ month: "2-digit",
348
+ day: "2-digit",
349
+ hour: "2-digit",
350
+ minute: "2-digit",
351
+ second: "2-digit",
352
+ hourCycle: "h23"
353
+ });
354
+ formatterCache2.set(key, fmt);
355
+ }
356
+ return fmt;
357
+ }
358
+ function partsInTimezone(utc, timeZone) {
359
+ const dtf = getCachedPartsFormatter(timeZone);
360
+ const parts = Object.fromEntries(dtf.formatToParts(utc).map((p) => [p.type, p.value]));
361
+ return {
362
+ year: Number(parts.year),
363
+ month: Number(parts.month),
364
+ day: Number(parts.day),
365
+ // Some locales/engines return '24' instead of '0' at midnight
366
+ hour: parts.hour === "24" ? 0 : Number(parts.hour),
367
+ minute: Number(parts.minute),
368
+ second: Number(parts.second)
369
+ };
370
+ }
371
+ function formatInTimezone(iso, formatStr, timeZone) {
372
+ const p = partsInTimezone(new Date(iso), timeZone);
373
+ const tokens = {
374
+ yyyy: String(p.year),
375
+ MM: String(p.month).padStart(2, "0"),
376
+ dd: String(p.day).padStart(2, "0"),
377
+ HH: String(p.hour).padStart(2, "0"),
378
+ mm: String(p.minute).padStart(2, "0"),
379
+ ss: String(p.second).padStart(2, "0")
380
+ };
381
+ let result = formatStr;
382
+ for (const [token, value] of Object.entries(tokens).sort((a, b) => b[0].length - a[0].length)) {
383
+ result = result.split(token).join(value);
384
+ }
385
+ return result;
386
+ }
387
+ function getTimezoneOffsetMinutes(iso, timeZone) {
388
+ const utc = new Date(iso);
389
+ const p = partsInTimezone(utc, timeZone);
390
+ const asUtcEpoch = Date.UTC(p.year, p.month - 1, p.day, p.hour, p.minute, p.second);
391
+ return Math.round((asUtcEpoch - utc.getTime()) / 6e4);
392
+ }
393
+ function startOfDayInTimezone(iso, timeZone) {
394
+ const utc = new Date(iso);
395
+ const p = partsInTimezone(utc, timeZone);
396
+ const civilMidnightUtc = Date.UTC(p.year, p.month - 1, p.day, 0, 0, 0);
397
+ const midnightProbe = new Date(civilMidnightUtc).toISOString();
398
+ const offsetMinutes = getTimezoneOffsetMinutes(midnightProbe, timeZone);
399
+ return new Date(civilMidnightUtc - offsetMinutes * 6e4).toISOString();
400
+ }
401
+ function isSameDayInTimezone(a, b, timeZone) {
402
+ const pa = partsInTimezone(new Date(a), timeZone);
403
+ const pb = partsInTimezone(new Date(b), timeZone);
404
+ return pa.year === pb.year && pa.month === pb.month && pa.day === pb.day;
405
+ }
406
+ function todayInTimezone(timeZone) {
407
+ return startOfDayInTimezone((/* @__PURE__ */ new Date()).toISOString(), timeZone);
408
+ }
409
+ function civilMidnightFromUtcDay(gridUtcIso, timeZone) {
410
+ const utc = new Date(gridUtcIso);
411
+ const probe = new Date(
412
+ Date.UTC(utc.getUTCFullYear(), utc.getUTCMonth(), utc.getUTCDate(), 12, 0, 0)
413
+ ).toISOString();
414
+ return startOfDayInTimezone(probe, timeZone);
415
+ }
416
+ function getTimeInTimezone(iso, timeZone) {
417
+ const p = partsInTimezone(new Date(iso), timeZone);
418
+ return { hours: p.hour, minutes: p.minute, seconds: p.second };
419
+ }
420
+ function setTimeInTimezone(iso, partial, timeZone) {
421
+ const p = partsInTimezone(new Date(iso), timeZone);
422
+ const targetHours = partial.hours ?? p.hour;
423
+ const targetMinutes = partial.minutes ?? p.minute;
424
+ const targetSeconds = partial.seconds ?? p.second;
425
+ const civilEpoch = Date.UTC(
426
+ p.year,
427
+ p.month - 1,
428
+ p.day,
429
+ targetHours,
430
+ targetMinutes,
431
+ targetSeconds
432
+ );
433
+ const probe1 = new Date(civilEpoch).toISOString();
434
+ const offset1 = getTimezoneOffsetMinutes(probe1, timeZone);
435
+ const realEpoch1 = civilEpoch - offset1 * 6e4;
436
+ const probe2 = new Date(realEpoch1).toISOString();
437
+ const offset2 = getTimezoneOffsetMinutes(probe2, timeZone);
438
+ const realEpoch2 = civilEpoch - offset2 * 6e4;
439
+ return new Date(realEpoch2).toISOString();
440
+ }
441
+
576
442
  // src/utils/labels.ts
577
443
  var DEFAULT_DATEPICKER_LABELS = {
578
444
  triggerOpen: "Open calendar",
@@ -613,7 +479,6 @@ var DEFAULT_DATETIMEPICKER_LABELS = {
613
479
  DEFAULT_DATETIMEPICKER_LABELS,
614
480
  DEFAULT_RANGEPICKER_LABELS,
615
481
  DEFAULT_TIMEPICKER_LABELS,
616
- DateFnsAdapter,
617
482
  civilMidnightFromUtcDay,
618
483
  formatFullDate,
619
484
  formatInTimezone,
package/dist/index.d.cts CHANGED
@@ -119,21 +119,6 @@ interface CalendarOptions {
119
119
  fixedWeeks?: boolean;
120
120
  }
121
121
 
122
- /**
123
- * DateAdapter implementation backed by date-fns.
124
- * All operations run in UTC to avoid timezone interference.
125
- *
126
- * @example
127
- * ```ts
128
- * import { DateFnsAdapter } from '@kalyx/core';
129
- *
130
- * DateFnsAdapter.format('2026-01-15T00:00:00.000Z', 'yyyy-MM-dd'); // "2026-01-15"
131
- * DateFnsAdapter.addDays('2026-01-15T00:00:00.000Z', 7); // "2026-01-22T..."
132
- * DateFnsAdapter.isSameDay('2026-01-15T00:00:00.000Z', '2026-01-15T23:59:59.000Z'); // true
133
- * ```
134
- */
135
- declare const DateFnsAdapter: DateAdapter;
136
-
137
122
  /**
138
123
  * Builds the calendar grid for the given month.
139
124
  * Returns a 2D array (`CalendarGrid`) organized by week.
@@ -218,7 +203,13 @@ declare function parseTimeString(input: string): TimeValue | null;
218
203
  declare function formatTimeString(time: TimeValue, withSeconds?: boolean): string;
219
204
  /**
220
205
  * Converts a 24-hour value to 12-hour form.
221
- * 0 → 12 AM, 12 → 12 PM
206
+ *
207
+ * - `0` → `{ hours12: 12, period: 'AM' }` (midnight)
208
+ * - `12` → `{ hours12: 12, period: 'PM' }` (noon)
209
+ *
210
+ * Throws if `hours24` isn't an integer in `[0, 23]`. The previous silent
211
+ * modulo behaviour mapped invalid inputs (e.g. `24`, `25`, `-1`) onto
212
+ * arbitrary valid-looking outputs, which masked caller bugs.
222
213
  */
223
214
  declare function to12Hour(hours24: number): {
224
215
  hours12: number;
@@ -226,6 +217,10 @@ declare function to12Hour(hours24: number): {
226
217
  };
227
218
  /**
228
219
  * Converts a 12-hour value to 24-hour form.
220
+ *
221
+ * Throws if `hours12` isn't an integer in `[1, 12]`. The previous silent
222
+ * arithmetic produced out-of-range outputs (e.g. `to24Hour(13, 'PM')` → `25`).
223
+ * `period` is constrained at the type level to `'AM' | 'PM'`.
229
224
  */
230
225
  declare function to24Hour(hours12: number, period: 'AM' | 'PM'): number;
231
226
  /**
@@ -432,4 +427,4 @@ declare const DEFAULT_RANGEPICKER_LABELS: RangePickerLabels;
432
427
  declare const DEFAULT_TIMEPICKER_LABELS: TimePickerLabels;
433
428
  declare const DEFAULT_DATETIMEPICKER_LABELS: DateTimePickerLabels;
434
429
 
435
- 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, getISOWeekNumber, getMonthName, getTime, getTimeInTimezone, getTimezoneOffsetMinutes, getWeekdayNames, isDateDisabled, isSameDayInTimezone, isSameTime, maxDate, minDate, normalizeISO, parseInputValue, parseTimeString, setTime, setTimeInTimezone, startOfDayInTimezone, to12Hour, to24Hour, todayInTimezone };
430
+ export { type CalendarDay, type CalendarGrid, type CalendarOptions, type CalendarWeek, DEFAULT_DATEPICKER_LABELS, DEFAULT_DATETIMEPICKER_LABELS, DEFAULT_RANGEPICKER_LABELS, DEFAULT_TIMEPICKER_LABELS, type DateAdapter, 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, getISOWeekNumber, 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
@@ -119,21 +119,6 @@ interface CalendarOptions {
119
119
  fixedWeeks?: boolean;
120
120
  }
121
121
 
122
- /**
123
- * DateAdapter implementation backed by date-fns.
124
- * All operations run in UTC to avoid timezone interference.
125
- *
126
- * @example
127
- * ```ts
128
- * import { DateFnsAdapter } from '@kalyx/core';
129
- *
130
- * DateFnsAdapter.format('2026-01-15T00:00:00.000Z', 'yyyy-MM-dd'); // "2026-01-15"
131
- * DateFnsAdapter.addDays('2026-01-15T00:00:00.000Z', 7); // "2026-01-22T..."
132
- * DateFnsAdapter.isSameDay('2026-01-15T00:00:00.000Z', '2026-01-15T23:59:59.000Z'); // true
133
- * ```
134
- */
135
- declare const DateFnsAdapter: DateAdapter;
136
-
137
122
  /**
138
123
  * Builds the calendar grid for the given month.
139
124
  * Returns a 2D array (`CalendarGrid`) organized by week.
@@ -218,7 +203,13 @@ declare function parseTimeString(input: string): TimeValue | null;
218
203
  declare function formatTimeString(time: TimeValue, withSeconds?: boolean): string;
219
204
  /**
220
205
  * Converts a 24-hour value to 12-hour form.
221
- * 0 → 12 AM, 12 → 12 PM
206
+ *
207
+ * - `0` → `{ hours12: 12, period: 'AM' }` (midnight)
208
+ * - `12` → `{ hours12: 12, period: 'PM' }` (noon)
209
+ *
210
+ * Throws if `hours24` isn't an integer in `[0, 23]`. The previous silent
211
+ * modulo behaviour mapped invalid inputs (e.g. `24`, `25`, `-1`) onto
212
+ * arbitrary valid-looking outputs, which masked caller bugs.
222
213
  */
223
214
  declare function to12Hour(hours24: number): {
224
215
  hours12: number;
@@ -226,6 +217,10 @@ declare function to12Hour(hours24: number): {
226
217
  };
227
218
  /**
228
219
  * Converts a 12-hour value to 24-hour form.
220
+ *
221
+ * Throws if `hours12` isn't an integer in `[1, 12]`. The previous silent
222
+ * arithmetic produced out-of-range outputs (e.g. `to24Hour(13, 'PM')` → `25`).
223
+ * `period` is constrained at the type level to `'AM' | 'PM'`.
229
224
  */
230
225
  declare function to24Hour(hours12: number, period: 'AM' | 'PM'): number;
231
226
  /**
@@ -432,4 +427,4 @@ declare const DEFAULT_RANGEPICKER_LABELS: RangePickerLabels;
432
427
  declare const DEFAULT_TIMEPICKER_LABELS: TimePickerLabels;
433
428
  declare const DEFAULT_DATETIMEPICKER_LABELS: DateTimePickerLabels;
434
429
 
435
- 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, getISOWeekNumber, getMonthName, getTime, getTimeInTimezone, getTimezoneOffsetMinutes, getWeekdayNames, isDateDisabled, isSameDayInTimezone, isSameTime, maxDate, minDate, normalizeISO, parseInputValue, parseTimeString, setTime, setTimeInTimezone, startOfDayInTimezone, to12Hour, to24Hour, todayInTimezone };
430
+ export { type CalendarDay, type CalendarGrid, type CalendarOptions, type CalendarWeek, DEFAULT_DATEPICKER_LABELS, DEFAULT_DATETIMEPICKER_LABELS, DEFAULT_RANGEPICKER_LABELS, DEFAULT_TIMEPICKER_LABELS, type DateAdapter, 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, getISOWeekNumber, 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,254 +1,3 @@
1
- // src/adapters/date-fns.ts
2
- import {
3
- parseISO as parseISO2,
4
- addDays as dfAddDays,
5
- addMonths as dfAddMonths,
6
- addYears as dfAddYears,
7
- isBefore as dfIsBefore,
8
- isAfter as dfIsAfter,
9
- isValid as dfIsValid
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(dtf.formatToParts(utc).map((p) => [p.type, p.value]));
36
- return {
37
- year: Number(parts.year),
38
- month: Number(parts.month),
39
- day: Number(parts.day),
40
- // Some locales/engines return '24' instead of '0' at midnight
41
- hour: parts.hour === "24" ? 0 : Number(parts.hour),
42
- minute: Number(parts.minute),
43
- second: Number(parts.second)
44
- };
45
- }
46
- function formatInTimezone(iso, formatStr, timeZone) {
47
- const p = partsInTimezone(parseISO(iso), timeZone);
48
- const tokens = {
49
- yyyy: String(p.year),
50
- MM: String(p.month).padStart(2, "0"),
51
- dd: String(p.day).padStart(2, "0"),
52
- HH: String(p.hour).padStart(2, "0"),
53
- mm: String(p.minute).padStart(2, "0"),
54
- ss: String(p.second).padStart(2, "0")
55
- };
56
- let result = formatStr;
57
- for (const [token, value] of Object.entries(tokens).sort((a, b) => b[0].length - a[0].length)) {
58
- result = result.split(token).join(value);
59
- }
60
- return result;
61
- }
62
- function getTimezoneOffsetMinutes(iso, timeZone) {
63
- const utc = parseISO(iso);
64
- const p = partsInTimezone(utc, timeZone);
65
- const asUtcEpoch = Date.UTC(p.year, p.month - 1, p.day, p.hour, p.minute, p.second);
66
- return Math.round((asUtcEpoch - utc.getTime()) / 6e4);
67
- }
68
- function startOfDayInTimezone(iso, timeZone) {
69
- const utc = parseISO(iso);
70
- const p = partsInTimezone(utc, timeZone);
71
- const civilMidnightUtc = Date.UTC(p.year, p.month - 1, p.day, 0, 0, 0);
72
- const midnightProbe = new Date(civilMidnightUtc).toISOString();
73
- const offsetMinutes = getTimezoneOffsetMinutes(midnightProbe, timeZone);
74
- return new Date(civilMidnightUtc - offsetMinutes * 6e4).toISOString();
75
- }
76
- function isSameDayInTimezone(a, b, timeZone) {
77
- const pa = partsInTimezone(parseISO(a), timeZone);
78
- const pb = partsInTimezone(parseISO(b), timeZone);
79
- return pa.year === pb.year && pa.month === pb.month && pa.day === pb.day;
80
- }
81
- function todayInTimezone(timeZone) {
82
- return startOfDayInTimezone((/* @__PURE__ */ new Date()).toISOString(), timeZone);
83
- }
84
- function civilMidnightFromUtcDay(gridUtcIso, timeZone) {
85
- const utc = parseISO(gridUtcIso);
86
- const probe = new Date(
87
- Date.UTC(utc.getUTCFullYear(), utc.getUTCMonth(), utc.getUTCDate(), 12, 0, 0)
88
- ).toISOString();
89
- return startOfDayInTimezone(probe, timeZone);
90
- }
91
- function getTimeInTimezone(iso, timeZone) {
92
- const p = partsInTimezone(parseISO(iso), timeZone);
93
- return { hours: p.hour, minutes: p.minute, seconds: p.second };
94
- }
95
- function setTimeInTimezone(iso, partial, timeZone) {
96
- const p = partsInTimezone(parseISO(iso), timeZone);
97
- const targetHours = partial.hours ?? p.hour;
98
- const targetMinutes = partial.minutes ?? p.minute;
99
- const targetSeconds = partial.seconds ?? p.second;
100
- const civilEpoch = Date.UTC(
101
- p.year,
102
- p.month - 1,
103
- p.day,
104
- targetHours,
105
- targetMinutes,
106
- targetSeconds
107
- );
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
118
- function toDate(iso) {
119
- return parseISO2(iso);
120
- }
121
- function toISO(date) {
122
- return date.toISOString();
123
- }
124
- function normalize(value) {
125
- if (/^\d{4}-\d{2}-\d{2}$/.test(value)) {
126
- return `${value}T00:00:00.000Z`;
127
- }
128
- return value;
129
- }
130
- function utcStartOfDay(d) {
131
- return new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate()));
132
- }
133
- function utcStartOfMonth(d) {
134
- return new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), 1));
135
- }
136
- function utcEndOfMonth(d) {
137
- return new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth() + 1, 0, 23, 59, 59, 999));
138
- }
139
- function utcStartOfWeek(d, weekStartsOn) {
140
- const day = d.getUTCDay();
141
- const diff = (day - weekStartsOn + 7) % 7;
142
- return new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate() - diff));
143
- }
144
- function utcEndOfWeek(d, weekStartsOn) {
145
- const start = utcStartOfWeek(d, weekStartsOn);
146
- return new Date(
147
- Date.UTC(start.getUTCFullYear(), start.getUTCMonth(), start.getUTCDate() + 6, 23, 59, 59, 999)
148
- );
149
- }
150
- var DateFnsAdapter = {
151
- parse(value) {
152
- if (!value) return "";
153
- return normalize(value);
154
- },
155
- format(iso, formatStr, timezone) {
156
- if (timezone) {
157
- return formatInTimezone(iso, formatStr, timezone);
158
- }
159
- const d = toDate(iso);
160
- const tokens = {
161
- yyyy: String(d.getUTCFullYear()),
162
- MM: String(d.getUTCMonth() + 1).padStart(2, "0"),
163
- dd: String(d.getUTCDate()).padStart(2, "0"),
164
- HH: String(d.getUTCHours()).padStart(2, "0"),
165
- mm: String(d.getUTCMinutes()).padStart(2, "0"),
166
- ss: String(d.getUTCSeconds()).padStart(2, "0"),
167
- M: String(d.getUTCMonth() + 1),
168
- d: String(d.getUTCDate())
169
- };
170
- let result = formatStr;
171
- for (const [token, value] of Object.entries(tokens).sort((a, b) => b[0].length - a[0].length)) {
172
- result = result.split(token).join(value);
173
- }
174
- return result;
175
- },
176
- addDays(iso, n) {
177
- return toISO(dfAddDays(toDate(iso), n));
178
- },
179
- addMonths(iso, n) {
180
- return toISO(dfAddMonths(toDate(iso), n));
181
- },
182
- addYears(iso, n) {
183
- return toISO(dfAddYears(toDate(iso), n));
184
- },
185
- isBefore(a, b) {
186
- return dfIsBefore(toDate(a), toDate(b));
187
- },
188
- isAfter(a, b) {
189
- return dfIsAfter(toDate(a), toDate(b));
190
- },
191
- isSameDay(a, b, timezone) {
192
- if (!a || !b) return false;
193
- if (timezone) {
194
- return isSameDayInTimezone(a, b, timezone);
195
- }
196
- const da = toDate(a);
197
- const db = toDate(b);
198
- return da.getUTCFullYear() === db.getUTCFullYear() && da.getUTCMonth() === db.getUTCMonth() && da.getUTCDate() === db.getUTCDate();
199
- },
200
- isSameMonth(a, b) {
201
- const da = toDate(a);
202
- const db = toDate(b);
203
- return da.getUTCFullYear() === db.getUTCFullYear() && da.getUTCMonth() === db.getUTCMonth();
204
- },
205
- startOfDay(iso, timezone) {
206
- if (timezone) {
207
- return startOfDayInTimezone(iso, timezone);
208
- }
209
- return toISO(utcStartOfDay(toDate(iso)));
210
- },
211
- startOfMonth(iso) {
212
- return toISO(utcStartOfMonth(toDate(iso)));
213
- },
214
- endOfMonth(iso) {
215
- return toISO(utcEndOfMonth(toDate(iso)));
216
- },
217
- startOfWeek(iso, weekStartsOn = 0) {
218
- return toISO(utcStartOfWeek(toDate(iso), weekStartsOn));
219
- },
220
- endOfWeek(iso, weekStartsOn = 0) {
221
- return toISO(utcEndOfWeek(toDate(iso), weekStartsOn));
222
- },
223
- now() {
224
- return (/* @__PURE__ */ new Date()).toISOString();
225
- },
226
- today(timezone) {
227
- if (timezone) {
228
- return todayInTimezone(timezone);
229
- }
230
- return toISO(utcStartOfDay(/* @__PURE__ */ new Date()));
231
- },
232
- isValid(value) {
233
- if (!value) return false;
234
- const normalized = normalize(value);
235
- const date = parseISO2(normalized);
236
- return dfIsValid(date);
237
- },
238
- getYear(iso) {
239
- return toDate(iso).getUTCFullYear();
240
- },
241
- getMonth(iso) {
242
- return toDate(iso).getUTCMonth();
243
- },
244
- getDate(iso) {
245
- return toDate(iso).getUTCDate();
246
- },
247
- getDay(iso) {
248
- return toDate(iso).getUTCDay();
249
- }
250
- };
251
-
252
1
  // src/utils/calendar.ts
253
2
  function getCalendarDays(monthISO, adapter, options = {}) {
254
3
  const {
@@ -426,12 +175,18 @@ function formatTimeString(time, withSeconds = false) {
426
175
  return `${hh}:${mm}`;
427
176
  }
428
177
  function to12Hour(hours24) {
178
+ if (!Number.isInteger(hours24) || hours24 < 0 || hours24 > 23) {
179
+ throw new RangeError(`[to12Hour] hours24 must be an integer in [0, 23], got ${hours24}`);
180
+ }
429
181
  const period = hours24 >= 12 ? "PM" : "AM";
430
182
  let hours12 = hours24 % 12;
431
183
  if (hours12 === 0) hours12 = 12;
432
184
  return { hours12, period };
433
185
  }
434
186
  function to24Hour(hours12, period) {
187
+ if (!Number.isInteger(hours12) || hours12 < 1 || hours12 > 12) {
188
+ throw new RangeError(`[to24Hour] hours12 must be an integer in [1, 12], got ${hours12}`);
189
+ }
435
190
  if (period === "AM") {
436
191
  return hours12 === 12 ? 0 : hours12;
437
192
  }
@@ -471,13 +226,13 @@ function formatTimeFromISO(iso, format) {
471
226
  }
472
227
 
473
228
  // src/utils/locale.ts
474
- var formatterCache2 = /* @__PURE__ */ new Map();
229
+ var formatterCache = /* @__PURE__ */ new Map();
475
230
  function getCachedFormatter(locale, options) {
476
231
  const key = `${locale}:${JSON.stringify(options)}`;
477
- let fmt = formatterCache2.get(key);
232
+ let fmt = formatterCache.get(key);
478
233
  if (!fmt) {
479
234
  fmt = new Intl.DateTimeFormat(locale, options);
480
- formatterCache2.set(key, fmt);
235
+ formatterCache.set(key, fmt);
481
236
  }
482
237
  return fmt;
483
238
  }
@@ -522,6 +277,110 @@ function formatFullDate(iso, locale = "en-US") {
522
277
  }).format(date);
523
278
  }
524
279
 
280
+ // src/utils/timezone.ts
281
+ var formatterCache2 = /* @__PURE__ */ new Map();
282
+ function getCachedPartsFormatter(timeZone) {
283
+ const key = `parts:${timeZone}`;
284
+ let fmt = formatterCache2.get(key);
285
+ if (!fmt) {
286
+ fmt = new Intl.DateTimeFormat("en-US", {
287
+ timeZone,
288
+ year: "numeric",
289
+ month: "2-digit",
290
+ day: "2-digit",
291
+ hour: "2-digit",
292
+ minute: "2-digit",
293
+ second: "2-digit",
294
+ hourCycle: "h23"
295
+ });
296
+ formatterCache2.set(key, fmt);
297
+ }
298
+ return fmt;
299
+ }
300
+ function partsInTimezone(utc, timeZone) {
301
+ const dtf = getCachedPartsFormatter(timeZone);
302
+ const parts = Object.fromEntries(dtf.formatToParts(utc).map((p) => [p.type, p.value]));
303
+ return {
304
+ year: Number(parts.year),
305
+ month: Number(parts.month),
306
+ day: Number(parts.day),
307
+ // Some locales/engines return '24' instead of '0' at midnight
308
+ hour: parts.hour === "24" ? 0 : Number(parts.hour),
309
+ minute: Number(parts.minute),
310
+ second: Number(parts.second)
311
+ };
312
+ }
313
+ function formatInTimezone(iso, formatStr, timeZone) {
314
+ const p = partsInTimezone(new Date(iso), timeZone);
315
+ const tokens = {
316
+ yyyy: String(p.year),
317
+ MM: String(p.month).padStart(2, "0"),
318
+ dd: String(p.day).padStart(2, "0"),
319
+ HH: String(p.hour).padStart(2, "0"),
320
+ mm: String(p.minute).padStart(2, "0"),
321
+ ss: String(p.second).padStart(2, "0")
322
+ };
323
+ let result = formatStr;
324
+ for (const [token, value] of Object.entries(tokens).sort((a, b) => b[0].length - a[0].length)) {
325
+ result = result.split(token).join(value);
326
+ }
327
+ return result;
328
+ }
329
+ function getTimezoneOffsetMinutes(iso, timeZone) {
330
+ const utc = new Date(iso);
331
+ const p = partsInTimezone(utc, timeZone);
332
+ const asUtcEpoch = Date.UTC(p.year, p.month - 1, p.day, p.hour, p.minute, p.second);
333
+ return Math.round((asUtcEpoch - utc.getTime()) / 6e4);
334
+ }
335
+ function startOfDayInTimezone(iso, timeZone) {
336
+ const utc = new Date(iso);
337
+ const p = partsInTimezone(utc, timeZone);
338
+ const civilMidnightUtc = Date.UTC(p.year, p.month - 1, p.day, 0, 0, 0);
339
+ const midnightProbe = new Date(civilMidnightUtc).toISOString();
340
+ const offsetMinutes = getTimezoneOffsetMinutes(midnightProbe, timeZone);
341
+ return new Date(civilMidnightUtc - offsetMinutes * 6e4).toISOString();
342
+ }
343
+ function isSameDayInTimezone(a, b, timeZone) {
344
+ const pa = partsInTimezone(new Date(a), timeZone);
345
+ const pb = partsInTimezone(new Date(b), timeZone);
346
+ return pa.year === pb.year && pa.month === pb.month && pa.day === pb.day;
347
+ }
348
+ function todayInTimezone(timeZone) {
349
+ return startOfDayInTimezone((/* @__PURE__ */ new Date()).toISOString(), timeZone);
350
+ }
351
+ function civilMidnightFromUtcDay(gridUtcIso, timeZone) {
352
+ const utc = new Date(gridUtcIso);
353
+ const probe = new Date(
354
+ Date.UTC(utc.getUTCFullYear(), utc.getUTCMonth(), utc.getUTCDate(), 12, 0, 0)
355
+ ).toISOString();
356
+ return startOfDayInTimezone(probe, timeZone);
357
+ }
358
+ function getTimeInTimezone(iso, timeZone) {
359
+ const p = partsInTimezone(new Date(iso), timeZone);
360
+ return { hours: p.hour, minutes: p.minute, seconds: p.second };
361
+ }
362
+ function setTimeInTimezone(iso, partial, timeZone) {
363
+ const p = partsInTimezone(new Date(iso), timeZone);
364
+ const targetHours = partial.hours ?? p.hour;
365
+ const targetMinutes = partial.minutes ?? p.minute;
366
+ const targetSeconds = partial.seconds ?? p.second;
367
+ const civilEpoch = Date.UTC(
368
+ p.year,
369
+ p.month - 1,
370
+ p.day,
371
+ targetHours,
372
+ targetMinutes,
373
+ targetSeconds
374
+ );
375
+ const probe1 = new Date(civilEpoch).toISOString();
376
+ const offset1 = getTimezoneOffsetMinutes(probe1, timeZone);
377
+ const realEpoch1 = civilEpoch - offset1 * 6e4;
378
+ const probe2 = new Date(realEpoch1).toISOString();
379
+ const offset2 = getTimezoneOffsetMinutes(probe2, timeZone);
380
+ const realEpoch2 = civilEpoch - offset2 * 6e4;
381
+ return new Date(realEpoch2).toISOString();
382
+ }
383
+
525
384
  // src/utils/labels.ts
526
385
  var DEFAULT_DATEPICKER_LABELS = {
527
386
  triggerOpen: "Open calendar",
@@ -561,7 +420,6 @@ export {
561
420
  DEFAULT_DATETIMEPICKER_LABELS,
562
421
  DEFAULT_RANGEPICKER_LABELS,
563
422
  DEFAULT_TIMEPICKER_LABELS,
564
- DateFnsAdapter,
565
423
  civilMidnightFromUtcDay,
566
424
  formatFullDate,
567
425
  formatInTimezone,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kalyx/core",
3
- "version": "1.0.0-rc.11",
3
+ "version": "1.0.0-rc.13",
4
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",
@@ -22,7 +22,7 @@
22
22
  "dst",
23
23
  "iso8601",
24
24
  "utc",
25
- "date-fns"
25
+ "adapter"
26
26
  ],
27
27
  "type": "module",
28
28
  "sideEffects": false,
@@ -46,9 +46,8 @@
46
46
  "CHANGELOG.md",
47
47
  "LICENSE"
48
48
  ],
49
- "dependencies": {
50
- "date-fns": "^4.0.0",
51
- "date-fns-tz": "^3.0.0"
49
+ "devDependencies": {
50
+ "@kalyx/adapter-date-fns": "1.0.0-rc.1"
52
51
  },
53
52
  "engines": {
54
53
  "node": ">=20.0.0"