@intech-software/chronera 0.1.4 → 0.1.6

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 (91) hide show
  1. package/dist/core/local-date.d.ts +1 -0
  2. package/dist/core/local-date.js +1 -0
  3. package/dist/core/local-date.js.map +1 -1
  4. package/dist/holidays/countries/ae.d.ts +6 -0
  5. package/dist/holidays/countries/ae.js +70 -0
  6. package/dist/holidays/countries/ae.js.map +1 -0
  7. package/dist/holidays/countries/au.d.ts +6 -0
  8. package/dist/holidays/countries/au.js +79 -0
  9. package/dist/holidays/countries/au.js.map +1 -0
  10. package/dist/holidays/countries/cn.d.ts +6 -0
  11. package/dist/holidays/countries/cn.js +57 -0
  12. package/dist/holidays/countries/cn.js.map +1 -0
  13. package/dist/holidays/countries/de.d.ts +6 -0
  14. package/dist/holidays/countries/de.js +81 -0
  15. package/dist/holidays/countries/de.js.map +1 -0
  16. package/dist/holidays/countries/fr.d.ts +6 -0
  17. package/dist/holidays/countries/fr.js +97 -0
  18. package/dist/holidays/countries/fr.js.map +1 -0
  19. package/dist/holidays/countries/gb.d.ts +6 -0
  20. package/dist/holidays/countries/gb.js +80 -0
  21. package/dist/holidays/countries/gb.js.map +1 -0
  22. package/dist/holidays/countries/hk.d.ts +6 -0
  23. package/dist/holidays/countries/hk.js +87 -0
  24. package/dist/holidays/countries/hk.js.map +1 -0
  25. package/dist/holidays/countries/in.d.ts +6 -0
  26. package/dist/holidays/countries/in.js +52 -0
  27. package/dist/holidays/countries/in.js.map +1 -0
  28. package/dist/holidays/countries/index.d.ts +18 -0
  29. package/dist/holidays/countries/index.js +34 -0
  30. package/dist/holidays/countries/index.js.map +1 -0
  31. package/dist/holidays/countries/ir.d.ts +7 -0
  32. package/dist/holidays/countries/ir.js +93 -0
  33. package/dist/holidays/countries/ir.js.map +1 -0
  34. package/dist/holidays/countries/jp.d.ts +6 -0
  35. package/dist/holidays/countries/jp.js +140 -0
  36. package/dist/holidays/countries/jp.js.map +1 -0
  37. package/dist/holidays/countries/sa.d.ts +7 -0
  38. package/dist/holidays/countries/sa.js +56 -0
  39. package/dist/holidays/countries/sa.js.map +1 -0
  40. package/dist/holidays/countries/sg.d.ts +6 -0
  41. package/dist/holidays/countries/sg.js +71 -0
  42. package/dist/holidays/countries/sg.js.map +1 -0
  43. package/dist/holidays/countries/th.d.ts +6 -0
  44. package/dist/holidays/countries/th.js +151 -0
  45. package/dist/holidays/countries/th.js.map +1 -0
  46. package/dist/holidays/countries/tw.d.ts +6 -0
  47. package/dist/holidays/countries/tw.js +57 -0
  48. package/dist/holidays/countries/tw.js.map +1 -0
  49. package/dist/holidays/countries/us.d.ts +6 -0
  50. package/dist/holidays/countries/us.js +112 -0
  51. package/dist/holidays/countries/us.js.map +1 -0
  52. package/dist/holidays/easter.d.ts +23 -0
  53. package/dist/holidays/easter.js +49 -0
  54. package/dist/holidays/easter.js.map +1 -0
  55. package/dist/holidays/index.d.ts +5 -0
  56. package/dist/holidays/index.js +6 -0
  57. package/dist/holidays/index.js.map +1 -0
  58. package/dist/holidays/registry.d.ts +14 -0
  59. package/dist/holidays/registry.js +79 -0
  60. package/dist/holidays/registry.js.map +1 -0
  61. package/dist/holidays/rules/easter-rule.d.ts +5 -0
  62. package/dist/holidays/rules/easter-rule.js +26 -0
  63. package/dist/holidays/rules/easter-rule.js.map +1 -0
  64. package/dist/holidays/rules/fixed-rule.d.ts +5 -0
  65. package/dist/holidays/rules/fixed-rule.js +72 -0
  66. package/dist/holidays/rules/fixed-rule.js.map +1 -0
  67. package/dist/holidays/rules/floating-rule.d.ts +6 -0
  68. package/dist/holidays/rules/floating-rule.js +53 -0
  69. package/dist/holidays/rules/floating-rule.js.map +1 -0
  70. package/dist/holidays/rules/hijri-rule.d.ts +6 -0
  71. package/dist/holidays/rules/hijri-rule.js +44 -0
  72. package/dist/holidays/rules/hijri-rule.js.map +1 -0
  73. package/dist/holidays/rules/index.d.ts +9 -0
  74. package/dist/holidays/rules/index.js +26 -0
  75. package/dist/holidays/rules/index.js.map +1 -0
  76. package/dist/holidays/types.d.ts +106 -0
  77. package/dist/holidays/types.js +2 -0
  78. package/dist/holidays/types.js.map +1 -0
  79. package/dist/index.d.ts +8 -2
  80. package/dist/index.js +5 -2
  81. package/dist/index.js.map +1 -1
  82. package/dist/operations/business-days.d.ts +30 -14
  83. package/dist/operations/business-days.js +115 -37
  84. package/dist/operations/business-days.js.map +1 -1
  85. package/dist/operations/business-hours.d.ts +137 -0
  86. package/dist/operations/business-hours.js +488 -0
  87. package/dist/operations/business-hours.js.map +1 -0
  88. package/dist/operations/holidays.d.ts +15 -0
  89. package/dist/operations/holidays.js +36 -0
  90. package/dist/operations/holidays.js.map +1 -0
  91. package/package.json +10 -1
