@uxf/datepicker 11.111.2 → 11.113.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.
Files changed (55) hide show
  1. package/hooks/use-date-picker-navigation.d.ts +4 -3
  2. package/hooks/use-date-picker-navigation.js +30 -34
  3. package/hooks/use-date-picker-navigation.test.js +40 -4
  4. package/hooks/use-date-picker.js +40 -26
  5. package/hooks/use-date-picker.test.js +2 -2
  6. package/hooks/use-date-range-picker.js +103 -76
  7. package/hooks/use-day.js +12 -11
  8. package/hooks/use-decade.js +9 -8
  9. package/jest.dst.config.js +22 -0
  10. package/jest.dst.global-setup.js +14 -0
  11. package/package.json +7 -2
  12. package/utils/can-select-range.d.ts +6 -5
  13. package/utils/can-select-range.js +7 -19
  14. package/utils/can-select-range.test.js +30 -27
  15. package/utils/date-string.d.ts +34 -0
  16. package/utils/date-string.js +64 -0
  17. package/utils/date-string.test.d.ts +1 -0
  18. package/utils/date-string.test.js +63 -0
  19. package/utils/dayjs-utils.d.ts +0 -1
  20. package/utils/dayjs-utils.js +1 -5
  21. package/utils/dst-robustness.test.d.ts +11 -0
  22. package/utils/dst-robustness.test.js +328 -0
  23. package/utils/get-current-year-month-and-date.js +1 -2
  24. package/utils/get-date-invervals.js +2 -5
  25. package/utils/get-date-month-and-year.js +6 -5
  26. package/utils/get-days.js +12 -9
  27. package/utils/get-each.d.ts +2 -1
  28. package/utils/get-each.js +37 -9
  29. package/utils/get-each.test.js +34 -19
  30. package/utils/get-initial-months.js +3 -6
  31. package/utils/get-months.js +7 -7
  32. package/utils/get-next-active-month.js +6 -10
  33. package/utils/get-weekday-labels.js +4 -2
  34. package/utils/get-years.js +5 -4
  35. package/utils/is-date-blocked.d.ts +7 -6
  36. package/utils/is-date-blocked.js +6 -7
  37. package/utils/is-date-blocked.test.js +108 -11
  38. package/utils/is-date-hovered.d.ts +6 -5
  39. package/utils/is-date-hovered.js +17 -24
  40. package/utils/is-date-hovered.test.js +42 -25
  41. package/utils/is-date-inside-range.d.ts +2 -1
  42. package/utils/is-date-inside-range.js +1 -2
  43. package/utils/is-date-selected.test.js +16 -8
  44. package/utils/is-end-date.d.ts +2 -1
  45. package/utils/is-end-date.js +1 -2
  46. package/utils/is-end-date.test.js +11 -6
  47. package/utils/is-first-or-last-selected-date.d.ts +2 -1
  48. package/utils/is-first-or-last-selected-date.js +1 -3
  49. package/utils/is-first-or-last-selected-date.test.js +9 -6
  50. package/utils/is-in-unavailable-dates.d.ts +2 -1
  51. package/utils/is-in-unavailable-dates.js +1 -2
  52. package/utils/is-in-unavailable-dates.test.js +10 -6
  53. package/utils/is-start-date.d.ts +2 -1
  54. package/utils/is-start-date.js +1 -2
  55. package/utils/is-start-date.test.js +12 -6
@@ -0,0 +1,328 @@
1
+ "use strict";
2
+ /**
3
+ * DST robustness tests. Utility functions now operate on DateString, so they are
4
+ * inherently timezone-proof. These tests verify string-based comparison correctness
5
+ * across DST boundaries and large UTC offsets.
6
+ *
7
+ * Run with:
8
+ * npm run test:tz:prague -w @uxf/datepicker
9
+ * npm run test:tz:ny -w @uxf/datepicker
10
+ * npm run test:tz:tokyo -w @uxf/datepicker
11
+ */
12
+ Object.defineProperty(exports, "__esModule", { value: true });
13
+ const can_select_range_1 = require("./can-select-range");
14
+ const date_string_1 = require("./date-string");
15
+ const get_current_year_month_and_date_1 = require("./get-current-year-month-and-date");
16
+ const get_days_1 = require("./get-days");
17
+ const get_weekday_labels_1 = require("./get-weekday-labels");
18
+ const get_years_1 = require("./get-years");
19
+ const is_date_blocked_1 = require("./is-date-blocked");
20
+ const is_date_hovered_1 = require("./is-date-hovered");
21
+ const is_date_inside_range_1 = require("./is-date-inside-range");
22
+ const is_end_date_1 = require("./is-end-date");
23
+ const is_first_or_last_selected_date_1 = require("./is-first-or-last-selected-date");
24
+ const is_in_unavailable_dates_1 = require("./is-in-unavailable-dates");
25
+ const is_start_date_1 = require("./is-start-date");
26
+ // Oct 25 2026 = DST fall-back in Europe/Prague (25h day, clocks go back)
27
+ // Mar 29 2026 = DST spring-forward in Europe/Prague (23h day, clocks go forward)
28
+ // Asia/Tokyo = UTC+9, no DST — local midnight is UTC previous-day 15:00 (extreme offset)
29
+ describe("toDateString: local constructor vs ISO string are equivalent (any timezone)", () => {
30
+ test("DST fall-back day: new Date(2026, 9, 25) === new Date('2026-10-25')", () => {
31
+ // new Date(2026, 9, 25) = local midnight — different UTC timestamp than ISO string in UTC+ zones
32
+ expect((0, date_string_1.toDateString)(new Date(2026, 9, 25))).toBe("2026-10-25");
33
+ expect((0, date_string_1.toDateString)(new Date("2026-10-25"))).toBe("2026-10-25");
34
+ expect((0, date_string_1.toDateString)(new Date(2026, 9, 25))).toBe((0, date_string_1.toDateString)(new Date("2026-10-25")));
35
+ });
36
+ test("DST spring-forward day: new Date(2026, 2, 29) === new Date('2026-03-29')", () => {
37
+ expect((0, date_string_1.toDateString)(new Date(2026, 2, 29))).toBe("2026-03-29");
38
+ expect((0, date_string_1.toDateString)(new Date(2026, 2, 29))).toBe((0, date_string_1.toDateString)(new Date("2026-03-29")));
39
+ });
40
+ test("year-end: new Date(2025, 11, 31) === new Date('2025-12-31')", () => {
41
+ expect((0, date_string_1.toDateString)(new Date(2025, 11, 31))).toBe("2025-12-31");
42
+ expect((0, date_string_1.toDateString)(new Date(2025, 11, 31))).toBe((0, date_string_1.toDateString)(new Date("2025-12-31")));
43
+ });
44
+ test("year-start: new Date(2026, 0, 1) === new Date('2026-01-01')", () => {
45
+ expect((0, date_string_1.toDateString)(new Date(2026, 0, 1))).toBe("2026-01-01");
46
+ expect((0, date_string_1.toDateString)(new Date(2026, 0, 1))).toBe((0, date_string_1.toDateString)(new Date("2026-01-01")));
47
+ });
48
+ });
49
+ describe("DST fall-back Oct 25 2026 (Europe/Prague)", () => {
50
+ test("isStartDate: date on DST boundary matches", () => {
51
+ expect((0, is_start_date_1.isStartDate)("2026-10-25", "2026-10-25")).toBe(true);
52
+ expect((0, is_start_date_1.isStartDate)("2026-10-24", "2026-10-25")).toBe(false);
53
+ expect((0, is_start_date_1.isStartDate)("2026-10-26", "2026-10-25")).toBe(false);
54
+ });
55
+ test("isDateInsideRange: date on DST fall-back boundary is inside range", () => {
56
+ expect((0, is_date_inside_range_1.isDateInsideRange)("2026-10-25", "2026-10-24", "2026-10-26")).toBe(true);
57
+ });
58
+ test("isInUnavailableDates: date on DST boundary found in list", () => {
59
+ expect((0, is_in_unavailable_dates_1.isInUnavailableDates)(["2026-10-25"], "2026-10-25")).toBe(true);
60
+ expect((0, is_in_unavailable_dates_1.isInUnavailableDates)(["2026-10-24"], "2026-10-25")).toBe(false);
61
+ });
62
+ test("canSelectRange: 2-day range across DST fall-back is valid", () => {
63
+ expect((0, can_select_range_1.canSelectRange)({
64
+ startDate: "2026-10-25",
65
+ endDate: "2026-10-26",
66
+ minBookingDays: 2,
67
+ minBookingDate: undefined,
68
+ maxBookingDate: undefined,
69
+ exactMinBookingDays: false,
70
+ isDateBlocked: () => false,
71
+ })).toBe(true);
72
+ });
73
+ test("isDateBlocked: date on DST boundary not blocked when minBookingDate is same day", () => {
74
+ expect((0, is_date_blocked_1.isDateBlocked)({
75
+ date: "2026-10-25",
76
+ startDate: null,
77
+ endDate: null,
78
+ minBookingDate: "2026-10-25",
79
+ })).toBe(false);
80
+ });
81
+ test("isEndDate: date on DST boundary matches endDate", () => {
82
+ expect((0, is_end_date_1.isEndDate)("2026-10-25", "2026-10-25")).toBe(true);
83
+ expect((0, is_end_date_1.isEndDate)("2026-10-24", "2026-10-25")).toBe(false);
84
+ });
85
+ test("isFirstOrLastSelectedDate: DST boundary date matches start or end", () => {
86
+ expect((0, is_first_or_last_selected_date_1.isFirstOrLastSelectedDate)("2026-10-25", "2026-10-25", "2026-10-28")).toBe(true);
87
+ expect((0, is_first_or_last_selected_date_1.isFirstOrLastSelectedDate)("2026-10-28", "2026-10-25", "2026-10-28")).toBe(true);
88
+ });
89
+ test("isDateBlocked: date on DST boundary not blocked when maxBookingDate is same day", () => {
90
+ expect((0, is_date_blocked_1.isDateBlocked)({
91
+ date: "2026-10-25",
92
+ startDate: null,
93
+ endDate: null,
94
+ minBookingDate: undefined,
95
+ maxBookingDate: "2026-10-25",
96
+ })).toBe(false);
97
+ });
98
+ });
99
+ describe("DST spring-forward Mar 29 2026 (Europe/Prague)", () => {
100
+ test("canSelectRange: 2-day range across DST spring-forward is valid", () => {
101
+ expect((0, can_select_range_1.canSelectRange)({
102
+ startDate: "2026-03-29",
103
+ endDate: "2026-03-30",
104
+ minBookingDays: 2,
105
+ minBookingDate: undefined,
106
+ maxBookingDate: undefined,
107
+ exactMinBookingDays: false,
108
+ isDateBlocked: () => false,
109
+ })).toBe(true);
110
+ });
111
+ test("isDateInsideRange: date on spring-forward boundary is inside range", () => {
112
+ expect((0, is_date_inside_range_1.isDateInsideRange)("2026-03-29", "2026-03-28", "2026-03-30")).toBe(true);
113
+ });
114
+ });
115
+ describe("UTC+9 (Asia/Tokyo, no DST) — year-end boundary Dec 31 2025 / Jan 1 2026", () => {
116
+ test("isStartDate: year-end date matches", () => {
117
+ expect((0, is_start_date_1.isStartDate)("2025-12-31", "2025-12-31")).toBe(true);
118
+ expect((0, is_start_date_1.isStartDate)("2026-01-01", "2025-12-31")).toBe(false);
119
+ });
120
+ test("isEndDate: New Year's Day matches", () => {
121
+ expect((0, is_end_date_1.isEndDate)("2026-01-01", "2026-01-01")).toBe(true);
122
+ expect((0, is_end_date_1.isEndDate)("2025-12-31", "2026-01-01")).toBe(false);
123
+ });
124
+ test("isDateInsideRange: date on year-end boundary is inside range", () => {
125
+ expect((0, is_date_inside_range_1.isDateInsideRange)("2025-12-31", "2025-12-30", "2026-01-01")).toBe(true);
126
+ expect((0, is_date_inside_range_1.isDateInsideRange)("2026-01-01", "2025-12-31", "2026-01-02")).toBe(true);
127
+ });
128
+ test("isInUnavailableDates: year-end date found in list", () => {
129
+ expect((0, is_in_unavailable_dates_1.isInUnavailableDates)(["2025-12-31"], "2025-12-31")).toBe(true);
130
+ expect((0, is_in_unavailable_dates_1.isInUnavailableDates)(["2025-12-31"], "2026-01-01")).toBe(false);
131
+ });
132
+ test("canSelectRange: 2-day range spanning year boundary is valid", () => {
133
+ expect((0, can_select_range_1.canSelectRange)({
134
+ startDate: "2025-12-31",
135
+ endDate: "2026-01-01",
136
+ minBookingDays: 2,
137
+ minBookingDate: undefined,
138
+ maxBookingDate: undefined,
139
+ exactMinBookingDays: false,
140
+ isDateBlocked: () => false,
141
+ })).toBe(true);
142
+ });
143
+ test("canSelectRange: startDate on minBookingDate at year boundary is valid", () => {
144
+ expect((0, can_select_range_1.canSelectRange)({
145
+ startDate: "2026-01-01",
146
+ endDate: null,
147
+ minBookingDays: 1,
148
+ minBookingDate: "2025-12-31",
149
+ maxBookingDate: undefined,
150
+ exactMinBookingDays: false,
151
+ isDateBlocked: () => false,
152
+ })).toBe(true);
153
+ });
154
+ test("canSelectRange: endDate within maxBookingDate at year boundary is valid", () => {
155
+ expect((0, can_select_range_1.canSelectRange)({
156
+ startDate: "2025-12-30",
157
+ endDate: "2025-12-31",
158
+ minBookingDays: 2,
159
+ minBookingDate: undefined,
160
+ maxBookingDate: "2026-01-02",
161
+ exactMinBookingDays: false,
162
+ isDateBlocked: () => false,
163
+ })).toBe(true);
164
+ });
165
+ test("isDateBlocked: Dec 31 not blocked when minBookingDate is Dec 31", () => {
166
+ expect((0, is_date_blocked_1.isDateBlocked)({
167
+ date: "2025-12-31",
168
+ startDate: null,
169
+ endDate: null,
170
+ minBookingDate: "2025-12-31",
171
+ })).toBe(false);
172
+ });
173
+ test("isDateBlocked: Jan 1 not blocked when maxBookingDate is Jan 1", () => {
174
+ expect((0, is_date_blocked_1.isDateBlocked)({
175
+ date: "2026-01-01",
176
+ startDate: null,
177
+ endDate: null,
178
+ maxBookingDate: "2026-01-01",
179
+ })).toBe(false);
180
+ });
181
+ test("isDateBlocked: Dec 30 blocked when minBookingDate is Dec 31", () => {
182
+ expect((0, is_date_blocked_1.isDateBlocked)({
183
+ date: "2025-12-30",
184
+ startDate: null,
185
+ endDate: null,
186
+ minBookingDate: "2025-12-31",
187
+ })).toBe(true);
188
+ });
189
+ test("isDateBlocked: Jan 2 blocked when maxBookingDate is Jan 1", () => {
190
+ expect((0, is_date_blocked_1.isDateBlocked)({
191
+ date: "2026-01-02",
192
+ startDate: null,
193
+ endDate: null,
194
+ maxBookingDate: "2026-01-01",
195
+ })).toBe(true);
196
+ });
197
+ test("isFirstOrLastSelectedDate: year-end and new year match start/end", () => {
198
+ expect((0, is_first_or_last_selected_date_1.isFirstOrLastSelectedDate)("2025-12-31", "2025-12-31", "2026-01-03")).toBe(true);
199
+ expect((0, is_first_or_last_selected_date_1.isFirstOrLastSelectedDate)("2026-01-03", "2025-12-31", "2026-01-03")).toBe(true);
200
+ expect((0, is_first_or_last_selected_date_1.isFirstOrLastSelectedDate)("2026-01-01", "2025-12-31", "2026-01-03")).toBe(false);
201
+ });
202
+ test("isDateHovered: date between startDate and hoveredDate highlighted across year boundary", () => {
203
+ expect((0, is_date_hovered_1.isDateHovered)({
204
+ date: "2025-12-31",
205
+ startDate: "2025-12-29",
206
+ endDate: null,
207
+ isDateBlocked: () => false,
208
+ hoveredDate: "2026-01-03",
209
+ minBookingDays: 1,
210
+ exactMinBookingDays: false,
211
+ })).toBe(true);
212
+ });
213
+ });
214
+ describe("getDays: month boundaries correct in UTC+ timezone", () => {
215
+ // April 2026 with firstDayOfWeek=1 (Monday):
216
+ // April 1 = Wednesday → 2 prev-month days (Mar 30, 31)
217
+ // April 30 = Thursday → 3 next-month overflow days (May 1, 2, 3)
218
+ // Total = 2 + 30 + 3 = 35 days
219
+ //
220
+ // Bug: in UTC+2 (Prague), dayjsEnWithTz().endOf("month").toDate() for April is
221
+ // 2026-04-30T23:59:59.999Z which local-methods read as 2026-05-01 →
222
+ // monthEndStr becomes "2026-05-01", so May 1 ends up in the current-month slice.
223
+ test("April 2026 calendar has exactly 35 days (5 weeks, firstDayOfWeek=1)", () => {
224
+ const days = (0, get_days_1.getDays)({ year: 2026, month: 3, firstDayOfWeek: 1 });
225
+ expect(days).toHaveLength(35);
226
+ });
227
+ test("last current-month day in April 2026 is April 30", () => {
228
+ const days = (0, get_days_1.getDays)({ year: 2026, month: 3, firstDayOfWeek: 1 });
229
+ const currentMonthDays = days.filter((d) => d.currentMonth);
230
+ expect(currentMonthDays).toHaveLength(30);
231
+ const last = currentMonthDays[currentMonthDays.length - 1];
232
+ expect((0, date_string_1.toDateString)(last.date)).toBe("2026-04-30");
233
+ });
234
+ test("May 1 appears as overflow (currentMonth: false) in April 2026 calendar", () => {
235
+ const days = (0, get_days_1.getDays)({ year: 2026, month: 3, firstDayOfWeek: 1 });
236
+ const may1 = days.find((d) => (0, date_string_1.toDateString)(d.date) === "2026-05-01");
237
+ expect(may1).toBeDefined();
238
+ expect(may1 === null || may1 === void 0 ? void 0 : may1.currentMonth).toBe(false);
239
+ });
240
+ test("overflow days after April 2026 are May 1, 2, 3", () => {
241
+ const days = (0, get_days_1.getDays)({ year: 2026, month: 3, firstDayOfWeek: 1 });
242
+ const overflow = days.filter((d) => !d.currentMonth && (0, date_string_1.toDateString)(d.date) >= "2026-05-01");
243
+ expect(overflow.map((d) => (0, date_string_1.toDateString)(d.date))).toEqual(["2026-05-01", "2026-05-02", "2026-05-03"]);
244
+ });
245
+ });
246
+ describe("getWeekdayLabels: exactly 7 labels in UTC+ timezone", () => {
247
+ // Bug: endOf("week").add(n, "day").toDate() has non-zero UTC ms (23:59:59.999Z).
248
+ // toDateString() falls back to local methods and shifts the date +1 day in UTC+
249
+ // timezones, so getEach() returns 8 dates → 8 labels (last one is a duplicate Mon).
250
+ test("returns exactly 7 labels with firstDayOfWeek=0", () => {
251
+ expect((0, get_weekday_labels_1.getWeekdayLabels)({ firstDayOfWeek: 0 })).toHaveLength(7);
252
+ });
253
+ test("returns exactly 7 labels with firstDayOfWeek=1 (Monday-start)", () => {
254
+ expect((0, get_weekday_labels_1.getWeekdayLabels)({ firstDayOfWeek: 1 })).toHaveLength(7);
255
+ });
256
+ test("Mo-start week is Mo…Su with no duplicates", () => {
257
+ expect((0, get_weekday_labels_1.getWeekdayLabels)({ firstDayOfWeek: 1 })).toEqual(["Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"]);
258
+ });
259
+ });
260
+ describe("getYears: exactly 9 years for ±4 decade view in UTC+ timezone", () => {
261
+ // Bug: useDecade passed endOf("year").toDate() to getYears, which called toDateString()
262
+ // on 2030-12-31T23:59:59.999Z — local methods in CEST give 2031-01-01, adding a 10th year.
263
+ // Fix: useDecade now passes new Date(createDateString(year+4, 0)) (UTC midnight Jan 1).
264
+ test("year=2026: 9 years returned (2022–2030)", () => {
265
+ const startYear = new Date("2022-01-01");
266
+ const endYear = new Date("2030-01-01");
267
+ expect((0, get_years_1.getYears)({ startYear, endYear })).toHaveLength(9);
268
+ });
269
+ test("year=2026: first year is 2022, last is 2030", () => {
270
+ const startYear = new Date("2022-01-01");
271
+ const endYear = new Date("2030-01-01");
272
+ const years = (0, get_years_1.getYears)({ startYear, endYear });
273
+ expect(years[0].yearLabel).toBe("2022");
274
+ expect(years[years.length - 1].yearLabel).toBe("2030");
275
+ });
276
+ });
277
+ describe("getCurrentYearMonthAndDate: returns local calendar month in UTC+ timezone", () => {
278
+ // Bug: dayjsEnWithTz().startOf("day") anchors to UTC day, so at 00:30 local Prague
279
+ // time (= 22:30 UTC the previous day) it returns the previous UTC day's month.
280
+ // Fix: getDateMonthAndYear(new Date()) — toDateString(new Date()) uses local methods
281
+ // for any non-UTC-midnight time, giving the correct local calendar day.
282
+ beforeAll(() => jest.useFakeTimers());
283
+ afterAll(() => jest.useRealTimers());
284
+ test("returns local calendar month, not UTC month", () => {
285
+ // Set clock to 22:30 UTC = 00:30 local in UTC+2 (Prague CEST)
286
+ // In UTC the local month is still August (7); in Prague it is September (8).
287
+ // The function must always agree with the local calendar, whatever the offset.
288
+ jest.setSystemTime(new Date("2021-08-31T22:30:00Z"));
289
+ const expectedMonth = new Date("2021-08-31T22:30:00Z").getMonth(); // local month
290
+ const result = (0, get_current_year_month_and_date_1.getCurrentYearMonthAndDate)();
291
+ expect(result.month).toBe(expectedMonth);
292
+ });
293
+ });
294
+ describe("Range boundary checks", () => {
295
+ test("canSelectRange: startDate on minBookingDate boundary is valid", () => {
296
+ expect((0, can_select_range_1.canSelectRange)({
297
+ startDate: "2026-10-26",
298
+ endDate: null,
299
+ minBookingDays: 1,
300
+ minBookingDate: "2026-10-25",
301
+ maxBookingDate: undefined,
302
+ exactMinBookingDays: false,
303
+ isDateBlocked: () => false,
304
+ })).toBe(true);
305
+ });
306
+ test("canSelectRange: endDate within maxBookingDate boundary is valid", () => {
307
+ expect((0, can_select_range_1.canSelectRange)({
308
+ startDate: "2026-10-24",
309
+ endDate: "2026-10-25",
310
+ minBookingDays: 2,
311
+ minBookingDate: undefined,
312
+ maxBookingDate: "2026-10-26",
313
+ exactMinBookingDays: false,
314
+ isDateBlocked: () => false,
315
+ })).toBe(true);
316
+ });
317
+ test("isDateHovered: date between startDate and hoveredDate is highlighted", () => {
318
+ expect((0, is_date_hovered_1.isDateHovered)({
319
+ date: "2026-10-26",
320
+ startDate: "2026-10-24",
321
+ endDate: null,
322
+ isDateBlocked: () => false,
323
+ hoveredDate: "2026-10-28",
324
+ minBookingDays: 1,
325
+ exactMinBookingDays: false,
326
+ })).toBe(true);
327
+ });
328
+ });
@@ -1,8 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.getCurrentYearMonthAndDate = getCurrentYearMonthAndDate;
4
- const dayjs_utils_1 = require("./dayjs-utils");
5
4
  const get_date_month_and_year_1 = require("./get-date-month-and-year");