@@ -0,0 +1,488 @@
1
+ import { gregorianFromAbsoluteDay } from "../calendar/gregory/absolute-day.js";
2
+ import { absoluteDayFromGregorianFields } from "../core/absolute-day.js";
3
+ import { getIsoDayOfWeek } from "../core/iso-week.js";
4
+ import { localDate } from "../core/local-date.js";
5
+ import { localDateTime } from "../core/local-date-time.js";
6
+ import { localTime } from "../core/local-time.js";
7
+ import { ChroneraError } from "../errors/errors.js";
8
+ import { getHolidayCalendar } from "../holidays/registry.js";
9
+ import { projectInstantToZonedFields, validateTimeZone, } from "../runtime/timezone.js";
10
+ import { getAbsoluteDay } from "./convenience.js";
11
+ import { isPublicHoliday } from "./holidays.js";
12
+ import { getTimeZoneOffset } from "./timezone.js";
13
+ /**
14
+ * Parses a shift time string ("HH:mm", "HH:mm:ss") or LocalTime into milliseconds from midnight.
15
+ */
16
+ function parseShiftTimeToMs(input) {
17
+ if (typeof input === "object" &&
18
+ input !== null &&
19
+ input.kind === "local-time") {
20
+ return (((input.hour * 60 + input.minute) * 60 + input.second) * 1000 +
21
+ input.millisecond);
22
+ }
23
+ if (typeof input === "string") {
24
+ const trimmed = input.trim();
25
+ const match = /^(\d{1,2}):(\d{2})(?::(\d{2}))?(?:\.(\d{1,3}))?$/.exec(trimmed);
26
+ if (!match) {
27
+ throw new ChroneraError("CHRONERA_INVALID_TIME", `Invalid shift time string: "${input}". Expected format "HH:mm" or "HH:mm:ss".`);
28
+ }
29
+ const hour = Number.parseInt(match[1], 10);
30
+ const minute = Number.parseInt(match[2], 10);
31
+ const second = match[3] ? Number.parseInt(match[3], 10) : 0;
32
+ const millisecond = match[4]
33
+ ? Number.parseInt(match[4].padEnd(3, "0"), 10)
34
+ : 0;
35
+ if (hour < 0 ||
36
+ hour > 24 ||
37
+ minute < 0 ||
38
+ minute > 59 ||
39
+ second < 0 ||
40
+ second > 59 ||
41
+ millisecond < 0 ||
42
+ millisecond > 999 ||
43
+ (hour === 24 && (minute > 0 || second > 0 || millisecond > 0))) {
44
+ throw new ChroneraError("CHRONERA_OUT_OF_RANGE", `Shift time out of range: "${input}".`);
45
+ }
46
+ return ((hour * 60 + minute) * 60 + second) * 1000 + millisecond;
47
+ }
48
+ throw new ChroneraError("CHRONERA_INVALID_TIME", 'Shift time must be a string formatted as "HH:mm" or a LocalTime object.');
49
+ }
50
+ /**
51
+ * Normalizes and validates an array of shift windows for a single day.
52
+ */
53
+ function normalizeShiftWindows(shifts) {
54
+ if (!shifts || shifts.length === 0) {
55
+ // Default 09:00 - 17:00 (8 hours)
56
+ return [
57
+ {
58
+ startMs: 9 * 3_600_000,
59
+ endMs: 17 * 3_600_000,
60
+ durationMs: 8 * 3_600_000,
61
+ },
62
+ ];
63
+ }
64
+ const normalized = shifts.map((w) => {
65
+ const startMs = parseShiftTimeToMs(w.start);
66
+ const endMs = parseShiftTimeToMs(w.end);
67
+ if (startMs >= endMs) {
68
+ throw new ChroneraError("CHRONERA_OUT_OF_RANGE", `Shift start (${String(w.start)}) must be strictly earlier than shift end (${String(w.end)}).`);
69
+ }
70
+ return {
71
+ startMs,
72
+ endMs,
73
+ durationMs: endMs - startMs,
74
+ };
75
+ });
76
+ normalized.sort((a, b) => a.startMs - b.startMs);
77
+ for (let i = 1; i < normalized.length; i++) {
78
+ const prev = normalized[i - 1];
79
+ const curr = normalized[i];
80
+ if (curr.startMs < prev.endMs) {
81
+ throw new ChroneraError("CHRONERA_OUT_OF_RANGE", `Overlapping shift windows detected: [${prev.startMs}ms - ${prev.endMs}ms] and [${curr.startMs}ms - ${curr.endMs}ms].`);
82
+ }
83
+ }
84
+ return normalized;
85
+ }
86
+ /**
87
+ * Returns the normalized shift windows for a given absolute day.
88
+ */
89
+ function getShiftsForDay(absDay, schedule) {
90
+ const dow = getIsoDayOfWeek(absDay);
91
+ if (schedule.dayShifts && schedule.dayShifts[dow] !== undefined) {
92
+ return normalizeShiftWindows(schedule.dayShifts[dow]);
93
+ }
94
+ return normalizeShiftWindows(schedule.shifts);
95
+ }
96
+ /**
97
+ * Checks whether a given absolute day is a working day under the provided schedule.
98
+ */
99
+ function isWorkingScheduleDay(absDay, schedule) {
100
+ const dow = getIsoDayOfWeek(absDay);
101
+ let isWorkDayOfWeek = false;
102
+ if (schedule.workDays && schedule.workDays.length > 0) {
103
+ isWorkDayOfWeek = schedule.workDays.includes(dow);
104
+ }
105
+ else if (schedule.weekendDays && schedule.weekendDays.length > 0) {
106
+ isWorkDayOfWeek = !schedule.weekendDays.includes(dow);
107
+ }
108
+ else if (schedule.country) {
109
+ try {
110
+ const cal = getHolidayCalendar(schedule.country);
111
+ if (cal.defaultWeekendDays) {
112
+ isWorkDayOfWeek = !cal.defaultWeekendDays.includes(dow);
113
+ }
114
+ else {
115
+ isWorkDayOfWeek = dow !== 6 && dow !== 7;
116
+ }
117
+ }
118
+ catch {
119
+ isWorkDayOfWeek = dow !== 6 && dow !== 7;
120
+ }
121
+ }
122
+ else {
123
+ isWorkDayOfWeek = dow !== 6 && dow !== 7;
124
+ }
125
+ if (!isWorkDayOfWeek) {
126
+ return false;
127
+ }
128
+ const gFields = gregorianFromAbsoluteDay(absDay);
129
+ const gDate = localDate(gFields.year, gFields.month, gFields.day);
130
+ if (schedule.country && isPublicHoliday(gDate, schedule.country)) {
131
+ return false;
132
+ }
133
+ if (schedule.holidays) {
134
+ if (typeof schedule.holidays === "function") {
135
+ if (schedule.holidays(gDate))
136
+ return false;
137
+ }
138
+ else if (typeof schedule.holidays === "string" ||
139
+ "rules" in schedule.holidays) {
140
+ if (isPublicHoliday(gDate, schedule.holidays))
141
+ return false;
142
+ }
143
+ else if (Array.isArray(schedule.holidays)) {
144
+ if (schedule.holidays.some((h) => getAbsoluteDay(h) === absDay)) {
145
+ return false;
146
+ }
147
+ }
148
+ }
149
+ const shifts = getShiftsForDay(absDay, schedule);
150
+ return shifts.length > 0;
151
+ }
152
+ function extractTimeInfo(input, timeZone) {
153
+ if (input instanceof Date) {
154
+ if (Number.isNaN(input.getTime())) {
155
+ throw new ChroneraError("CHRONERA_INVALID_DATE", "Invalid Date object.");
156
+ }
157
+ if (timeZone) {
158
+ validateTimeZone(timeZone);
159
+ const inst = {
160
+ kind: "instant",
161
+ epochMilliseconds: input.getTime(),
162
+ };
163
+ const f = projectInstantToZonedFields(inst, timeZone);
164
+ const absDay = absoluteDayFromGregorianFields(f.year, f.month, f.day);
165
+ const timeMs = ((f.hour * 60 + f.minute) * 60 + f.second) * 1000 + f.millisecond;
166
+ return { kind: "date", absDay, timeMs, timeZone };
167
+ }
168
+ const absDay = absoluteDayFromGregorianFields(input.getFullYear(), input.getMonth() + 1, input.getDate());
169
+ const timeMs = ((input.getHours() * 60 + input.getMinutes()) * 60 + input.getSeconds()) *
170
+ 1000 +
171
+ input.getMilliseconds();
172
+ return { kind: "date", absDay, timeMs };
173
+ }
174
+ if (typeof input === "object" &&
175
+ input !== null &&
176
+ "kind" in input &&
177
+ input.kind === "instant") {
178
+ if (!Number.isFinite(input.epochMilliseconds)) {
179
+ throw new ChroneraError("CHRONERA_INVALID_INSTANT", "Invalid Instant epochMilliseconds.");
180
+ }
181
+ if (timeZone) {
182
+ validateTimeZone(timeZone);
183
+ const f = projectInstantToZonedFields(input, timeZone);
184
+ const absDay = absoluteDayFromGregorianFields(f.year, f.month, f.day);
185
+ const timeMs = ((f.hour * 60 + f.minute) * 60 + f.second) * 1000 + f.millisecond;
186
+ return { kind: "instant", absDay, timeMs, timeZone };
187
+ }
188
+ const d = new Date(input.epochMilliseconds);
189
+ const absDay = absoluteDayFromGregorianFields(d.getUTCFullYear(), d.getUTCMonth() + 1, d.getUTCDate());
190
+ const timeMs = ((d.getUTCHours() * 60 + d.getUTCMinutes()) * 60 + d.getUTCSeconds()) *
191
+ 1000 +
192
+ d.getUTCMilliseconds();
193
+ return { kind: "instant", absDay, timeMs };
194
+ }
195
+ if (typeof input === "object" &&
196
+ input !== null &&
197
+ "kind" in input &&
198
+ input.kind === "local-date-time") {
199
+ const absDay = getAbsoluteDay(input.date);
200
+ const timeMs = ((input.time.hour * 60 + input.time.minute) * 60 + input.time.second) *
201
+ 1000 +
202
+ input.time.millisecond;
203
+ return { kind: "local-date-time", absDay, timeMs };
204
+ }
205
+ if (typeof input === "object" &&
206
+ input !== null &&
207
+ "kind" in input &&
208
+ input.kind === "local-date") {
209
+ const absDay = getAbsoluteDay(input);
210
+ return { kind: "local-date", absDay, timeMs: 0 };
211
+ }
212
+ throw new ChroneraError("CHRONERA_INVALID_DATE", "Unsupported time input type. Expected LocalDateTime, Instant, Date, or LocalDate.");
213
+ }
214
+ function zonedFieldsToInstant(year, month, day, timeMs, timeZone) {
215
+ const hour = Math.floor(timeMs / 3_600_000);
216
+ const minute = Math.floor((timeMs % 3_600_000) / 60_000);
217
+ const second = Math.floor((timeMs % 60_000) / 1000);
218
+ const millisecond = timeMs % 1000;
219
+ const utcGuess = Date.UTC(year, month - 1, day, hour, minute, second, millisecond);
220
+ const offsetMin = getTimeZoneOffset(timeZone, { kind: "instant", epochMilliseconds: utcGuess }, "minutes");
221
+ let epochMs = utcGuess - offsetMin * 60_000;
222
+ const checkOffsetMin = getTimeZoneOffset(timeZone, { kind: "instant", epochMilliseconds: epochMs }, "minutes");
223
+ if (checkOffsetMin !== offsetMin) {
224
+ epochMs = utcGuess - checkOffsetMin * 60_000;
225
+ }
226
+ return { kind: "instant", epochMilliseconds: epochMs };
227
+ }
228
+ function reconstructResult(info, absDay, timeMs) {
229
+ const g = gregorianFromAbsoluteDay(absDay);
230
+ const hour = Math.floor(timeMs / 3_600_000);
231
+ const minute = Math.floor((timeMs % 3_600_000) / 60_000);
232
+ const second = Math.floor((timeMs % 60_000) / 1000);
233
+ const millisecond = timeMs % 1000;
234
+ if (info.kind === "local-date-time" || info.kind === "local-date") {
235
+ return localDateTime(localDate(g.year, g.month, g.day), localTime(hour, minute, second, millisecond));
236
+ }
237
+ if (info.kind === "instant") {
238
+ if (info.timeZone) {
239
+ return zonedFieldsToInstant(g.year, g.month, g.day, timeMs, info.timeZone);
240
+ }
241
+ return {
242
+ kind: "instant",
243
+ epochMilliseconds: Date.UTC(g.year, g.month - 1, g.day, hour, minute, second, millisecond),
244
+ };
245
+ }
246
+ if (info.kind === "date") {
247
+ if (info.timeZone) {
248
+ const inst = zonedFieldsToInstant(g.year, g.month, g.day, timeMs, info.timeZone);
249
+ return new Date(inst.epochMilliseconds);
250
+ }
251
+ return new Date(g.year, g.month - 1, g.day, hour, minute, second, millisecond);
252
+ }
253
+ throw new ChroneraError("CHRONERA_INVALID_DATE", "Cannot reconstruct time result.");
254
+ }
255
+ /**
256
+ * Checks whether the given timestamp falls inside an active working shift on a working day.
257
+ * Returns false during non-working days, holidays, weekends, lunch breaks, or outside shift hours.
258
+ *
259
+ * @param input - LocalDateTime, Instant, Date, or LocalDate to check.
260
+ * @param schedule - Working schedule, shift windows, and holiday configuration.
261
+ */
262
+ export function isBusinessHour(input, schedule = {}) {
263
+ const info = extractTimeInfo(input, schedule.timeZone);
264
+ if (!isWorkingScheduleDay(info.absDay, schedule)) {
265
+ return false;
266
+ }
267
+ const shifts = getShiftsForDay(info.absDay, schedule);
268
+ return shifts.some((shift) => info.timeMs >= shift.startMs && info.timeMs < shift.endMs);
269
+ }
270
+ export function addBusinessHours(input, hours, schedule = {}) {
271
+ if (!Number.isFinite(hours)) {
272
+ throw new ChroneraError("CHRONERA_OUT_OF_RANGE", "Hours must be a finite number.");
273
+ }
274
+ if (hours < 0) {
275
+ return subtractBusinessHours(input, -hours, schedule);
276
+ }
277
+ const info = extractTimeInfo(input, schedule.timeZone);
278
+ let currentAbs = info.absDay;
279
+ let currentMs = info.timeMs;
280
+ let remainingMs = Math.round(hours * 3_600_000);
281
+ let loopCount = 0;
282
+ const maxDays = 10_000;
283
+ while (loopCount++ < maxDays) {
284
+ if (!isWorkingScheduleDay(currentAbs, schedule)) {
285
+ currentAbs += 1;
286
+ currentMs = 0;
287
+ continue;
288
+ }
289
+ const shifts = getShiftsForDay(currentAbs, schedule);
290
+ for (const shift of shifts) {
291
+ if (currentMs >= shift.endMs) {
292
+ continue;
293
+ }
294
+ if (currentMs < shift.startMs) {
295
+ currentMs = shift.startMs;
296
+ }
297
+ if (remainingMs === 0) {
298
+ return reconstructResult(info, currentAbs, currentMs);
299
+ }
300
+ const availableInShift = shift.endMs - currentMs;
301
+ if (remainingMs <= availableInShift) {
302
+ currentMs += remainingMs;
303
+ remainingMs = 0;
304
+ return reconstructResult(info, currentAbs, currentMs);
305
+ }
306
+ remainingMs -= availableInShift;
307
+ currentMs = shift.endMs;
308
+ }
309
+ currentAbs += 1;
310
+ currentMs = 0;
311
+ }
312
+ throw new ChroneraError("CHRONERA_OUT_OF_RANGE", "Could not satisfy business hours within 10,000 calendar days.");
313
+ }
314
+ export function subtractBusinessHours(input, hours, schedule = {}) {
315
+ if (!Number.isFinite(hours)) {
316
+ throw new ChroneraError("CHRONERA_OUT_OF_RANGE", "Hours must be a finite number.");
317
+ }
318
+ if (hours < 0) {
319
+ return addBusinessHours(input, -hours, schedule);
320
+ }
321
+ const info = extractTimeInfo(input, schedule.timeZone);
322
+ let currentAbs = info.absDay;
323
+ let currentMs = info.timeMs;
324
+ let remainingMs = Math.round(hours * 3_600_000);
325
+ let loopCount = 0;
326
+ const maxDays = 10_000;
327
+ while (loopCount++ < maxDays) {
328
+ if (!isWorkingScheduleDay(currentAbs, schedule)) {
329
+ currentAbs -= 1;
330
+ currentMs = 86_400_000;
331
+ continue;
332
+ }
333
+ const shifts = getShiftsForDay(currentAbs, schedule);
334
+ for (let i = shifts.length - 1; i >= 0; i--) {
335
+ const shift = shifts[i];
336
+ if (currentMs <= shift.startMs) {
337
+ continue;
338
+ }
339
+ if (currentMs > shift.endMs) {
340
+ currentMs = shift.endMs;
341
+ }
342
+ if (remainingMs === 0) {
343
+ return reconstructResult(info, currentAbs, currentMs);
344
+ }
345
+ const availableInShift = currentMs - shift.startMs;
346
+ if (remainingMs <= availableInShift) {
347
+ currentMs -= remainingMs;
348
+ remainingMs = 0;
349
+ return reconstructResult(info, currentAbs, currentMs);
350
+ }
351
+ remainingMs -= availableInShift;
352
+ currentMs = shift.startMs;
353
+ }
354
+ currentAbs -= 1;
355
+ currentMs = 86_400_000;
356
+ }
357
+ throw new ChroneraError("CHRONERA_OUT_OF_RANGE", "Could not satisfy business hours within 10,000 calendar days.");
358
+ }
359
+ /**
360
+ * Computes the exact signed number of business hours elapsed between two timestamps (left - right).
361
+ * Non-working hours, lunch breaks, weekends, and statutory holidays are excluded.
362
+ *
363
+ * @param left - The later or earlier timestamp.
364
+ * @param right - The reference timestamp.
365
+ * @param schedule - Business schedule configuration.
366
+ * @returns Fractional business hours elapsed (positive if left > right, negative if left < right, 0 if equal).
367
+ */
368
+ export function diffInBusinessHours(left, right, schedule = {}) {
369
+ const leftInfo = extractTimeInfo(left, schedule.timeZone);
370
+ const rightInfo = extractTimeInfo(right, schedule.timeZone);
371
+ let cmp = 0;
372
+ if (leftInfo.absDay !== rightInfo.absDay) {
373
+ cmp = leftInfo.absDay - rightInfo.absDay;
374
+ }
375
+ else {
376
+ cmp = leftInfo.timeMs - rightInfo.timeMs;
377
+ }
378
+ if (cmp === 0) {
379
+ return 0;
380
+ }
381
+ if (cmp < 0) {
382
+ return -diffInBusinessHours(right, left, schedule);
383
+ }
384
+ const startDay = rightInfo.absDay;
385
+ const startMs = rightInfo.timeMs;
386
+ const endDay = leftInfo.absDay;
387
+ const endMs = leftInfo.timeMs;
388
+ let totalMs = 0;
389
+ for (let day = startDay; day <= endDay; day++) {
390
+ if (!isWorkingScheduleDay(day, schedule)) {
391
+ continue;
392
+ }
393
+ const shifts = getShiftsForDay(day, schedule);
394
+ for (const shift of shifts) {
395
+ const activeStart = day === startDay ? Math.max(shift.startMs, startMs) : shift.startMs;
396
+ const activeEnd = day === endDay ? Math.min(shift.endMs, endMs) : shift.endMs;
397
+ if (activeStart < activeEnd) {
398
+ totalMs += activeEnd - activeStart;
399
+ }
400
+ }
401
+ }
402
+ return totalMs / 3_600_000;
403
+ }
404
+ export function nextBusinessShift(input, schedule = {}, options = {}) {
405
+ const info = extractTimeInfo(input, schedule.timeZone);
406
+ const includeCurrent = options.includeCurrent ?? true;
407
+ let currentAbs = info.absDay;
408
+ let currentMs = info.timeMs;
409
+ let loopCount = 0;
410
+ const maxDays = 10_000;
411
+ while (loopCount++ < maxDays) {
412
+ if (isWorkingScheduleDay(currentAbs, schedule)) {
413
+ const shifts = getShiftsForDay(currentAbs, schedule);
414
+ for (const shift of shifts) {
415
+ if (currentAbs === info.absDay) {
416
+ if (includeCurrent) {
417
+ if (currentMs < shift.endMs) {
418
+ return {
419
+ start: reconstructResult(info, currentAbs, shift.startMs),
420
+ end: reconstructResult(info, currentAbs, shift.endMs),
421
+ };
422
+ }
423
+ }
424
+ else {
425
+ if (currentMs < shift.startMs) {
426
+ return {
427
+ start: reconstructResult(info, currentAbs, shift.startMs),
428
+ end: reconstructResult(info, currentAbs, shift.endMs),
429
+ };
430
+ }
431
+ }
432
+ }
433
+ else {
434
+ return {
435
+ start: reconstructResult(info, currentAbs, shift.startMs),
436
+ end: reconstructResult(info, currentAbs, shift.endMs),
437
+ };
438
+ }
439
+ }
440
+ }
441
+ currentAbs += 1;
442
+ currentMs = 0;
443
+ }
444
+ throw new ChroneraError("CHRONERA_OUT_OF_RANGE", "No upcoming business shift found within 10,000 calendar days.");
445
+ }
446
+ /**
447
+ * Returns the earliest shift start time for a given date as a LocalDateTime.
448
+ */
449
+ export function startOfBusinessDay(date, schedule = {}) {
450
+ const absDay = getAbsoluteDay(date instanceof Date
451
+ ? localDate(date.getFullYear(), date.getMonth() + 1, date.getDate())
452
+ : date.kind === "local-date-time"
453
+ ? date.date
454
+ : date);
455
+ const shifts = getShiftsForDay(absDay, schedule);
456
+ if (shifts.length === 0) {
457
+ throw new ChroneraError("CHRONERA_OUT_OF_RANGE", "No business shifts defined for the given date.");
458
+ }
459
+ const firstShift = shifts[0];
460
+ const g = gregorianFromAbsoluteDay(absDay);
461
+ const hour = Math.floor(firstShift.startMs / 3_600_000);
462
+ const minute = Math.floor((firstShift.startMs % 3_600_000) / 60_000);
463
+ const second = Math.floor((firstShift.startMs % 60_000) / 1000);
464
+ const millisecond = firstShift.startMs % 1000;
465
+ return localDateTime(localDate(g.year, g.month, g.day), localTime(hour, minute, second, millisecond));
466
+ }
467
+ /**
468
+ * Returns the latest shift end time for a given date as a LocalDateTime.
469
+ */
470
+ export function endOfBusinessDay(date, schedule = {}) {
471
+ const absDay = getAbsoluteDay(date instanceof Date
472
+ ? localDate(date.getFullYear(), date.getMonth() + 1, date.getDate())
473
+ : date.kind === "local-date-time"
474
+ ? date.date
475
+ : date);
476
+ const shifts = getShiftsForDay(absDay, schedule);
477
+ if (shifts.length === 0) {
478
+ throw new ChroneraError("CHRONERA_OUT_OF_RANGE", "No business shifts defined for the given date.");
479
+ }
480
+ const lastShift = shifts[shifts.length - 1];
481
+ const g = gregorianFromAbsoluteDay(absDay);
482
+ const hour = Math.floor(lastShift.endMs / 3_600_000);
483
+ const minute = Math.floor((lastShift.endMs % 3_600_000) / 60_000);
484
+ const second = Math.floor((lastShift.endMs % 60_000) / 1000);
485
+ const millisecond = lastShift.endMs % 1000;
486
+ return localDateTime(localDate(g.year, g.month, g.day), localTime(hour, minute, second, millisecond));
487
+ }
488
+ //# sourceMappingURL=business-hours.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"business-hours.js","sourceRoot":"","sources":["../../src/operations/business-hours.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,wBAAwB,EAAE,MAAM,qCAAqC,CAAC;AAC/E,OAAO,EAAE,8BAA8B,EAAE,MAAM,yBAAyB,CAAC;AACzE,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AACtD,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAClD,OAAO,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAC3D,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAClD,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAC7D,OAAO,EACL,2BAA2B,EAC3B,gBAAgB,GACjB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAiFlD;;GAEG;AACH,SAAS,kBAAkB,CAAC,KAAyB;IACnD,IACE,OAAO,KAAK,KAAK,QAAQ;QACzB,KAAK,KAAK,IAAI;QACd,KAAK,CAAC,IAAI,KAAK,YAAY,EAC3B,CAAC;QACD,OAAO,CACL,CAAC,CAAC,KAAK,CAAC,IAAI,GAAG,EAAE,GAAG,KAAK,CAAC,MAAM,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC,MAAM,CAAC,GAAG,IAAI;YAC7D,KAAK,CAAC,WAAW,CAClB,CAAC;IACJ,CAAC;IAED,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;QAC7B,MAAM,KAAK,GAAG,kDAAkD,CAAC,IAAI,CACnE,OAAO,CACR,CAAC;QACF,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,MAAM,IAAI,aAAa,CACrB,uBAAuB,EACvB,+BAA+B,KAAK,2CAA2C,CAChF,CAAC;QACJ,CAAC;QACD,MAAM,IAAI,GAAG,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAE,EAAE,EAAE,CAAC,CAAC;QAC5C,MAAM,MAAM,GAAG,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAE,EAAE,EAAE,CAAC,CAAC;QAC9C,MAAM,MAAM,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAC5D,MAAM,WAAW,GAAG,KAAK,CAAC,CAAC,CAAC;YAC1B,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,EAAE,CAAC;YAC9C,CAAC,CAAC,CAAC,CAAC;QAEN,IACE,IAAI,GAAG,CAAC;YACR,IAAI,GAAG,EAAE;YACT,MAAM,GAAG,CAAC;YACV,MAAM,GAAG,EAAE;YACX,MAAM,GAAG,CAAC;YACV,MAAM,GAAG,EAAE;YACX,WAAW,GAAG,CAAC;YACf,WAAW,GAAG,GAAG;YACjB,CAAC,IAAI,KAAK,EAAE,IAAI,CAAC,MAAM,GAAG,CAAC,IAAI,MAAM,GAAG,CAAC,IAAI,WAAW,GAAG,CAAC,CAAC,CAAC,EAC9D,CAAC;YACD,MAAM,IAAI,aAAa,CACrB,uBAAuB,EACvB,6BAA6B,KAAK,IAAI,CACvC,CAAC;QACJ,CAAC;QAED,OAAO,CAAC,CAAC,IAAI,GAAG,EAAE,GAAG,MAAM,CAAC,GAAG,EAAE,GAAG,MAAM,CAAC,GAAG,IAAI,GAAG,WAAW,CAAC;IACnE,CAAC;IAED,MAAM,IAAI,aAAa,CACrB,uBAAuB,EACvB,yEAAyE,CAC1E,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,SAAS,qBAAqB,CAC5B,MAA+B;IAE/B,IAAI,CAAC,MAAM,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACnC,kCAAkC;QAClC,OAAO;YACL;gBACE,OAAO,EAAE,CAAC,GAAG,SAAS;gBACtB,KAAK,EAAE,EAAE,GAAG,SAAS;gBACrB,UAAU,EAAE,CAAC,GAAG,SAAS;aAC1B;SACF,CAAC;IACJ,CAAC;IAED,MAAM,UAAU,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;QAClC,MAAM,OAAO,GAAG,kBAAkB,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;QAC5C,MAAM,KAAK,GAAG,kBAAkB,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;QACxC,IAAI,OAAO,IAAI,KAAK,EAAE,CAAC;YACrB,MAAM,IAAI,aAAa,CACrB,uBAAuB,EACvB,gBAAgB,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,8CAA8C,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAC/F,CAAC;QACJ,CAAC;QACD,OAAO;YACL,OAAO;YACP,KAAK;YACL,UAAU,EAAE,KAAK,GAAG,OAAO;SAC5B,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC;IAEjD,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,UAAU,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QAC3C,MAAM,IAAI,GAAG,UAAU,CAAC,CAAC,GAAG,CAAC,CAAE,CAAC;QAChC,MAAM,IAAI,GAAG,UAAU,CAAC,CAAC,CAAE,CAAC;QAC5B,IAAI,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,KAAK,EAAE,CAAC;YAC9B,MAAM,IAAI,aAAa,CACrB,uBAAuB,EACvB,wCAAwC,IAAI,CAAC,OAAO,QAAQ,IAAI,CAAC,KAAK,YAAY,IAAI,CAAC,OAAO,QAAQ,IAAI,CAAC,KAAK,MAAM,CACvH,CAAC;QACJ,CAAC;IACH,CAAC;IAED,OAAO,UAAU,CAAC;AACpB,CAAC;AAED;;GAEG;AACH,SAAS,eAAe,CACtB,MAAc,EACd,QAA0B;IAE1B,MAAM,GAAG,GAAG,eAAe,CAAC,MAAM,CAAC,CAAC;IACpC,IAAI,QAAQ,CAAC,SAAS,IAAI,QAAQ,CAAC,SAAS,CAAC,GAAG,CAAC,KAAK,SAAS,EAAE,CAAC;QAChE,OAAO,qBAAqB,CAAC,QAAQ,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC;IACxD,CAAC;IACD,OAAO,qBAAqB,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;AAChD,CAAC;AAED;;GAEG;AACH,SAAS,oBAAoB,CAC3B,MAAc,EACd,QAA0B;IAE1B,MAAM,GAAG,GAAG,eAAe,CAAC,MAAM,CAAC,CAAC;IAEpC,IAAI,eAAe,GAAG,KAAK,CAAC;IAC5B,IAAI,QAAQ,CAAC,QAAQ,IAAI,QAAQ,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACtD,eAAe,GAAG,QAAQ,CAAC,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;IACpD,CAAC;SAAM,IAAI,QAAQ,CAAC,WAAW,IAAI,QAAQ,CAAC,WAAW,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACnE,eAAe,GAAG,CAAC,QAAQ,CAAC,WAAW,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;IACxD,CAAC;SAAM,IAAI,QAAQ,CAAC,OAAO,EAAE,CAAC;QAC5B,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,kBAAkB,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;YACjD,IAAI,GAAG,CAAC,kBAAkB,EAAE,CAAC;gBAC3B,eAAe,GAAG,CAAC,GAAG,CAAC,kBAAkB,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;YAC1D,CAAC;iBAAM,CAAC;gBACN,eAAe,GAAG,GAAG,KAAK,CAAC,IAAI,GAAG,KAAK,CAAC,CAAC;YAC3C,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,eAAe,GAAG,GAAG,KAAK,CAAC,IAAI,GAAG,KAAK,CAAC,CAAC;QAC3C,CAAC;IACH,CAAC;SAAM,CAAC;QACN,eAAe,GAAG,GAAG,KAAK,CAAC,IAAI,GAAG,KAAK,CAAC,CAAC;IAC3C,CAAC;IAED,IAAI,CAAC,eAAe,EAAE,CAAC;QACrB,OAAO,KAAK,CAAC;IACf,CAAC;IAED,MAAM,OAAO,GAAG,wBAAwB,CAAC,MAAM,CAAC,CAAC;IACjD,MAAM,KAAK,GAAG,SAAS,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,KAAK,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC;IAElE,IAAI,QAAQ,CAAC,OAAO,IAAI,eAAe,CAAC,KAAK,EAAE,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;QACjE,OAAO,KAAK,CAAC;IACf,CAAC;IAED,IAAI,QAAQ,CAAC,QAAQ,EAAE,CAAC;QACtB,IAAI,OAAO,QAAQ,CAAC,QAAQ,KAAK,UAAU,EAAE,CAAC;YAC5C,IAAI,QAAQ,CAAC,QAAQ,CAAC,KAAK,CAAC;gBAAE,OAAO,KAAK,CAAC;QAC7C,CAAC;aAAM,IACL,OAAO,QAAQ,CAAC,QAAQ,KAAK,QAAQ;YACrC,OAAO,IAAI,QAAQ,CAAC,QAAQ,EAC5B,CAAC;YACD,IAAI,eAAe,CAAC,KAAK,EAAE,QAAQ,CAAC,QAAQ,CAAC;gBAAE,OAAO,KAAK,CAAC;QAC9D,CAAC;aAAM,IAAI,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC5C,IAAI,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,cAAc,CAAC,CAAC,CAAC,KAAK,MAAM,CAAC,EAAE,CAAC;gBAChE,OAAO,KAAK,CAAC;YACf,CAAC;QACH,CAAC;IACH,CAAC;IAED,MAAM,MAAM,GAAG,eAAe,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IACjD,OAAO,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC;AAC3B,CAAC;AASD,SAAS,eAAe,CACtB,KAAwB,EACxB,QAAqB;IAErB,IAAI,KAAK,YAAY,IAAI,EAAE,CAAC;QAC1B,IAAI,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC,EAAE,CAAC;YAClC,MAAM,IAAI,aAAa,CAAC,uBAAuB,EAAE,sBAAsB,CAAC,CAAC;QAC3E,CAAC;QACD,IAAI,QAAQ,EAAE,CAAC;YACb,gBAAgB,CAAC,QAAQ,CAAC,CAAC;YAC3B,MAAM,IAAI,GAAY;gBACpB,IAAI,EAAE,SAAS;gBACf,iBAAiB,EAAE,KAAK,CAAC,OAAO,EAAE;aACnC,CAAC;YACF,MAAM,CAAC,GAAG,2BAA2B,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;YACtD,MAAM,MAAM,GAAG,8BAA8B,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC;YACtE,MAAM,MAAM,GACV,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,EAAE,GAAG,CAAC,CAAC,MAAM,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,MAAM,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC,WAAW,CAAC;YACpE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC;QACpD,CAAC;QACD,MAAM,MAAM,GAAG,8BAA8B,CAC3C,KAAK,CAAC,WAAW,EAAE,EACnB,KAAK,CAAC,QAAQ,EAAE,GAAG,CAAC,EACpB,KAAK,CAAC,OAAO,EAAE,CAChB,CAAC;QACF,MAAM,MAAM,GACV,CAAC,CAAC,KAAK,CAAC,QAAQ,EAAE,GAAG,EAAE,GAAG,KAAK,CAAC,UAAU,EAAE,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC,UAAU,EAAE,CAAC;YACtE,IAAI;YACN,KAAK,CAAC,eAAe,EAAE,CAAC;QAC1B,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;IAC1C,CAAC;IAED,IACE,OAAO,KAAK,KAAK,QAAQ;QACzB,KAAK,KAAK,IAAI;QACd,MAAM,IAAI,KAAK;QACf,KAAK,CAAC,IAAI,KAAK,SAAS,EACxB,CAAC;QACD,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,iBAAiB,CAAC,EAAE,CAAC;YAC9C,MAAM,IAAI,aAAa,CACrB,0BAA0B,EAC1B,oCAAoC,CACrC,CAAC;QACJ,CAAC;QACD,IAAI,QAAQ,EAAE,CAAC;YACb,gBAAgB,CAAC,QAAQ,CAAC,CAAC;YAC3B,MAAM,CAAC,GAAG,2BAA2B,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;YACvD,MAAM,MAAM,GAAG,8BAA8B,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC;YACtE,MAAM,MAAM,GACV,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,EAAE,GAAG,CAAC,CAAC,MAAM,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,MAAM,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC,WAAW,CAAC;YACpE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC;QACvD,CAAC;QACD,MAAM,CAAC,GAAG,IAAI,IAAI,CAAC,KAAK,CAAC,iBAAiB,CAAC,CAAC;QAC5C,MAAM,MAAM,GAAG,8BAA8B,CAC3C,CAAC,CAAC,cAAc,EAAE,EAClB,CAAC,CAAC,WAAW,EAAE,GAAG,CAAC,EACnB,CAAC,CAAC,UAAU,EAAE,CACf,CAAC;QACF,MAAM,MAAM,GACV,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC,aAAa,EAAE,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,aAAa,EAAE,CAAC;YACnE,IAAI;YACN,CAAC,CAAC,kBAAkB,EAAE,CAAC;QACzB,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;IAC7C,CAAC;IAED,IACE,OAAO,KAAK,KAAK,QAAQ;QACzB,KAAK,KAAK,IAAI;QACd,MAAM,IAAI,KAAK;QACf,KAAK,CAAC,IAAI,KAAK,iBAAiB,EAChC,CAAC;QACD,MAAM,MAAM,GAAG,cAAc,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAC1C,MAAM,MAAM,GACV,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,GAAG,EAAE,GAAG,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC;YACnE,IAAI;YACN,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC;QACzB,OAAO,EAAE,IAAI,EAAE,iBAAiB,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;IACrD,CAAC;IAED,IACE,OAAO,KAAK,KAAK,QAAQ;QACzB,KAAK,KAAK,IAAI;QACd,MAAM,IAAI,KAAK;QACf,KAAK,CAAC,IAAI,KAAK,YAAY,EAC3B,CAAC;QACD,MAAM,MAAM,GAAG,cAAc,CAAC,KAAK,CAAC,CAAC;QACrC,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC;IACnD,CAAC;IAED,MAAM,IAAI,aAAa,CACrB,uBAAuB,EACvB,mFAAmF,CACpF,CAAC;AACJ,CAAC;AAED,SAAS,oBAAoB,CAC3B,IAAY,EACZ,KAAa,EACb,GAAW,EACX,MAAc,EACd,QAAoB;IAEpB,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;IAC5C,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,MAAM,GAAG,SAAS,CAAC,GAAG,MAAM,CAAC,CAAC;IACzD,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,MAAM,GAAG,MAAM,CAAC,GAAG,IAAI,CAAC,CAAC;IACpD,MAAM,WAAW,GAAG,MAAM,GAAG,IAAI,CAAC;IAElC,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CACvB,IAAI,EACJ,KAAK,GAAG,CAAC,EACT,GAAG,EACH,IAAI,EACJ,MAAM,EACN,MAAM,EACN,WAAW,CACZ,CAAC;IACF,MAAM,SAAS,GAAG,iBAAiB,CACjC,QAAQ,EACR,EAAE,IAAI,EAAE,SAAS,EAAE,iBAAiB,EAAE,QAAQ,EAAE,EAChD,SAAS,CACV,CAAC;IACF,IAAI,OAAO,GAAG,QAAQ,GAAG,SAAS,GAAG,MAAM,CAAC;IAE5C,MAAM,cAAc,GAAG,iBAAiB,CACtC,QAAQ,EACR,EAAE,IAAI,EAAE,SAAS,EAAE,iBAAiB,EAAE,OAAO,EAAE,EAC/C,SAAS,CACV,CAAC;IACF,IAAI,cAAc,KAAK,SAAS,EAAE,CAAC;QACjC,OAAO,GAAG,QAAQ,GAAG,cAAc,GAAG,MAAM,CAAC;IAC/C,CAAC;IACD,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,iBAAiB,EAAE,OAAO,EAAE,CAAC;AACzD,CAAC;AAED,SAAS,iBAAiB,CACxB,IAAuB,EACvB,MAAc,EACd,MAAc;IAEd,MAAM,CAAC,GAAG,wBAAwB,CAAC,MAAM,CAAC,CAAC;IAC3C,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;IAC5C,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,MAAM,GAAG,SAAS,CAAC,GAAG,MAAM,CAAC,CAAC;IACzD,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,MAAM,GAAG,MAAM,CAAC,GAAG,IAAI,CAAC,CAAC;IACpD,MAAM,WAAW,GAAG,MAAM,GAAG,IAAI,CAAC;IAElC,IAAI,IAAI,CAAC,IAAI,KAAK,iBAAiB,IAAI,IAAI,CAAC,IAAI,KAAK,YAAY,EAAE,CAAC;QAClE,OAAO,aAAa,CAClB,SAAS,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,GAAG,CAAC,EACjC,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,CAAC,CAC7C,CAAC;IACJ,CAAC;IAED,IAAI,IAAI,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;QAC5B,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YAClB,OAAO,oBAAoB,CACzB,CAAC,CAAC,IAAI,EACN,CAAC,CAAC,KAAK,EACP,CAAC,CAAC,GAAG,EACL,MAAM,EACN,IAAI,CAAC,QAAQ,CACd,CAAC;QACJ,CAAC;QACD,OAAO;YACL,IAAI,EAAE,SAAS;YACf,iBAAiB,EAAE,IAAI,CAAC,GAAG,CACzB,CAAC,CAAC,IAAI,EACN,CAAC,CAAC,KAAK,GAAG,CAAC,EACX,CAAC,CAAC,GAAG,EACL,IAAI,EACJ,MAAM,EACN,MAAM,EACN,WAAW,CACZ;SACF,CAAC;IACJ,CAAC;IAED,IAAI,IAAI,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;QACzB,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YAClB,MAAM,IAAI,GAAG,oBAAoB,CAC/B,CAAC,CAAC,IAAI,EACN,CAAC,CAAC,KAAK,EACP,CAAC,CAAC,GAAG,EACL,MAAM,EACN,IAAI,CAAC,QAAQ,CACd,CAAC;YACF,OAAO,IAAI,IAAI,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC;QAC1C,CAAC;QACD,OAAO,IAAI,IAAI,CACb,CAAC,CAAC,IAAI,EACN,CAAC,CAAC,KAAK,GAAG,CAAC,EACX,CAAC,CAAC,GAAG,EACL,IAAI,EACJ,MAAM,EACN,MAAM,EACN,WAAW,CACZ,CAAC;IACJ,CAAC;IAED,MAAM,IAAI,aAAa,CACrB,uBAAuB,EACvB,iCAAiC,CAClC,CAAC;AACJ,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,cAAc,CAC5B,KAAwB,EACxB,WAA6B,EAAE;IAE/B,MAAM,IAAI,GAAG,eAAe,CAAC,KAAK,EAAE,QAAQ,CAAC,QAAQ,CAAC,CAAC;IACvD,IAAI,CAAC,oBAAoB,CAAC,IAAI,CAAC,MAAM,EAAE,QAAQ,CAAC,EAAE,CAAC;QACjD,OAAO,KAAK,CAAC;IACf,CAAC;IAED,MAAM,MAAM,GAAG,eAAe,CAAC,IAAI,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IACtD,OAAO,MAAM,CAAC,IAAI,CAChB,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,IAAI,KAAK,CAAC,OAAO,IAAI,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC,KAAK,CACrE,CAAC;AACJ,CAAC;AA2BD,MAAM,UAAU,gBAAgB,CAC9B,KAAwB,EACxB,KAAa,EACb,WAA6B,EAAE;IAE/B,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;QAC5B,MAAM,IAAI,aAAa,CACrB,uBAAuB,EACvB,gCAAgC,CACjC,CAAC;IACJ,CAAC;IAED,IAAI,KAAK,GAAG,CAAC,EAAE,CAAC;QACd,OAAO,qBAAqB,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;IACxD,CAAC;IAED,MAAM,IAAI,GAAG,eAAe,CAAC,KAAK,EAAE,QAAQ,CAAC,QAAQ,CAAC,CAAC;IACvD,IAAI,UAAU,GAAG,IAAI,CAAC,MAAM,CAAC;IAC7B,IAAI,SAAS,GAAG,IAAI,CAAC,MAAM,CAAC;IAC5B,IAAI,WAAW,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,SAAS,CAAC,CAAC;IAEhD,IAAI,SAAS,GAAG,CAAC,CAAC;IAClB,MAAM,OAAO,GAAG,MAAM,CAAC;IAEvB,OAAO,SAAS,EAAE,GAAG,OAAO,EAAE,CAAC;QAC7B,IAAI,CAAC,oBAAoB,CAAC,UAAU,EAAE,QAAQ,CAAC,EAAE,CAAC;YAChD,UAAU,IAAI,CAAC,CAAC;YAChB,SAAS,GAAG,CAAC,CAAC;YACd,SAAS;QACX,CAAC;QAED,MAAM,MAAM,GAAG,eAAe,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;QAErD,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;YAC3B,IAAI,SAAS,IAAI,KAAK,CAAC,KAAK,EAAE,CAAC;gBAC7B,SAAS;YACX,CAAC;YAED,IAAI,SAAS,GAAG,KAAK,CAAC,OAAO,EAAE,CAAC;gBAC9B,SAAS,GAAG,KAAK,CAAC,OAAO,CAAC;YAC5B,CAAC;YAED,IAAI,WAAW,KAAK,CAAC,EAAE,CAAC;gBACtB,OAAO,iBAAiB,CAAC,IAAI,EAAE,UAAU,EAAE,SAAS,CAAC,CAAC;YACxD,CAAC;YAED,MAAM,gBAAgB,GAAG,KAAK,CAAC,KAAK,GAAG,SAAS,CAAC;YACjD,IAAI,WAAW,IAAI,gBAAgB,EAAE,CAAC;gBACpC,SAAS,IAAI,WAAW,CAAC;gBACzB,WAAW,GAAG,CAAC,CAAC;gBAChB,OAAO,iBAAiB,CAAC,IAAI,EAAE,UAAU,EAAE,SAAS,CAAC,CAAC;YACxD,CAAC;YAED,WAAW,IAAI,gBAAgB,CAAC;YAChC,SAAS,GAAG,KAAK,CAAC,KAAK,CAAC;QAC1B,CAAC;QAED,UAAU,IAAI,CAAC,CAAC;QAChB,SAAS,GAAG,CAAC,CAAC;IAChB,CAAC;IAED,MAAM,IAAI,aAAa,CACrB,uBAAuB,EACvB,+DAA+D,CAChE,CAAC;AACJ,CAAC;AAyBD,MAAM,UAAU,qBAAqB,CACnC,KAAwB,EACxB,KAAa,EACb,WAA6B,EAAE;IAE/B,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;QAC5B,MAAM,IAAI,aAAa,CACrB,uBAAuB,EACvB,gCAAgC,CACjC,CAAC;IACJ,CAAC;IAED,IAAI,KAAK,GAAG,CAAC,EAAE,CAAC;QACd,OAAO,gBAAgB,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;IACnD,CAAC;IAED,MAAM,IAAI,GAAG,eAAe,CAAC,KAAK,EAAE,QAAQ,CAAC,QAAQ,CAAC,CAAC;IACvD,IAAI,UAAU,GAAG,IAAI,CAAC,MAAM,CAAC;IAC7B,IAAI,SAAS,GAAG,IAAI,CAAC,MAAM,CAAC;IAC5B,IAAI,WAAW,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,SAAS,CAAC,CAAC;IAEhD,IAAI,SAAS,GAAG,CAAC,CAAC;IAClB,MAAM,OAAO,GAAG,MAAM,CAAC;IAEvB,OAAO,SAAS,EAAE,GAAG,OAAO,EAAE,CAAC;QAC7B,IAAI,CAAC,oBAAoB,CAAC,UAAU,EAAE,QAAQ,CAAC,EAAE,CAAC;YAChD,UAAU,IAAI,CAAC,CAAC;YAChB,SAAS,GAAG,UAAU,CAAC;YACvB,SAAS;QACX,CAAC;QAED,MAAM,MAAM,GAAG,eAAe,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;QAErD,KAAK,IAAI,CAAC,GAAG,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;YAC5C,MAAM,KAAK,GAAG,MAAM,CAAC,CAAC,CAAE,CAAC;YACzB,IAAI,SAAS,IAAI,KAAK,CAAC,OAAO,EAAE,CAAC;gBAC/B,SAAS;YACX,CAAC;YAED,IAAI,SAAS,GAAG,KAAK,CAAC,KAAK,EAAE,CAAC;gBAC5B,SAAS,GAAG,KAAK,CAAC,KAAK,CAAC;YAC1B,CAAC;YAED,IAAI,WAAW,KAAK,CAAC,EAAE,CAAC;gBACtB,OAAO,iBAAiB,CAAC,IAAI,EAAE,UAAU,EAAE,SAAS,CAAC,CAAC;YACxD,CAAC;YAED,MAAM,gBAAgB,GAAG,SAAS,GAAG,KAAK,CAAC,OAAO,CAAC;YACnD,IAAI,WAAW,IAAI,gBAAgB,EAAE,CAAC;gBACpC,SAAS,IAAI,WAAW,CAAC;gBACzB,WAAW,GAAG,CAAC,CAAC;gBAChB,OAAO,iBAAiB,CAAC,IAAI,EAAE,UAAU,EAAE,SAAS,CAAC,CAAC;YACxD,CAAC;YAED,WAAW,IAAI,gBAAgB,CAAC;YAChC,SAAS,GAAG,KAAK,CAAC,OAAO,CAAC;QAC5B,CAAC;QAED,UAAU,IAAI,CAAC,CAAC;QAChB,SAAS,GAAG,UAAU,CAAC;IACzB,CAAC;IAED,MAAM,IAAI,aAAa,CACrB,uBAAuB,EACvB,+DAA+D,CAChE,CAAC;AACJ,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,mBAAmB,CACjC,IAAuB,EACvB,KAAwB,EACxB,WAA6B,EAAE;IAE/B,MAAM,QAAQ,GAAG,eAAe,CAAC,IAAI,EAAE,QAAQ,CAAC,QAAQ,CAAC,CAAC;IAC1D,MAAM,SAAS,GAAG,eAAe,CAAC,KAAK,EAAE,QAAQ,CAAC,QAAQ,CAAC,CAAC;IAE5D,IAAI,GAAG,GAAG,CAAC,CAAC;IACZ,IAAI,QAAQ,CAAC,MAAM,KAAK,SAAS,CAAC,MAAM,EAAE,CAAC;QACzC,GAAG,GAAG,QAAQ,CAAC,MAAM,GAAG,SAAS,CAAC,MAAM,CAAC;IAC3C,CAAC;SAAM,CAAC;QACN,GAAG,GAAG,QAAQ,CAAC,MAAM,GAAG,SAAS,CAAC,MAAM,CAAC;IAC3C,CAAC;IAED,IAAI,GAAG,KAAK,CAAC,EAAE,CAAC;QACd,OAAO,CAAC,CAAC;IACX,CAAC;IAED,IAAI,GAAG,GAAG,CAAC,EAAE,CAAC;QACZ,OAAO,CAAC,mBAAmB,CAAC,KAAK,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAC;IACrD,CAAC;IAED,MAAM,QAAQ,GAAG,SAAS,CAAC,MAAM,CAAC;IAClC,MAAM,OAAO,GAAG,SAAS,CAAC,MAAM,CAAC;IACjC,MAAM,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAC;IAC/B,MAAM,KAAK,GAAG,QAAQ,CAAC,MAAM,CAAC;IAE9B,IAAI,OAAO,GAAG,CAAC,CAAC;IAEhB,KAAK,IAAI,GAAG,GAAG,QAAQ,EAAE,GAAG,IAAI,MAAM,EAAE,GAAG,EAAE,EAAE,CAAC;QAC9C,IAAI,CAAC,oBAAoB,CAAC,GAAG,EAAE,QAAQ,CAAC,EAAE,CAAC;YACzC,SAAS;QACX,CAAC;QAED,MAAM,MAAM,GAAG,eAAe,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;QAC9C,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;YAC3B,MAAM,WAAW,GACf,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC;YACtE,MAAM,SAAS,GACb,GAAG,KAAK,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC;YAE9D,IAAI,WAAW,GAAG,SAAS,EAAE,CAAC;gBAC5B,OAAO,IAAI,SAAS,GAAG,WAAW,CAAC;YACrC,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,OAAO,GAAG,SAAS,CAAC;AAC7B,CAAC;AAmCD,MAAM,UAAU,iBAAiB,CAC/B,KAAwB,EACxB,WAA6B,EAAE,EAC/B,UAAoC,EAAE;IAEtC,MAAM,IAAI,GAAG,eAAe,CAAC,KAAK,EAAE,QAAQ,CAAC,QAAQ,CAAC,CAAC;IACvD,MAAM,cAAc,GAAG,OAAO,CAAC,cAAc,IAAI,IAAI,CAAC;IAEtD,IAAI,UAAU,GAAG,IAAI,CAAC,MAAM,CAAC;IAC7B,IAAI,SAAS,GAAG,IAAI,CAAC,MAAM,CAAC;IAC5B,IAAI,SAAS,GAAG,CAAC,CAAC;IAClB,MAAM,OAAO,GAAG,MAAM,CAAC;IAEvB,OAAO,SAAS,EAAE,GAAG,OAAO,EAAE,CAAC;QAC7B,IAAI,oBAAoB,CAAC,UAAU,EAAE,QAAQ,CAAC,EAAE,CAAC;YAC/C,MAAM,MAAM,GAAG,eAAe,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;YACrD,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;gBAC3B,IAAI,UAAU,KAAK,IAAI,CAAC,MAAM,EAAE,CAAC;oBAC/B,IAAI,cAAc,EAAE,CAAC;wBACnB,IAAI,SAAS,GAAG,KAAK,CAAC,KAAK,EAAE,CAAC;4BAC5B,OAAO;gCACL,KAAK,EAAE,iBAAiB,CAAC,IAAI,EAAE,UAAU,EAAE,KAAK,CAAC,OAAO,CAAC;gCACzD,GAAG,EAAE,iBAAiB,CAAC,IAAI,EAAE,UAAU,EAAE,KAAK,CAAC,KAAK,CAAC;6BACtD,CAAC;wBACJ,CAAC;oBACH,CAAC;yBAAM,CAAC;wBACN,IAAI,SAAS,GAAG,KAAK,CAAC,OAAO,EAAE,CAAC;4BAC9B,OAAO;gCACL,KAAK,EAAE,iBAAiB,CAAC,IAAI,EAAE,UAAU,EAAE,KAAK,CAAC,OAAO,CAAC;gCACzD,GAAG,EAAE,iBAAiB,CAAC,IAAI,EAAE,UAAU,EAAE,KAAK,CAAC,KAAK,CAAC;6BACtD,CAAC;wBACJ,CAAC;oBACH,CAAC;gBACH,CAAC;qBAAM,CAAC;oBACN,OAAO;wBACL,KAAK,EAAE,iBAAiB,CAAC,IAAI,EAAE,UAAU,EAAE,KAAK,CAAC,OAAO,CAAC;wBACzD,GAAG,EAAE,iBAAiB,CAAC,IAAI,EAAE,UAAU,EAAE,KAAK,CAAC,KAAK,CAAC;qBACtD,CAAC;gBACJ,CAAC;YACH,CAAC;QACH,CAAC;QAED,UAAU,IAAI,CAAC,CAAC;QAChB,SAAS,GAAG,CAAC,CAAC;IAChB,CAAC;IAED,MAAM,IAAI,aAAa,CACrB,uBAAuB,EACvB,+DAA+D,CAChE,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,kBAAkB,CAChC,IAAsC,EACtC,WAA6B,EAAE;IAE/B,MAAM,MAAM,GAAG,cAAc,CAC3B,IAAI,YAAY,IAAI;QAClB,CAAC,CAAC,SAAS,CAAC,IAAI,CAAC,WAAW,EAAE,EAAE,IAAI,CAAC,QAAQ,EAAE,GAAG,CAAC,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC;QACpE,CAAC,CAAC,IAAI,CAAC,IAAI,KAAK,iBAAiB;YAC/B,CAAC,CAAC,IAAI,CAAC,IAAI;YACX,CAAC,CAAC,IAAI,CACX,CAAC;IACF,MAAM,MAAM,GAAG,eAAe,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IACjD,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACxB,MAAM,IAAI,aAAa,CACrB,uBAAuB,EACvB,gDAAgD,CACjD,CAAC;IACJ,CAAC;IACD,MAAM,UAAU,GAAG,MAAM,CAAC,CAAC,CAAE,CAAC;IAC9B,MAAM,CAAC,GAAG,wBAAwB,CAAC,MAAM,CAAC,CAAC;IAC3C,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,OAAO,GAAG,SAAS,CAAC,CAAC;IACxD,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,UAAU,CAAC,OAAO,GAAG,SAAS,CAAC,GAAG,MAAM,CAAC,CAAC;IACrE,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,UAAU,CAAC,OAAO,GAAG,MAAM,CAAC,GAAG,IAAI,CAAC,CAAC;IAChE,MAAM,WAAW,GAAG,UAAU,CAAC,OAAO,GAAG,IAAI,CAAC;IAC9C,OAAO,aAAa,CAClB,SAAS,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,GAAG,CAAC,EACjC,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,CAAC,CAC7C,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,gBAAgB,CAC9B,IAAsC,EACtC,WAA6B,EAAE;IAE/B,MAAM,MAAM,GAAG,cAAc,CAC3B,IAAI,YAAY,IAAI;QAClB,CAAC,CAAC,SAAS,CAAC,IAAI,CAAC,WAAW,EAAE,EAAE,IAAI,CAAC,QAAQ,EAAE,GAAG,CAAC,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC;QACpE,CAAC,CAAC,IAAI,CAAC,IAAI,KAAK,iBAAiB;YAC/B,CAAC,CAAC,IAAI,CAAC,IAAI;YACX,CAAC,CAAC,IAAI,CACX,CAAC;IACF,MAAM,MAAM,GAAG,eAAe,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IACjD,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACxB,MAAM,IAAI,aAAa,CACrB,uBAAuB,EACvB,gDAAgD,CACjD,CAAC;IACJ,CAAC;IACD,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAE,CAAC;IAC7C,MAAM,CAAC,GAAG,wBAAwB,CAAC,MAAM,CAAC,CAAC;IAC3C,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,KAAK,GAAG,SAAS,CAAC,CAAC;IACrD,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,SAAS,CAAC,KAAK,GAAG,SAAS,CAAC,GAAG,MAAM,CAAC,CAAC;IAClE,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,SAAS,CAAC,KAAK,GAAG,MAAM,CAAC,GAAG,IAAI,CAAC,CAAC;IAC7D,MAAM,WAAW,GAAG,SAAS,CAAC,KAAK,GAAG,IAAI,CAAC;IAC3C,OAAO,aAAa,CAClB,SAAS,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,GAAG,CAAC,EACjC,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,CAAC,CAC7C,CAAC;AACJ,CAAC","sourcesContent":["import { gregorianFromAbsoluteDay } from \"../calendar/gregory/absolute-day.js\";\nimport { absoluteDayFromGregorianFields } from \"../core/absolute-day.js\";\nimport { getIsoDayOfWeek } from \"../core/iso-week.js\";\nimport { localDate } from \"../core/local-date.js\";\nimport { localDateTime } from \"../core/local-date-time.js\";\nimport { localTime } from \"../core/local-time.js\";\nimport { ChroneraError } from \"../errors/errors.js\";\nimport { getHolidayCalendar } from \"../holidays/registry.js\";\nimport {\n projectInstantToZonedFields,\n validateTimeZone,\n} from \"../runtime/timezone.js\";\nimport { getAbsoluteDay } from \"./convenience.js\";\nimport { isPublicHoliday } from \"./holidays.js\";\nimport { getTimeZoneOffset } from \"./timezone.js\";\n\nimport type { CountryCode, HolidayCalendar } from \"../holidays/types.js\";\nimport type { BusinessDaysOptions } from \"./business-days.js\";\nimport type {\n Instant,\n LocalDate,\n LocalDateTime,\n LocalTime,\n TimeZoneId,\n} from \"../public-types.js\";\n\n/**\n * Defines an active working shift window within a day.\n */\nexport interface ShiftWindow {\n /**\n * Shift start time as \"HH:mm\", \"HH:mm:ss\", or a LocalTime object (e.g. \"08:30\").\n */\n readonly start: string | LocalTime;\n /**\n * Shift end time as \"HH:mm\", \"HH:mm:ss\", or a LocalTime object (e.g. \"17:30\").\n */\n readonly end: string | LocalTime;\n}\n\n/**\n * Business schedule configuration for working hours, shifts, weekends, and holidays.\n */\nexport interface BusinessSchedule {\n /**\n * Working days of the week as ISO weekday numbers (1 = Monday, 7 = Sunday).\n * Defaults to [1, 2, 3, 4, 5] (Monday to Friday), or country weekend defaults.\n */\n readonly workDays?: readonly number[];\n /**\n * Standard daily shifts. Defaults to a single shift 09:00 - 17:00.\n */\n readonly shifts?: readonly ShiftWindow[];\n /**\n * Day-of-week specific shift overrides (e.g. `{ 5: [{ start: \"08:30\", end: \"12:00\" }] }` for Friday half-day).\n */\n readonly dayShifts?: Partial<Record<number, readonly ShiftWindow[]>>;\n /**\n * Country code (e.g. \"th\", \"jp\", \"us\", \"gb\", \"de\") or custom HolidayCalendar for automatic holiday exclusion.\n */\n readonly country?: CountryCode | string | HolidayCalendar;\n /**\n * Custom holiday list, holiday predicate, or holiday calendar.\n */\n readonly holidays?: BusinessDaysOptions[\"holidays\"];\n /**\n * Custom weekend days (e.g. [6, 7] for Sat-Sun, or [4, 5] for Iran).\n */\n readonly weekendDays?: readonly number[];\n /**\n * TimeZone identifier (e.g. \"Asia/Bangkok\", \"America/New_York\", \"UTC\").\n * Required when evaluating Instant or Date across specific local working hours.\n */\n readonly timeZone?: TimeZoneId;\n}\n\n/**\n * Normalized shift representation in milliseconds from midnight [0, 86_400_000].\n */\nexport interface NormalizedShift {\n readonly startMs: number;\n readonly endMs: number;\n readonly durationMs: number;\n}\n\n/**\n * Result representing an upcoming or active business shift window.\n */\nexport interface NextBusinessShiftResult<T> {\n readonly start: T;\n readonly end: T;\n}\n\nexport type BusinessTimeInput = LocalDateTime | Instant | Date | LocalDate;\n\n/**\n * Parses a shift time string (\"HH:mm\", \"HH:mm:ss\") or LocalTime into milliseconds from midnight.\n */\nfunction parseShiftTimeToMs(input: string | LocalTime): number {\n if (\n typeof input === \"object\" &&\n input !== null &&\n input.kind === \"local-time\"\n ) {\n return (\n ((input.hour * 60 + input.minute) * 60 + input.second) * 1000 +\n input.millisecond\n );\n }\n\n if (typeof input === \"string\") {\n const trimmed = input.trim();\n const match = /^(\\d{1,2}):(\\d{2})(?::(\\d{2}))?(?:\\.(\\d{1,3}))?$/.exec(\n trimmed,\n );\n if (!match) {\n throw new ChroneraError(\n \"CHRONERA_INVALID_TIME\",\n `Invalid shift time string: \"${input}\". Expected format \"HH:mm\" or \"HH:mm:ss\".`,\n );\n }\n const hour = Number.parseInt(match[1]!, 10);\n const minute = Number.parseInt(match[2]!, 10);\n const second = match[3] ? Number.parseInt(match[3], 10) : 0;\n const millisecond = match[4]\n ? Number.parseInt(match[4].padEnd(3, \"0\"), 10)\n : 0;\n\n if (\n hour < 0 ||\n hour > 24 ||\n minute < 0 ||\n minute > 59 ||\n second < 0 ||\n second > 59 ||\n millisecond < 0 ||\n millisecond > 999 ||\n (hour === 24 && (minute > 0 || second > 0 || millisecond > 0))\n ) {\n throw new ChroneraError(\n \"CHRONERA_OUT_OF_RANGE\",\n `Shift time out of range: \"${input}\".`,\n );\n }\n\n return ((hour * 60 + minute) * 60 + second) * 1000 + millisecond;\n }\n\n throw new ChroneraError(\n \"CHRONERA_INVALID_TIME\",\n 'Shift time must be a string formatted as \"HH:mm\" or a LocalTime object.',\n );\n}\n\n/**\n * Normalizes and validates an array of shift windows for a single day.\n */\nfunction normalizeShiftWindows(\n shifts?: readonly ShiftWindow[],\n): readonly NormalizedShift[] {\n if (!shifts || shifts.length === 0) {\n // Default 09:00 - 17:00 (8 hours)\n return [\n {\n startMs: 9 * 3_600_000,\n endMs: 17 * 3_600_000,\n durationMs: 8 * 3_600_000,\n },\n ];\n }\n\n const normalized = shifts.map((w) => {\n const startMs = parseShiftTimeToMs(w.start);\n const endMs = parseShiftTimeToMs(w.end);\n if (startMs >= endMs) {\n throw new ChroneraError(\n \"CHRONERA_OUT_OF_RANGE\",\n `Shift start (${String(w.start)}) must be strictly earlier than shift end (${String(w.end)}).`,\n );\n }\n return {\n startMs,\n endMs,\n durationMs: endMs - startMs,\n };\n });\n\n normalized.sort((a, b) => a.startMs - b.startMs);\n\n for (let i = 1; i < normalized.length; i++) {\n const prev = normalized[i - 1]!;\n const curr = normalized[i]!;\n if (curr.startMs < prev.endMs) {\n throw new ChroneraError(\n \"CHRONERA_OUT_OF_RANGE\",\n `Overlapping shift windows detected: [${prev.startMs}ms - ${prev.endMs}ms] and [${curr.startMs}ms - ${curr.endMs}ms].`,\n );\n }\n }\n\n return normalized;\n}\n\n/**\n * Returns the normalized shift windows for a given absolute day.\n */\nfunction getShiftsForDay(\n absDay: number,\n schedule: BusinessSchedule,\n): readonly NormalizedShift[] {\n const dow = getIsoDayOfWeek(absDay);\n if (schedule.dayShifts && schedule.dayShifts[dow] !== undefined) {\n return normalizeShiftWindows(schedule.dayShifts[dow]);\n }\n return normalizeShiftWindows(schedule.shifts);\n}\n\n/**\n * Checks whether a given absolute day is a working day under the provided schedule.\n */\nfunction isWorkingScheduleDay(\n absDay: number,\n schedule: BusinessSchedule,\n): boolean {\n const dow = getIsoDayOfWeek(absDay);\n\n let isWorkDayOfWeek = false;\n if (schedule.workDays && schedule.workDays.length > 0) {\n isWorkDayOfWeek = schedule.workDays.includes(dow);\n } else if (schedule.weekendDays && schedule.weekendDays.length > 0) {\n isWorkDayOfWeek = !schedule.weekendDays.includes(dow);\n } else if (schedule.country) {\n try {\n const cal = getHolidayCalendar(schedule.country);\n if (cal.defaultWeekendDays) {\n isWorkDayOfWeek = !cal.defaultWeekendDays.includes(dow);\n } else {\n isWorkDayOfWeek = dow !== 6 && dow !== 7;\n }\n } catch {\n isWorkDayOfWeek = dow !== 6 && dow !== 7;\n }\n } else {\n isWorkDayOfWeek = dow !== 6 && dow !== 7;\n }\n\n if (!isWorkDayOfWeek) {\n return false;\n }\n\n const gFields = gregorianFromAbsoluteDay(absDay);\n const gDate = localDate(gFields.year, gFields.month, gFields.day);\n\n if (schedule.country && isPublicHoliday(gDate, schedule.country)) {\n return false;\n }\n\n if (schedule.holidays) {\n if (typeof schedule.holidays === \"function\") {\n if (schedule.holidays(gDate)) return false;\n } else if (\n typeof schedule.holidays === \"string\" ||\n \"rules\" in schedule.holidays\n ) {\n if (isPublicHoliday(gDate, schedule.holidays)) return false;\n } else if (Array.isArray(schedule.holidays)) {\n if (schedule.holidays.some((h) => getAbsoluteDay(h) === absDay)) {\n return false;\n }\n }\n }\n\n const shifts = getShiftsForDay(absDay, schedule);\n return shifts.length > 0;\n}\n\ninterface ExtractedTimeInfo {\n readonly kind: \"local-date-time\" | \"instant\" | \"date\" | \"local-date\";\n readonly absDay: number;\n readonly timeMs: number;\n readonly timeZone?: TimeZoneId;\n}\n\nfunction extractTimeInfo(\n input: BusinessTimeInput,\n timeZone?: TimeZoneId,\n): ExtractedTimeInfo {\n if (input instanceof Date) {\n if (Number.isNaN(input.getTime())) {\n throw new ChroneraError(\"CHRONERA_INVALID_DATE\", \"Invalid Date object.\");\n }\n if (timeZone) {\n validateTimeZone(timeZone);\n const inst: Instant = {\n kind: \"instant\",\n epochMilliseconds: input.getTime(),\n };\n const f = projectInstantToZonedFields(inst, timeZone);\n const absDay = absoluteDayFromGregorianFields(f.year, f.month, f.day);\n const timeMs =\n ((f.hour * 60 + f.minute) * 60 + f.second) * 1000 + f.millisecond;\n return { kind: \"date\", absDay, timeMs, timeZone };\n }\n const absDay = absoluteDayFromGregorianFields(\n input.getFullYear(),\n input.getMonth() + 1,\n input.getDate(),\n );\n const timeMs =\n ((input.getHours() * 60 + input.getMinutes()) * 60 + input.getSeconds()) *\n 1000 +\n input.getMilliseconds();\n return { kind: \"date\", absDay, timeMs };\n }\n\n if (\n typeof input === \"object\" &&\n input !== null &&\n \"kind\" in input &&\n input.kind === \"instant\"\n ) {\n if (!Number.isFinite(input.epochMilliseconds)) {\n throw new ChroneraError(\n \"CHRONERA_INVALID_INSTANT\",\n \"Invalid Instant epochMilliseconds.\",\n );\n }\n if (timeZone) {\n validateTimeZone(timeZone);\n const f = projectInstantToZonedFields(input, timeZone);\n const absDay = absoluteDayFromGregorianFields(f.year, f.month, f.day);\n const timeMs =\n ((f.hour * 60 + f.minute) * 60 + f.second) * 1000 + f.millisecond;\n return { kind: \"instant\", absDay, timeMs, timeZone };\n }\n const d = new Date(input.epochMilliseconds);\n const absDay = absoluteDayFromGregorianFields(\n d.getUTCFullYear(),\n d.getUTCMonth() + 1,\n d.getUTCDate(),\n );\n const timeMs =\n ((d.getUTCHours() * 60 + d.getUTCMinutes()) * 60 + d.getUTCSeconds()) *\n 1000 +\n d.getUTCMilliseconds();\n return { kind: \"instant\", absDay, timeMs };\n }\n\n if (\n typeof input === \"object\" &&\n input !== null &&\n \"kind\" in input &&\n input.kind === \"local-date-time\"\n ) {\n const absDay = getAbsoluteDay(input.date);\n const timeMs =\n ((input.time.hour * 60 + input.time.minute) * 60 + input.time.second) *\n 1000 +\n input.time.millisecond;\n return { kind: \"local-date-time\", absDay, timeMs };\n }\n\n if (\n typeof input === \"object\" &&\n input !== null &&\n \"kind\" in input &&\n input.kind === \"local-date\"\n ) {\n const absDay = getAbsoluteDay(input);\n return { kind: \"local-date\", absDay, timeMs: 0 };\n }\n\n throw new ChroneraError(\n \"CHRONERA_INVALID_DATE\",\n \"Unsupported time input type. Expected LocalDateTime, Instant, Date, or LocalDate.\",\n );\n}\n\nfunction zonedFieldsToInstant(\n year: number,\n month: number,\n day: number,\n timeMs: number,\n timeZone: TimeZoneId,\n): Instant {\n const hour = Math.floor(timeMs / 3_600_000);\n const minute = Math.floor((timeMs % 3_600_000) / 60_000);\n const second = Math.floor((timeMs % 60_000) / 1000);\n const millisecond = timeMs % 1000;\n\n const utcGuess = Date.UTC(\n year,\n month - 1,\n day,\n hour,\n minute,\n second,\n millisecond,\n );\n const offsetMin = getTimeZoneOffset(\n timeZone,\n { kind: \"instant\", epochMilliseconds: utcGuess },\n \"minutes\",\n );\n let epochMs = utcGuess - offsetMin * 60_000;\n\n const checkOffsetMin = getTimeZoneOffset(\n timeZone,\n { kind: \"instant\", epochMilliseconds: epochMs },\n \"minutes\",\n );\n if (checkOffsetMin !== offsetMin) {\n epochMs = utcGuess - checkOffsetMin * 60_000;\n }\n return { kind: \"instant\", epochMilliseconds: epochMs };\n}\n\nfunction reconstructResult(\n info: ExtractedTimeInfo,\n absDay: number,\n timeMs: number,\n): LocalDateTime | Instant | Date {\n const g = gregorianFromAbsoluteDay(absDay);\n const hour = Math.floor(timeMs / 3_600_000);\n const minute = Math.floor((timeMs % 3_600_000) / 60_000);\n const second = Math.floor((timeMs % 60_000) / 1000);\n const millisecond = timeMs % 1000;\n\n if (info.kind === \"local-date-time\" || info.kind === \"local-date\") {\n return localDateTime(\n localDate(g.year, g.month, g.day),\n localTime(hour, minute, second, millisecond),\n );\n }\n\n if (info.kind === \"instant\") {\n if (info.timeZone) {\n return zonedFieldsToInstant(\n g.year,\n g.month,\n g.day,\n timeMs,\n info.timeZone,\n );\n }\n return {\n kind: \"instant\",\n epochMilliseconds: Date.UTC(\n g.year,\n g.month - 1,\n g.day,\n hour,\n minute,\n second,\n millisecond,\n ),\n };\n }\n\n if (info.kind === \"date\") {\n if (info.timeZone) {\n const inst = zonedFieldsToInstant(\n g.year,\n g.month,\n g.day,\n timeMs,\n info.timeZone,\n );\n return new Date(inst.epochMilliseconds);\n }\n return new Date(\n g.year,\n g.month - 1,\n g.day,\n hour,\n minute,\n second,\n millisecond,\n );\n }\n\n throw new ChroneraError(\n \"CHRONERA_INVALID_DATE\",\n \"Cannot reconstruct time result.\",\n );\n}\n\n/**\n * Checks whether the given timestamp falls inside an active working shift on a working day.\n * Returns false during non-working days, holidays, weekends, lunch breaks, or outside shift hours.\n *\n * @param input - LocalDateTime, Instant, Date, or LocalDate to check.\n * @param schedule - Working schedule, shift windows, and holiday configuration.\n */\nexport function isBusinessHour(\n input: BusinessTimeInput,\n schedule: BusinessSchedule = {},\n): boolean {\n const info = extractTimeInfo(input, schedule.timeZone);\n if (!isWorkingScheduleDay(info.absDay, schedule)) {\n return false;\n }\n\n const shifts = getShiftsForDay(info.absDay, schedule);\n return shifts.some(\n (shift) => info.timeMs >= shift.startMs && info.timeMs < shift.endMs,\n );\n}\n\n/**\n * Adds business hours to a timestamp, skipping non-working hours, breaks, weekends, and statutory holidays.\n * If the starting timestamp falls outside business hours (e.g. at night or during lunch),\n * the SLA calculation automatically starts ticking from the start of the next active shift.\n *\n * @param input - The starting LocalDateTime, Instant, Date, or LocalDate.\n * @param hours - Number of business hours to add (supports fractional hours like 2.5).\n * @param schedule - Business schedule defining shift windows, weekends, holidays, and timezone.\n * @returns The resulting deadline in the same type as the input.\n */\nexport function addBusinessHours(\n input: LocalDate,\n hours: number,\n schedule?: BusinessSchedule,\n): LocalDateTime;\nexport function addBusinessHours<T extends LocalDateTime | Instant | Date>(\n input: T,\n hours: number,\n schedule?: BusinessSchedule,\n): T;\nexport function addBusinessHours(\n input: BusinessTimeInput,\n hours: number,\n schedule?: BusinessSchedule,\n): LocalDateTime | Instant | Date;\nexport function addBusinessHours(\n input: BusinessTimeInput,\n hours: number,\n schedule: BusinessSchedule = {},\n): BusinessTimeInput {\n if (!Number.isFinite(hours)) {\n throw new ChroneraError(\n \"CHRONERA_OUT_OF_RANGE\",\n \"Hours must be a finite number.\",\n );\n }\n\n if (hours < 0) {\n return subtractBusinessHours(input, -hours, schedule);\n }\n\n const info = extractTimeInfo(input, schedule.timeZone);\n let currentAbs = info.absDay;\n let currentMs = info.timeMs;\n let remainingMs = Math.round(hours * 3_600_000);\n\n let loopCount = 0;\n const maxDays = 10_000;\n\n while (loopCount++ < maxDays) {\n if (!isWorkingScheduleDay(currentAbs, schedule)) {\n currentAbs += 1;\n currentMs = 0;\n continue;\n }\n\n const shifts = getShiftsForDay(currentAbs, schedule);\n\n for (const shift of shifts) {\n if (currentMs >= shift.endMs) {\n continue;\n }\n\n if (currentMs < shift.startMs) {\n currentMs = shift.startMs;\n }\n\n if (remainingMs === 0) {\n return reconstructResult(info, currentAbs, currentMs);\n }\n\n const availableInShift = shift.endMs - currentMs;\n if (remainingMs <= availableInShift) {\n currentMs += remainingMs;\n remainingMs = 0;\n return reconstructResult(info, currentAbs, currentMs);\n }\n\n remainingMs -= availableInShift;\n currentMs = shift.endMs;\n }\n\n currentAbs += 1;\n currentMs = 0;\n }\n\n throw new ChroneraError(\n \"CHRONERA_OUT_OF_RANGE\",\n \"Could not satisfy business hours within 10,000 calendar days.\",\n );\n}\n\n/**\n * Subtracts business hours from a timestamp backwards, skipping non-working hours, breaks, weekends, and holidays.\n *\n * @param input - The starting LocalDateTime, Instant, Date, or LocalDate.\n * @param hours - Number of business hours to subtract (supports fractional hours).\n * @param schedule - Business schedule configuration.\n * @returns The resulting earlier timestamp in the same type as the input.\n */\nexport function subtractBusinessHours(\n input: LocalDate,\n hours: number,\n schedule?: BusinessSchedule,\n): LocalDateTime;\nexport function subtractBusinessHours<T extends LocalDateTime | Instant | Date>(\n input: T,\n hours: number,\n schedule?: BusinessSchedule,\n): T;\nexport function subtractBusinessHours(\n input: BusinessTimeInput,\n hours: number,\n schedule?: BusinessSchedule,\n): LocalDateTime | Instant | Date;\nexport function subtractBusinessHours(\n input: BusinessTimeInput,\n hours: number,\n schedule: BusinessSchedule = {},\n): BusinessTimeInput {\n if (!Number.isFinite(hours)) {\n throw new ChroneraError(\n \"CHRONERA_OUT_OF_RANGE\",\n \"Hours must be a finite number.\",\n );\n }\n\n if (hours < 0) {\n return addBusinessHours(input, -hours, schedule);\n }\n\n const info = extractTimeInfo(input, schedule.timeZone);\n let currentAbs = info.absDay;\n let currentMs = info.timeMs;\n let remainingMs = Math.round(hours * 3_600_000);\n\n let loopCount = 0;\n const maxDays = 10_000;\n\n while (loopCount++ < maxDays) {\n if (!isWorkingScheduleDay(currentAbs, schedule)) {\n currentAbs -= 1;\n currentMs = 86_400_000;\n continue;\n }\n\n const shifts = getShiftsForDay(currentAbs, schedule);\n\n for (let i = shifts.length - 1; i >= 0; i--) {\n const shift = shifts[i]!;\n if (currentMs <= shift.startMs) {\n continue;\n }\n\n if (currentMs > shift.endMs) {\n currentMs = shift.endMs;\n }\n\n if (remainingMs === 0) {\n return reconstructResult(info, currentAbs, currentMs);\n }\n\n const availableInShift = currentMs - shift.startMs;\n if (remainingMs <= availableInShift) {\n currentMs -= remainingMs;\n remainingMs = 0;\n return reconstructResult(info, currentAbs, currentMs);\n }\n\n remainingMs -= availableInShift;\n currentMs = shift.startMs;\n }\n\n currentAbs -= 1;\n currentMs = 86_400_000;\n }\n\n throw new ChroneraError(\n \"CHRONERA_OUT_OF_RANGE\",\n \"Could not satisfy business hours within 10,000 calendar days.\",\n );\n}\n\n/**\n * Computes the exact signed number of business hours elapsed between two timestamps (left - right).\n * Non-working hours, lunch breaks, weekends, and statutory holidays are excluded.\n *\n * @param left - The later or earlier timestamp.\n * @param right - The reference timestamp.\n * @param schedule - Business schedule configuration.\n * @returns Fractional business hours elapsed (positive if left > right, negative if left < right, 0 if equal).\n */\nexport function diffInBusinessHours(\n left: BusinessTimeInput,\n right: BusinessTimeInput,\n schedule: BusinessSchedule = {},\n): number {\n const leftInfo = extractTimeInfo(left, schedule.timeZone);\n const rightInfo = extractTimeInfo(right, schedule.timeZone);\n\n let cmp = 0;\n if (leftInfo.absDay !== rightInfo.absDay) {\n cmp = leftInfo.absDay - rightInfo.absDay;\n } else {\n cmp = leftInfo.timeMs - rightInfo.timeMs;\n }\n\n if (cmp === 0) {\n return 0;\n }\n\n if (cmp < 0) {\n return -diffInBusinessHours(right, left, schedule);\n }\n\n const startDay = rightInfo.absDay;\n const startMs = rightInfo.timeMs;\n const endDay = leftInfo.absDay;\n const endMs = leftInfo.timeMs;\n\n let totalMs = 0;\n\n for (let day = startDay; day <= endDay; day++) {\n if (!isWorkingScheduleDay(day, schedule)) {\n continue;\n }\n\n const shifts = getShiftsForDay(day, schedule);\n for (const shift of shifts) {\n const activeStart =\n day === startDay ? Math.max(shift.startMs, startMs) : shift.startMs;\n const activeEnd =\n day === endDay ? Math.min(shift.endMs, endMs) : shift.endMs;\n\n if (activeStart < activeEnd) {\n totalMs += activeEnd - activeStart;\n }\n }\n }\n\n return totalMs / 3_600_000;\n}\n\n/**\n * Options for resolving the next business shift window.\n */\nexport interface NextBusinessShiftOptions {\n /**\n * If true (default), returns the currently ongoing shift if the timestamp falls inside it.\n * If false, strictly returns the next upcoming shift after the timestamp.\n */\n readonly includeCurrent?: boolean;\n}\n\n/**\n * Resolves the next upcoming (or currently active) business shift window.\n *\n * @param input - The reference timestamp.\n * @param schedule - Business schedule configuration.\n * @param options - Options for including the currently active shift.\n */\nexport function nextBusinessShift(\n input: LocalDate,\n schedule?: BusinessSchedule,\n options?: NextBusinessShiftOptions,\n): NextBusinessShiftResult<LocalDateTime>;\nexport function nextBusinessShift<T extends LocalDateTime | Instant | Date>(\n input: T,\n schedule?: BusinessSchedule,\n options?: NextBusinessShiftOptions,\n): NextBusinessShiftResult<T>;\nexport function nextBusinessShift(\n input: BusinessTimeInput,\n schedule?: BusinessSchedule,\n options?: NextBusinessShiftOptions,\n): NextBusinessShiftResult<LocalDateTime | Instant | Date>;\nexport function nextBusinessShift(\n input: BusinessTimeInput,\n schedule: BusinessSchedule = {},\n options: NextBusinessShiftOptions = {},\n): NextBusinessShiftResult<LocalDateTime | Instant | Date> {\n const info = extractTimeInfo(input, schedule.timeZone);\n const includeCurrent = options.includeCurrent ?? true;\n\n let currentAbs = info.absDay;\n let currentMs = info.timeMs;\n let loopCount = 0;\n const maxDays = 10_000;\n\n while (loopCount++ < maxDays) {\n if (isWorkingScheduleDay(currentAbs, schedule)) {\n const shifts = getShiftsForDay(currentAbs, schedule);\n for (const shift of shifts) {\n if (currentAbs === info.absDay) {\n if (includeCurrent) {\n if (currentMs < shift.endMs) {\n return {\n start: reconstructResult(info, currentAbs, shift.startMs),\n end: reconstructResult(info, currentAbs, shift.endMs),\n };\n }\n } else {\n if (currentMs < shift.startMs) {\n return {\n start: reconstructResult(info, currentAbs, shift.startMs),\n end: reconstructResult(info, currentAbs, shift.endMs),\n };\n }\n }\n } else {\n return {\n start: reconstructResult(info, currentAbs, shift.startMs),\n end: reconstructResult(info, currentAbs, shift.endMs),\n };\n }\n }\n }\n\n currentAbs += 1;\n currentMs = 0;\n }\n\n throw new ChroneraError(\n \"CHRONERA_OUT_OF_RANGE\",\n \"No upcoming business shift found within 10,000 calendar days.\",\n );\n}\n\n/**\n * Returns the earliest shift start time for a given date as a LocalDateTime.\n */\nexport function startOfBusinessDay(\n date: LocalDate | LocalDateTime | Date,\n schedule: BusinessSchedule = {},\n): LocalDateTime {\n const absDay = getAbsoluteDay(\n date instanceof Date\n ? localDate(date.getFullYear(), date.getMonth() + 1, date.getDate())\n : date.kind === \"local-date-time\"\n ? date.date\n : date,\n );\n const shifts = getShiftsForDay(absDay, schedule);\n if (shifts.length === 0) {\n throw new ChroneraError(\n \"CHRONERA_OUT_OF_RANGE\",\n \"No business shifts defined for the given date.\",\n );\n }\n const firstShift = shifts[0]!;\n const g = gregorianFromAbsoluteDay(absDay);\n const hour = Math.floor(firstShift.startMs / 3_600_000);\n const minute = Math.floor((firstShift.startMs % 3_600_000) / 60_000);\n const second = Math.floor((firstShift.startMs % 60_000) / 1000);\n const millisecond = firstShift.startMs % 1000;\n return localDateTime(\n localDate(g.year, g.month, g.day),\n localTime(hour, minute, second, millisecond),\n );\n}\n\n/**\n * Returns the latest shift end time for a given date as a LocalDateTime.\n */\nexport function endOfBusinessDay(\n date: LocalDate | LocalDateTime | Date,\n schedule: BusinessSchedule = {},\n): LocalDateTime {\n const absDay = getAbsoluteDay(\n date instanceof Date\n ? localDate(date.getFullYear(), date.getMonth() + 1, date.getDate())\n : date.kind === \"local-date-time\"\n ? date.date\n : date,\n );\n const shifts = getShiftsForDay(absDay, schedule);\n if (shifts.length === 0) {\n throw new ChroneraError(\n \"CHRONERA_OUT_OF_RANGE\",\n \"No business shifts defined for the given date.\",\n );\n }\n const lastShift = shifts[shifts.length - 1]!;\n const g = gregorianFromAbsoluteDay(absDay);\n const hour = Math.floor(lastShift.endMs / 3_600_000);\n const minute = Math.floor((lastShift.endMs % 3_600_000) / 60_000);\n const second = Math.floor((lastShift.endMs % 60_000) / 1000);\n const millisecond = lastShift.endMs % 1000;\n return localDateTime(\n localDate(g.year, g.month, g.day),\n localTime(hour, minute, second, millisecond),\n );\n}\n"]}
@@ -0,0 +1,15 @@
1
+ import type { CountryCode, HolidayCalendar, HolidayOptions, PublicHoliday } from "../holidays/types.js";
2
+ import type { DateOrCalendarDate } from "../public-types.js";
3
+ /**
4
+ * Returns true if the specified date is an official public holiday in the given country.
5
+ * Supports both Gregorian LocalDate and any cultural CalendarDate (Thai Buddhist, Japanese Reiwa, Hijri, etc.).
6
+ */
7
+ export declare function isPublicHoliday(date: DateOrCalendarDate, country: CountryCode | string | HolidayCalendar, options?: HolidayOptions): boolean;
8
+ /**
9
+ * Returns all public holidays for a given year in the specified country.
10
+ */
11
+ export declare function getPublicHolidays(year: number, country: CountryCode | string | HolidayCalendar, options?: HolidayOptions): readonly PublicHoliday[];
12
+ /**
13
+ * Returns the public holiday details if the specified date is a holiday, or undefined otherwise.
14
+ */
15
+ export declare function getHolidayDetails(date: DateOrCalendarDate, country: CountryCode | string | HolidayCalendar, options?: HolidayOptions): PublicHoliday | undefined;
@@ -0,0 +1,36 @@
1
+ import { gregorianFromAbsoluteDay } from "../calendar/gregory/absolute-day.js";
2
+ import { localDate } from "../core/local-date.js";
3
+ import { getAbsoluteDay } from "./convenience.js";
4
+ import { resolveAnnualHolidays } from "../holidays/registry.js";
5
+ function toGregorianLocalDate(date) {
6
+ if (date.kind === "local-date") {
7
+ return date;
8
+ }
9
+ const abs = getAbsoluteDay(date);
10
+ const fields = gregorianFromAbsoluteDay(abs);
11
+ return localDate(fields.year, fields.month, fields.day);
12
+ }
13
+ /**
14
+ * Returns true if the specified date is an official public holiday in the given country.
15
+ * Supports both Gregorian LocalDate and any cultural CalendarDate (Thai Buddhist, Japanese Reiwa, Hijri, etc.).
16
+ */
17
+ export function isPublicHoliday(date, country, options) {
18
+ const gDate = toGregorianLocalDate(date);
19
+ const annualHolidays = resolveAnnualHolidays(country, gDate.year, options);
20
+ return annualHolidays.some((h) => h.date.month === gDate.month && h.date.day === gDate.day);
21
+ }
22
+ /**
23
+ * Returns all public holidays for a given year in the specified country.
24
+ */
25
+ export function getPublicHolidays(year, country, options) {
26
+ return resolveAnnualHolidays(country, year, options);
27
+ }
28
+ /**
29
+ * Returns the public holiday details if the specified date is a holiday, or undefined otherwise.
30
+ */
31
+ export function getHolidayDetails(date, country, options) {
32
+ const gDate = toGregorianLocalDate(date);
33
+ const annualHolidays = resolveAnnualHolidays(country, gDate.year, options);
34
+ return annualHolidays.find((h) => h.date.month === gDate.month && h.date.day === gDate.day);
35
+ }
36
+ //# sourceMappingURL=holidays.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"holidays.js","sourceRoot":"","sources":["../../src/operations/holidays.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,wBAAwB,EAAE,MAAM,qCAAqC,CAAC;AAC/E,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAClD,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAUhE,SAAS,oBAAoB,CAAC,IAAwB;IACpD,IAAI,IAAI,CAAC,IAAI,KAAK,YAAY,EAAE,CAAC;QAC/B,OAAO,IAAI,CAAC;IACd,CAAC;IACD,MAAM,GAAG,GAAG,cAAc,CAAC,IAAI,CAAC,CAAC;IACjC,MAAM,MAAM,GAAG,wBAAwB,CAAC,GAAG,CAAC,CAAC;IAC7C,OAAO,SAAS,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC;AAC1D,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,eAAe,CAC7B,IAAwB,EACxB,OAA+C,EAC/C,OAAwB;IAExB,MAAM,KAAK,GAAG,oBAAoB,CAAC,IAAI,CAAC,CAAC;IACzC,MAAM,cAAc,GAAG,qBAAqB,CAAC,OAAO,EAAE,KAAK,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IAE3E,OAAO,cAAc,CAAC,IAAI,CACxB,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,KAAK,KAAK,CAAC,KAAK,IAAI,CAAC,CAAC,IAAI,CAAC,GAAG,KAAK,KAAK,CAAC,GAAG,CAChE,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,iBAAiB,CAC/B,IAAY,EACZ,OAA+C,EAC/C,OAAwB;IAExB,OAAO,qBAAqB,CAAC,OAAO,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC;AACvD,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,iBAAiB,CAC/B,IAAwB,EACxB,OAA+C,EAC/C,OAAwB;IAExB,MAAM,KAAK,GAAG,oBAAoB,CAAC,IAAI,CAAC,CAAC;IACzC,MAAM,cAAc,GAAG,qBAAqB,CAAC,OAAO,EAAE,KAAK,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IAE3E,OAAO,cAAc,CAAC,IAAI,CACxB,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,KAAK,KAAK,CAAC,KAAK,IAAI,CAAC,CAAC,IAAI,CAAC,GAAG,KAAK,KAAK,CAAC,GAAG,CAChE,CAAC;AACJ,CAAC","sourcesContent":["import { gregorianFromAbsoluteDay } from \"../calendar/gregory/absolute-day.js\";\nimport { localDate } from \"../core/local-date.js\";\nimport { getAbsoluteDay } from \"./convenience.js\";\nimport { resolveAnnualHolidays } from \"../holidays/registry.js\";\n\nimport type {\n CountryCode,\n HolidayCalendar,\n HolidayOptions,\n PublicHoliday,\n} from \"../holidays/types.js\";\nimport type { DateOrCalendarDate, LocalDate } from \"../public-types.js\";\n\nfunction toGregorianLocalDate(date: DateOrCalendarDate): LocalDate {\n if (date.kind === \"local-date\") {\n return date;\n }\n const abs = getAbsoluteDay(date);\n const fields = gregorianFromAbsoluteDay(abs);\n return localDate(fields.year, fields.month, fields.day);\n}\n\n/**\n * Returns true if the specified date is an official public holiday in the given country.\n * Supports both Gregorian LocalDate and any cultural CalendarDate (Thai Buddhist, Japanese Reiwa, Hijri, etc.).\n */\nexport function isPublicHoliday(\n date: DateOrCalendarDate,\n country: CountryCode | string | HolidayCalendar,\n options?: HolidayOptions,\n): boolean {\n const gDate = toGregorianLocalDate(date);\n const annualHolidays = resolveAnnualHolidays(country, gDate.year, options);\n\n return annualHolidays.some(\n (h) => h.date.month === gDate.month && h.date.day === gDate.day,\n );\n}\n\n/**\n * Returns all public holidays for a given year in the specified country.\n */\nexport function getPublicHolidays(\n year: number,\n country: CountryCode | string | HolidayCalendar,\n options?: HolidayOptions,\n): readonly PublicHoliday[] {\n return resolveAnnualHolidays(country, year, options);\n}\n\n/**\n * Returns the public holiday details if the specified date is a holiday, or undefined otherwise.\n */\nexport function getHolidayDetails(\n date: DateOrCalendarDate,\n country: CountryCode | string | HolidayCalendar,\n options?: HolidayOptions,\n): PublicHoliday | undefined {\n const gDate = toGregorianLocalDate(date);\n const annualHolidays = resolveAnnualHolidays(country, gDate.year, options);\n\n return annualHolidays.find(\n (h) => h.date.month === gDate.month && h.date.day === gDate.day,\n );\n}\n"]}