6
5
  function getCurrentYearMonthAndDate() {
7
- return (0, get_date_month_and_year_1.getDateMonthAndYear)((0, dayjs_utils_1.dayjsEnWithTz)().startOf("day").toDate());
6
+ return (0, get_date_month_and_year_1.getDateMonthAndYear)(new Date());
8
7
  }
@@ -2,7 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.getDateIntervals = getDateIntervals;
4
4
  const is_not_nil_1 = require("@uxf/core/utils/is-not-nil");
5
- const dayjs_utils_1 = require("./dayjs-utils");
5
+ const date_string_1 = require("./date-string");
6
6
  function getDateIntervals(dates) {
7
7
  const sortedDates = dates.slice().sort((a, b) => a.getTime() - b.getTime());
8
8
  const intervals = [];
@@ -11,9 +11,7 @@ function getDateIntervals(dates) {
11
11
  let currentEnd = firstSortedDate !== null && firstSortedDate !== void 0 ? firstSortedDate : new Date();
12
12
  for (let i = 1; i < sortedDates.length; i++) {
13
13
  const currentDate = sortedDates[i];
14
- const nextDate = (0, dayjs_utils_1.dayjsEnRaw)(currentEnd).add(1, "day").toDate();
15
- // If the current date matches the expected next date, it's contiguous
16
- if ((0, dayjs_utils_1.dayjsEnRaw)(currentDate).isSame(nextDate, "day")) {
14
+ if ((0, date_string_1.toDateString)(currentDate) === (0, date_string_1.addDays)((0, date_string_1.toDateString)(currentEnd), 1)) {
17
15
  currentEnd = currentDate;
18
16
  }
19
17
  else {
@@ -23,7 +21,6 @@ function getDateIntervals(dates) {
23
21
  }
24
22
  }
25
23
  if ((0, is_not_nil_1.isNotNil)(firstSortedDate)) {
26
- // Push the last interval
27
24
  intervals.push({ start: currentStart, end: currentEnd });
28
25
  }
29
26
  return intervals;
@@ -1,14 +1,15 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.getDateMonthAndYear = getDateMonthAndYear;
4
- const dayjs_utils_1 = require("./dayjs-utils");
4
+ const create_date_string_1 = require("./create-date-string");
5
+ const date_string_1 = require("./date-string");
5
6
  function getDateMonthAndYear(date) {
6
- const day = (0, dayjs_utils_1.dayjsEnWithTz)(date).startOf("month").toDate();
7
- const year = (0, dayjs_utils_1.dayjsEnWithTz)(day).year();
8
- const month = (0, dayjs_utils_1.dayjsEnWithTz)(day).month();
7
+ const ds = (0, date_string_1.toDateString)(date);
8
+ const year = parseInt(ds.slice(0, 4), 10);
9
+ const month = parseInt(ds.slice(5, 7), 10) - 1; // 0-indexed, matching dayjs convention
9
10
  return {
10
11
  year,
11
12
  month,
12
- date: day,
13
+ date: (0, date_string_1.fromDateString)((0, create_date_string_1.createDateString)(year, month)),
13
14
  };
14
15
  }
package/utils/get-days.js CHANGED
@@ -2,35 +2,38 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.getDays = getDays;
4
4
  const create_date_string_1 = require("./create-date-string");
5
+ const date_string_1 = require("./date-string");
5
6
  const dayjs_utils_1 = require("./dayjs-utils");
6
7
  const get_each_1 = require("./get-each");
7
8
  function getDays({ year, month, firstDayOfWeek = 0, dayLabelFormat = (date) => (0, dayjs_utils_1.dayjsEnWithTz)(date).format("DD"), }) {
8
9
  const date = new Date((0, create_date_string_1.createDateString)(year, month));
9
10
  const lastDayOfWeek = firstDayOfWeek + 7;
10
11
  const monthStart = (0, dayjs_utils_1.dayjsEnWithTz)(date).startOf("month");
12
+ const monthStartStr = monthStart.format("YYYY-MM-DD");
11
13
  const monthStartDay = monthStart.day();
12
14
  const monthEnd = (0, dayjs_utils_1.dayjsEnWithTz)(date).endOf("month");
15
+ const monthEndStr = monthEnd.format("YYYY-MM-DD");
13
16
  const monthEndDay = monthEnd.day();
14
17
  const prevMonthDaysCount = monthStartDay >= firstDayOfWeek ? monthStartDay - firstDayOfWeek : 6 - firstDayOfWeek + monthStartDay + 1;
15
18
  const prevMonthDays = prevMonthDaysCount > 0
16
- ? (0, get_each_1.getEach)(monthStart.subtract(prevMonthDaysCount, "days").toDate(), monthStart.subtract(1, "day").toDate(), "day").map((d) => ({
19
+ ? (0, get_each_1.getEach)((0, date_string_1.addDays)(monthStartStr, -prevMonthDaysCount), (0, date_string_1.addDays)(monthStartStr, -1), "day").map((d) => ({
17
20
  currentMonth: false,
18
- date: d,
19
- dayLabel: dayLabelFormat(d),
21
+ date: (0, date_string_1.fromDateString)(d),
22
+ dayLabel: dayLabelFormat((0, date_string_1.fromDateString)(d)),
20
23
  }))
21
24
  : [];
22
25
  const nextMonthDaysCount = lastDayOfWeek - monthEndDay - 1;
23
26
  const nextMonthDays = nextMonthDaysCount > 0 && nextMonthDaysCount < 7
24
- ? (0, get_each_1.getEach)(monthEnd.add(1, "day").toDate(), monthEnd.add(nextMonthDaysCount, "days").toDate(), "day").map((d) => ({
27
+ ? (0, get_each_1.getEach)((0, date_string_1.addDays)(monthEndStr, 1), (0, date_string_1.addDays)(monthEndStr, nextMonthDaysCount), "day").map((d) => ({
25
28
  currentMonth: false,
26
- date: d,
27
- dayLabel: dayLabelFormat(d),
29
+ date: (0, date_string_1.fromDateString)(d),
30
+ dayLabel: dayLabelFormat((0, date_string_1.fromDateString)(d)),
28
31
  }))
29
32
  : [];
30
- const days = (0, get_each_1.getEach)(monthStart.toDate(), monthEnd.toDate(), "day").map((d) => ({
33
+ const days = (0, get_each_1.getEach)(monthStartStr, monthEndStr, "day").map((d) => ({
31
34
  currentMonth: true,
32
- date: d,
33
- dayLabel: dayLabelFormat(d),
35
+ date: (0, date_string_1.fromDateString)(d),
36
+ dayLabel: dayLabelFormat((0, date_string_1.fromDateString)(d)),
34
37
  }));
35
38
  return [...prevMonthDays, ...days, ...nextMonthDays];
36
39
  }
@@ -1 +1,2 @@
1
- export declare function getEach(start: Date, end: Date, type: "day" | "month" | "year"): Date[];
1
+ import { DateString } from "@uxf/core/date";
2
+ export declare function getEach(start: DateString, end: DateString, type: "day" | "month" | "year"): DateString[];
package/utils/get-each.js CHANGED
@@ -1,18 +1,46 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.getEach = getEach;
4
- const dayjs_utils_1 = require("./dayjs-utils");
4
+ const create_date_string_1 = require("./create-date-string");
5
+ const date_string_1 = require("./date-string");
6
+ const DAY_MS = 86400000;
5
7
  function getEach(start, end, type) {
6
- const startOfStart = (0, dayjs_utils_1.dayjsEnWithTz)(start).startOf(type);
7
- const startOfEnd = (0, dayjs_utils_1.dayjsEnWithTz)(end).startOf(type);
8
- if (startOfStart.toDate().getTime() > startOfEnd.toDate().getTime()) {
8
+ if (type === "day") {
9
+ const startMs = new Date(start).getTime();
10
+ const endMs = new Date(end).getTime();
11
+ if (startMs > endMs) {
12
+ throw new Error("start must precede end");
13
+ }
14
+ const count = Math.round((endMs - startMs) / DAY_MS) + 1;
15
+ const results = new Array(count);
16
+ for (let i = 0; i < count; i++) {
17
+ results[i] = (0, date_string_1.toDateString)(new Date(startMs + i * DAY_MS));
18
+ }
19
+ return results;
20
+ }
21
+ if (type === "month") {
22
+ const [sy, sm] = start.split("-").map(Number);
23
+ const [ey, em] = end.split("-").map(Number);
24
+ if (sy > ey || (sy === ey && sm > em)) {
25
+ throw new Error("start must precede end");
26
+ }
27
+ const count = (ey - sy) * 12 + (em - sm) + 1;
28
+ const results = new Array(count);
29
+ for (let i = 0; i < count; i++) {
30
+ const totalMonths = sm - 1 + i; // 0-indexed from start month
31
+ results[i] = (0, create_date_string_1.createDateString)(sy + Math.floor(totalMonths / 12), totalMonths % 12);
32
+ }
33
+ return results;
34
+ }
35
+ // type === "year"
36
+ const sy = parseInt(start.slice(0, 4), 10);
37
+ const ey = parseInt(end.slice(0, 4), 10);
38
+ if (sy > ey) {
9
39
  throw new Error("start must precede end");
10
40
  }
11
- const results = [];
12
- let current = startOfStart;
13
- while (current.toDate().getTime() <= startOfEnd.toDate().getTime()) {
14
- results.push(current.toDate());
15
- current = current.add(1, type);
41
+ const results = new Array(ey - sy + 1);
42
+ for (let i = 0; i < results.length; i++) {
43
+ results[i] = (0, create_date_string_1.createDateString)(sy + i);
16
44
  }
17
45
  return results;
18
46
  }
@@ -2,30 +2,45 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const get_each_1 = require("./get-each");
4
4
  test("getEach day", () => {
5
- expect((0, get_each_1.getEach)(new Date("2021-05-05"), new Date("2021-05-07"), "day")).toStrictEqual([
6
- new Date("2021-05-05T00:00:00Z"),
7
- new Date("2021-05-06T00:00:00Z"),
8
- new Date("2021-05-07T00:00:00Z"),
9
- ]);
5
+ expect((0, get_each_1.getEach)("2021-05-05", "2021-05-07", "day")).toStrictEqual(["2021-05-05", "2021-05-06", "2021-05-07"]);
6
+ });
7
+ test("getEach day — DST fall-back (Oct 25 2026)", () => {
8
+ expect((0, get_each_1.getEach)("2026-10-24", "2026-10-26", "day")).toStrictEqual(["2026-10-24", "2026-10-25", "2026-10-26"]);
9
+ });
10
+ test("getEach day — DST spring-forward (Mar 29 2026)", () => {
11
+ expect((0, get_each_1.getEach)("2026-03-28", "2026-03-30", "day")).toStrictEqual(["2026-03-28", "2026-03-29", "2026-03-30"]);
10
12
  });
11
13
  test("getEach month", () => {
12
- expect((0, get_each_1.getEach)(new Date("2021-05-05"), new Date("2021-08-07"), "month")).toStrictEqual([
13
- new Date("2021-05-01T00:00:00Z"),
14
- new Date("2021-06-01T00:00:00Z"),
15
- new Date("2021-07-01T00:00:00Z"),
16
- new Date("2021-08-01T00:00:00Z"),
14
+ expect((0, get_each_1.getEach)("2021-05-05", "2021-08-07", "month")).toStrictEqual([
15
+ "2021-05-01",
16
+ "2021-06-01",
17
+ "2021-07-01",
18
+ "2021-08-01",
19
+ ]);
20
+ });
21
+ test("getEach month — year boundary", () => {
22
+ expect((0, get_each_1.getEach)("2021-11-01", "2022-02-01", "month")).toStrictEqual([
23
+ "2021-11-01",
24
+ "2021-12-01",
25
+ "2022-01-01",
26
+ "2022-02-01",
17
27
  ]);
18
28
  });
19
29
  test("getEach year", () => {
20
- expect((0, get_each_1.getEach)(new Date("2015-05-05"), new Date("2020-05-07"), "year")).toStrictEqual([
21
- new Date("2015-01-01T00:00:00Z"),
22
- new Date("2016-01-01T00:00:00Z"),
23
- new Date("2017-01-01T00:00:00Z"),
24
- new Date("2018-01-01T00:00:00Z"),
25
- new Date("2019-01-01T00:00:00Z"),
26
- new Date("2020-01-01T00:00:00Z"),
30
+ expect((0, get_each_1.getEach)("2015-05-05", "2020-05-07", "year")).toStrictEqual([
31
+ "2015-01-01",
32
+ "2016-01-01",
33
+ "2017-01-01",
34
+ "2018-01-01",
35
+ "2019-01-01",
36
+ "2020-01-01",
27
37
  ]);
28
38
  });
29
- test("getEach invalid input", () => {
30
- expect(() => (0, get_each_1.getEach)(new Date("2015-05-05"), new Date("2014-05-07T00:00:00Z"), "day")).toThrow();
39
+ test("getEach single day", () => {
40
+ expect((0, get_each_1.getEach)("2021-05-05", "2021-05-05", "day")).toStrictEqual(["2021-05-05"]);
41
+ });
42
+ test("getEach invalid input throws", () => {
43
+ expect(() => (0, get_each_1.getEach)("2015-05-05", "2014-05-07", "day")).toThrow("start must precede end");
44
+ expect(() => (0, get_each_1.getEach)("2021-06-01", "2021-05-01", "month")).toThrow("start must precede end");
45
+ expect(() => (0, get_each_1.getEach)("2020-01-01", "2019-01-01", "year")).toThrow("start must precede end");
31
46
  });
@@ -1,19 +1,16 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.getInitialMonths = getInitialMonths;
4
- const dayjs_utils_1 = require("./dayjs-utils");
4
+ const date_string_1 = require("./date-string");
5
5
  const get_current_year_month_and_date_1 = require("./get-current-year-month-and-date");
6
6
  const get_date_month_and_year_1 = require("./get-date-month-and-year");
7
7
  function getInitialMonths(numberOfMonths, startDate) {
8
8
  const firstMonth = startDate ? (0, get_date_month_and_year_1.getDateMonthAndYear)(startDate) : (0, get_current_year_month_and_date_1.getCurrentYearMonthAndDate)();
9
- let prevMonthDate = firstMonth.date;
10
9
  let months = [firstMonth];
11
10
  if (numberOfMonths > 1) {
12
11
  months = Array.from(Array(numberOfMonths - 1).keys()).reduce((m) => {
13
- prevMonthDate = (0, dayjs_utils_1.dayjsEnWithTz)(m[m.length - 1].date)
14
- .add(1, "month")
15
- .toDate();
16
- return m.concat([(0, get_date_month_and_year_1.getDateMonthAndYear)(prevMonthDate)]);
12
+ const nextDate = (0, date_string_1.fromDateString)((0, date_string_1.addMonths)((0, date_string_1.toDateString)(m[m.length - 1].date), 1));
13
+ return m.concat([(0, get_date_month_and_year_1.getDateMonthAndYear)(nextDate)]);
17
14
  }, months);
18
15
  }
19
16
  return months;
@@ -2,14 +2,14 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.getMonths = getMonths;
4
4
  const create_date_string_1 = require("./create-date-string");
5
+ const date_string_1 = require("./date-string");
5
6
  const dayjs_utils_1 = require("./dayjs-utils");
6
7
  const get_each_1 = require("./get-each");
7
8
  function getMonths({ year, monthLabelFormat = (date) => (0, dayjs_utils_1.dayjsEnWithTz)(date).format("MMMM"), }) {
8
- const date = new Date((0, create_date_string_1.createDateString)(year, 0));
9
- const yearStart = (0, dayjs_utils_1.dayjsEnWithTz)(date).startOf("year").toDate();
10
- const yearEnd = (0, dayjs_utils_1.dayjsEnWithTz)(date).endOf("year").toDate();
11
- return (0, get_each_1.getEach)(yearStart, yearEnd, "month").map((d) => ({
12
- date: d,
13
- monthLabel: monthLabelFormat(d),
14
- }));
9
+ const yearStart = (0, create_date_string_1.createDateString)(year, 0);
10
+ const yearEnd = (0, create_date_string_1.createDateString)(year, 11);
11
+ return (0, get_each_1.getEach)(yearStart, yearEnd, "month").map((d) => {
12
+ const date = (0, date_string_1.fromDateString)(d);
13
+ return { date, monthLabel: monthLabelFormat(date) };
14
+ });
15
15
  }