temporal-workdays 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -32,232 +32,906 @@ function daysInMonth(year, month) {
32
32
  if (month === 2) return isLeapYear(year) ? 29 : 28;
33
33
  return month === 4 || month === 6 || month === 9 || month === 11 ? 30 : 31;
34
34
  }
35
+ function floorDiv(a, b) {
36
+ const q = a / b;
37
+ return a % b < 0n ? q - 1n : q;
38
+ }
35
39
 
36
- // src/index.ts
37
- var MAX_SCAN_DAYS = 366 * 20;
38
- var DATE_RE = /^([+-]?\d{4,6})-(\d{2})-(\d{2})$/;
39
- var MONTH_DAY_RE = /^(?:--)?(\d{2})-(\d{2})$/;
40
- function checkDay(year, month, day, source) {
41
- if (month < 1 || month > 12 || day < 1 || day > daysInMonth(year, month)) {
42
- throw new RangeError(`temporal-workdays: invalid date ${JSON.stringify(String(source))}`);
40
+ // src/errors.ts
41
+ var PREFIX = "temporal-workdays: ";
42
+ function rangeError(message) {
43
+ return new RangeError(PREFIX + message);
44
+ }
45
+ function typeError(message) {
46
+ return new TypeError(PREFIX + message);
47
+ }
48
+ function describe(value) {
49
+ switch (typeof value) {
50
+ case "string":
51
+ return JSON.stringify(value);
52
+ case "number":
53
+ case "boolean":
54
+ case "bigint":
55
+ case "undefined":
56
+ return String(value);
57
+ case "symbol":
58
+ return value.toString();
59
+ case "function":
60
+ return "a function";
61
+ case "object":
62
+ return value === null ? "null" : Object.prototype.toString.call(value);
63
+ }
64
+ }
65
+
66
+ // src/temporal.ts
67
+ function hasBrand(value, brand) {
68
+ return typeof value === "object" && value !== null && Object.prototype.toString.call(value) === `[object ${brand}]`;
69
+ }
70
+ function isPlainDate(value) {
71
+ return hasBrand(value, "Temporal.PlainDate");
72
+ }
73
+ function isPlainDateTime(value) {
74
+ return hasBrand(value, "Temporal.PlainDateTime");
75
+ }
76
+ function isZonedDateTime(value) {
77
+ return hasBrand(value, "Temporal.ZonedDateTime");
78
+ }
79
+ function toPlainDate(date) {
80
+ if (isPlainDate(date)) return date;
81
+ if (isPlainDateTime(date) || isZonedDateTime(date)) {
82
+ return date.toPlainDate();
43
83
  }
84
+ throw typeError(`expected a Temporal.PlainDate, PlainDateTime or ZonedDateTime, got ${describe(date)}`);
85
+ }
86
+ function toIso(value) {
87
+ return value.calendarId === "iso8601" ? value : value.withCalendar("iso8601");
88
+ }
89
+ function withCalendarId(value, calendarId) {
90
+ return calendarId === "iso8601" ? value : value.withCalendar(calendarId);
91
+ }
92
+ function toIsoDate(date) {
93
+ return toIso(date);
44
94
  }
45
- function epochDayOfPlainDate(date) {
46
- const iso = date.calendarId === "iso8601" ? date : date.withCalendar("iso8601");
95
+ function epochDayOf(date) {
96
+ const iso = toIsoDate(date);
47
97
  return epochDayFromIso(iso.year, iso.month, iso.day);
48
98
  }
49
- function isTemporalLike(value, tag) {
50
- return typeof value === "object" && value !== null && Object.prototype.toString.call(value) === `[object ${tag}]`;
99
+ var DateFactory = class {
100
+ #iso;
101
+ #epochDay;
102
+ constructor(reference) {
103
+ this.#iso = toIsoDate(reference);
104
+ this.#epochDay = epochDayFromIso(this.#iso.year, this.#iso.month, this.#iso.day);
105
+ }
106
+ get epochDay() {
107
+ return this.#epochDay;
108
+ }
109
+ at(epochDay) {
110
+ return epochDay === this.#epochDay ? this.#iso : this.#iso.add({ days: epochDay - this.#epochDay });
111
+ }
112
+ };
113
+
114
+ // src/day-calendar.ts
115
+ var MAX_SCAN_DAYS = 366 * 20;
116
+ var DATE_RE = /^([+-]\d{6}|\d{4})-(\d{2})-(\d{2})$/;
117
+ var MONTH_DAY_RE = /^(?:--)?(\d{2})-(\d{2})$/;
118
+ var OPTION_KEYS = /* @__PURE__ */ new Set([
119
+ "weekend",
120
+ "holidays",
121
+ "extraWorkdays",
122
+ "hours",
123
+ "specialHours"
124
+ ]);
125
+ function checkDate(year, month, day, source) {
126
+ if (month < 1 || month > 12 || day < 1 || day > daysInMonth(year, month)) {
127
+ throw rangeError(`invalid date ${describe(source)}`);
128
+ }
51
129
  }
52
- function parseSingleDate(value) {
130
+ function parseDate(value) {
53
131
  if (typeof value === "string") {
54
132
  const m = DATE_RE.exec(value);
55
- if (!m) throw new RangeError(`temporal-workdays: expected "YYYY-MM-DD", got ${JSON.stringify(value)}`);
56
- const [y, mo, d] = [Number(m[1]), Number(m[2]), Number(m[3])];
57
- checkDay(y, mo, d, value);
58
- return epochDayFromIso(y, mo, d);
133
+ if (!m) throw rangeError(`expected a "YYYY-MM-DD" date, got ${describe(value)}`);
134
+ const [year, month, day] = [Number(m[1]), Number(m[2]), Number(m[3])];
135
+ checkDate(year, month, day, value);
136
+ return epochDayFromIso(year, month, day);
137
+ }
138
+ if (isPlainDate(value)) return epochDayOf(value);
139
+ throw typeError(`expected a "YYYY-MM-DD" string or Temporal.PlainDate, got ${describe(value)}`);
140
+ }
141
+ function addHoliday(entry, dates, annual) {
142
+ if (typeof entry === "string") {
143
+ const md = MONTH_DAY_RE.exec(entry);
144
+ if (md) {
145
+ const [month, day] = [Number(md[1]), Number(md[2])];
146
+ checkDate(2e3, month, day, entry);
147
+ annual.add(month * 100 + day);
148
+ return;
149
+ }
150
+ } else if (hasBrand(entry, "Temporal.PlainMonthDay")) {
151
+ const pmd = entry;
152
+ if (pmd.calendarId !== "iso8601") {
153
+ throw rangeError("Temporal.PlainMonthDay holidays must use the iso8601 calendar");
154
+ }
155
+ annual.add(Number(pmd.monthCode.slice(1)) * 100 + pmd.day);
156
+ return;
157
+ }
158
+ dates.add(parseDate(entry));
159
+ }
160
+ function isIterable(value) {
161
+ return typeof value === "object" && value !== null && typeof value[Symbol.iterator] === "function";
162
+ }
163
+ function checkOptionKeys(options, allowed) {
164
+ for (const key of Object.keys(options)) {
165
+ if (!allowed.has(key)) throw typeError(`unknown option ${JSON.stringify(key)}`);
166
+ }
167
+ }
168
+ function checkOptionsObject(options) {
169
+ if (options !== void 0 && (typeof options !== "object" || options === null)) {
170
+ throw typeError(`options must be an object, got ${describe(options)}`);
59
171
  }
60
- if (isTemporalLike(value, "Temporal.PlainDate")) return epochDayOfPlainDate(value);
61
- throw new TypeError(`temporal-workdays: expected a "YYYY-MM-DD" string or Temporal.PlainDate, got ${String(value)}`);
62
172
  }
63
- function compile(options) {
64
- const weekendDays = options?.weekend ?? [6, 7];
173
+ function compileDayCalendar(options = {}) {
174
+ checkOptionsObject(options);
175
+ checkOptionKeys(options, OPTION_KEYS);
65
176
  const weekend = new Array(8).fill(false);
177
+ const weekendDays = options.weekend ?? [6, 7];
178
+ if (!isIterable(weekendDays)) throw typeError(`weekend must be an array of days, got ${describe(weekendDays)}`);
66
179
  for (const d of weekendDays) {
67
180
  if (!Number.isInteger(d) || d < 1 || d > 7) {
68
- throw new RangeError(`temporal-workdays: weekend days must be integers 1 (Mon) \u2013 7 (Sun), got ${String(d)}`);
181
+ throw rangeError(`weekend days must be integers 1 (Mon) \u2013 7 (Sun), got ${describe(d)}`);
69
182
  }
70
183
  weekend[d] = true;
71
184
  }
72
185
  const dates = /* @__PURE__ */ new Set();
73
186
  const annual = /* @__PURE__ */ new Set();
74
187
  let predicate;
75
- const holidays = options?.holidays;
188
+ const holidays = options.holidays;
76
189
  if (typeof holidays === "function") {
77
190
  predicate = holidays;
78
- } else if (holidays != null) {
79
- for (const h of holidays) {
80
- if (typeof h === "string") {
81
- const md = MONTH_DAY_RE.exec(h);
82
- if (md) {
83
- const [mo, d] = [Number(md[1]), Number(md[2])];
84
- checkDay(2e3, mo, d, h);
85
- annual.add(mo * 100 + d);
86
- continue;
87
- }
88
- dates.add(parseSingleDate(h));
89
- } else if (isTemporalLike(h, "Temporal.PlainMonthDay")) {
90
- const pmd = h;
91
- if (pmd.calendarId !== "iso8601") {
92
- throw new RangeError("temporal-workdays: Temporal.PlainMonthDay holidays must use the iso8601 calendar");
93
- }
94
- annual.add(Number(pmd.monthCode.slice(1)) * 100 + pmd.day);
95
- } else {
96
- dates.add(parseSingleDate(h));
97
- }
98
- }
191
+ } else if (holidays !== void 0) {
192
+ if (!isIterable(holidays)) throw typeError(`holidays must be an array or a function, got ${describe(holidays)}`);
193
+ for (const entry of holidays) addHoliday(entry, dates, annual);
99
194
  }
100
195
  const extra = /* @__PURE__ */ new Set();
101
- if (options?.extraWorkdays != null) {
102
- for (const e of options.extraWorkdays) extra.add(parseSingleDate(e));
196
+ const extraWorkdays = options.extraWorkdays;
197
+ if (extraWorkdays !== void 0) {
198
+ if (!isIterable(extraWorkdays)) throw typeError(`extraWorkdays must be an array, got ${describe(extraWorkdays)}`);
199
+ for (const entry of extraWorkdays) extra.add(parseDate(entry));
103
200
  }
104
201
  return { weekend, dates, annual, predicate, extra };
105
202
  }
106
- var cache = /* @__PURE__ */ new WeakMap();
107
- var DEFAULT = compile(void 0);
108
- function getCompiled(options) {
109
- if (options === void 0) return DEFAULT;
110
- let c = cache.get(options);
111
- if (!c) {
112
- c = compile(options);
113
- cache.set(options, c);
114
- }
115
- return c;
203
+ function isWeekendDay(cal, epochDay) {
204
+ return cal.weekend[dayOfWeekFromEpochDay(epochDay)] === true;
116
205
  }
117
- function toPlainDate(date) {
118
- if (date == null || typeof date.add !== "function") {
119
- throw new TypeError("temporal-workdays: expected a Temporal.PlainDate, PlainDateTime or ZonedDateTime");
206
+ function isListedHoliday(cal, epochDay, dates) {
207
+ if (cal.dates.has(epochDay)) return true;
208
+ if (cal.annual.size > 0) {
209
+ const [, month, day] = isoFromEpochDay(epochDay);
210
+ if (cal.annual.has(month * 100 + day)) return true;
120
211
  }
121
- return "toPlainDate" in date ? date.toPlainDate() : date;
212
+ return Boolean(cal.predicate?.(dates.at(epochDay)));
122
213
  }
123
- function isHolidayDay(c, epochDay, base, baseDay) {
124
- if (c.dates.has(epochDay)) return true;
125
- if (c.annual.size > 0) {
126
- const [, mo, d] = isoFromEpochDay(epochDay);
127
- if (c.annual.has(mo * 100 + d)) return true;
128
- }
129
- if (c.predicate) {
130
- const iso = base.calendarId === "iso8601" ? base : base.withCalendar("iso8601");
131
- return c.predicate(epochDay === baseDay ? iso : iso.add({ days: epochDay - baseDay }));
132
- }
133
- return false;
134
- }
135
- function isWorkdayDay(c, epochDay, base, baseDay) {
136
- if (c.extra.has(epochDay)) return true;
137
- if (c.weekend[dayOfWeekFromEpochDay(epochDay)]) return false;
138
- return !isHolidayDay(c, epochDay, base, baseDay);
214
+ function isHolidayDay(cal, epochDay, dates) {
215
+ return !cal.extra.has(epochDay) && isListedHoliday(cal, epochDay, dates);
139
216
  }
140
- function checkInteger(n, name) {
141
- if (!Number.isInteger(n)) throw new RangeError(`temporal-workdays: ${name} must be an integer, got ${String(n)}`);
217
+ function isWorkdayDay(cal, epochDay, dates) {
218
+ if (cal.extra.has(epochDay)) return true;
219
+ if (isWeekendDay(cal, epochDay)) return false;
220
+ return !isListedHoliday(cal, epochDay, dates);
142
221
  }
143
222
  function noWorkdayError() {
144
- return new RangeError(
145
- `temporal-workdays: no workday found within ${MAX_SCAN_DAYS} days \u2014 check your weekend/holiday configuration`
146
- );
223
+ return rangeError(`no workday found within ${MAX_SCAN_DAYS} days \u2014 check the weekend/holiday configuration`);
147
224
  }
148
- function step(c, base, count, dir) {
149
- const baseDay = epochDayOfPlainDate(base);
150
- let day = baseDay;
225
+ function stepWorkdays(cal, from, count, dir, dates) {
226
+ let day = from;
151
227
  let remaining = count;
152
228
  let gap = 0;
153
229
  while (remaining > 0) {
154
230
  day += dir;
155
- if (isWorkdayDay(c, day, base, baseDay)) {
231
+ if (isWorkdayDay(cal, day, dates)) {
156
232
  remaining--;
157
233
  gap = 0;
158
234
  } else if (++gap > MAX_SCAN_DAYS) {
159
235
  throw noWorkdayError();
160
236
  }
161
237
  }
162
- return day - baseDay;
238
+ return day;
239
+ }
240
+ function countWorkdays(cal, from, to, dates) {
241
+ const [lo, hi, sign] = from <= to ? [from, to, 1] : [to, from, -1];
242
+ let count = 0;
243
+ for (let day = lo + 1; day <= hi; day++) {
244
+ if (isWorkdayDay(cal, day, dates)) count++;
245
+ }
246
+ return count === 0 ? 0 : sign * count;
247
+ }
248
+
249
+ // src/time.ts
250
+ var NS_PER_SECOND = 1e9;
251
+ var NS_PER_MINUTE = 60 * NS_PER_SECOND;
252
+ var NS_PER_HOUR = 60 * NS_PER_MINUTE;
253
+ var NS_PER_DAY = 24 * NS_PER_HOUR;
254
+ var TIME_RE = /^(\d{2}):(\d{2})(?::(\d{2})(?:[.,](\d{1,9}))?)?$/;
255
+ function nsOfDay(hour, minute, second = 0, subsecondNs = 0) {
256
+ return hour * NS_PER_HOUR + minute * NS_PER_MINUTE + second * NS_PER_SECOND + subsecondNs;
257
+ }
258
+ function nsOfTime(t) {
259
+ return nsOfDay(t.hour, t.minute, t.second, t.millisecond * 1e6 + t.microsecond * 1e3 + t.nanosecond);
260
+ }
261
+ function parseTime(value) {
262
+ if (typeof value === "string") {
263
+ const m = TIME_RE.exec(value);
264
+ if (m) {
265
+ const [hour, minute, second] = [Number(m[1]), Number(m[2]), Number(m[3] ?? 0)];
266
+ const subsecond = Number((m[4] ?? "").padEnd(9, "0"));
267
+ const ns = nsOfDay(hour, minute, second, subsecond);
268
+ if (minute < 60 && second < 60 && ns <= NS_PER_DAY) return ns;
269
+ }
270
+ throw rangeError(`expected a time like "09:30" (00:00 \u2013 24:00), got ${describe(value)}`);
271
+ }
272
+ if (hasBrand(value, "Temporal.PlainTime")) return nsOfTime(value);
273
+ throw typeError(`expected a "HH:MM" string or Temporal.PlainTime, got ${describe(value)}`);
274
+ }
275
+ var pad = (n, width = 2) => String(n).padStart(width, "0");
276
+ function formatTime(ns) {
277
+ const hour = Math.floor(ns / NS_PER_HOUR);
278
+ const minute = Math.floor(ns % NS_PER_HOUR / NS_PER_MINUTE);
279
+ const second = Math.floor(ns % NS_PER_MINUTE / NS_PER_SECOND);
280
+ const fraction = ns % NS_PER_SECOND;
281
+ let text = `${pad(hour)}:${pad(minute)}`;
282
+ if (second !== 0 || fraction !== 0) text += `:${pad(second)}`;
283
+ if (fraction !== 0) text += `.${pad(fraction, 9).replace(/0+$/, "")}`;
284
+ return text;
285
+ }
286
+ function parseDayHours(value, where) {
287
+ if (!Array.isArray(value))
288
+ throw typeError(`${where} must be an array of [start, end] ranges, got ${describe(value)}`);
289
+ const ranges = value.map((range) => {
290
+ if (!Array.isArray(range) || range.length !== 2) {
291
+ throw typeError(`${where}: each range must be a [start, end] pair, got ${describe(range)}`);
292
+ }
293
+ const [start, end] = range;
294
+ const interval = [parseTime(start), parseTime(end)];
295
+ if (interval[0] >= interval[1]) {
296
+ throw rangeError(`${where}: range start must be before its end, got ${describe(start)}\u2013${describe(end)}`);
297
+ }
298
+ return interval;
299
+ });
300
+ ranges.sort((a, b) => a[0] - b[0]);
301
+ const merged = [];
302
+ for (const [start, end] of ranges) {
303
+ const last = merged.at(-1);
304
+ if (last && start < last[1]) {
305
+ throw rangeError(
306
+ `${where}: ranges overlap (${formatTime(last[0])}\u2013${formatTime(last[1])} and ${formatTime(start)}\u2013${formatTime(end)})`
307
+ );
308
+ }
309
+ if (last?.[1] === start) last[1] = end;
310
+ else merged.push([start, end]);
311
+ }
312
+ return merged;
313
+ }
314
+ function formatDayHours(intervals) {
315
+ return intervals.map(([start, end]) => [formatTime(start), formatTime(end)]);
316
+ }
317
+ function subtractInterval(intervals, cutStart, cutEnd) {
318
+ const out = [];
319
+ for (const [start, end] of intervals) {
320
+ if (end <= cutStart || start >= cutEnd) {
321
+ out.push([start, end]);
322
+ continue;
323
+ }
324
+ if (start < cutStart) out.push([start, cutStart]);
325
+ if (end > cutEnd) out.push([cutEnd, end]);
326
+ }
327
+ return out;
328
+ }
329
+
330
+ // src/hours.ts
331
+ function byDayOfWeek(f) {
332
+ return { 1: f(1), 2: f(2), 3: f(3), 4: f(4), 5: f(5), 6: f(6), 7: f(7) };
333
+ }
334
+ var WEEKLY_KEYS = /* @__PURE__ */ new Set(["1", "2", "3", "4", "5", "6", "7", "default"]);
335
+ var EMPTY = [];
336
+ function compileWeekly(hours) {
337
+ if (hours === void 0) throw typeError("working-time functions need an `hours` option");
338
+ if (Array.isArray(hours)) {
339
+ const uniform = parseDayHours(hours, "hours");
340
+ return byDayOfWeek(() => uniform);
341
+ }
342
+ if (typeof hours !== "object" || hours === null) {
343
+ throw typeError(`hours must be an array of ranges or an object by day of week, got ${describe(hours)}`);
344
+ }
345
+ checkOptionKeys(hours, WEEKLY_KEYS);
346
+ const byDay = hours;
347
+ const fallback = byDay["default"] === void 0 ? EMPTY : parseDayHours(byDay["default"], "hours.default");
348
+ return byDayOfWeek((dow) => byDay[dow] === void 0 ? fallback : parseDayHours(byDay[dow], `hours[${dow}]`));
349
+ }
350
+ function compileSpecial(special) {
351
+ if (special === void 0) return { kind: "none" };
352
+ if (typeof special === "function") return { kind: "function", fn: special };
353
+ if (typeof special !== "object" || special === null || Array.isArray(special)) {
354
+ throw typeError(`specialHours must be an object keyed by date or a function, got ${describe(special)}`);
355
+ }
356
+ const byDay = /* @__PURE__ */ new Map();
357
+ for (const [date, hours] of Object.entries(special)) {
358
+ byDay.set(parseDate(date), parseDayHours(hours, `specialHours[${JSON.stringify(date)}]`));
359
+ }
360
+ return { kind: "table", byDay };
361
+ }
362
+ function compileHoursCalendar(options) {
363
+ checkOptionsObject(options);
364
+ const days = compileDayCalendar(options);
365
+ const weekly = compileWeekly(options?.hours);
366
+ const weeklyText = byDayOfWeek((dow) => formatDayHours(weekly[dow]));
367
+ return { days, weekly, weeklyText, special: compileSpecial(options.specialHours) };
368
+ }
369
+ function intervalsOn(cal, epochDay, dates) {
370
+ const dow = dayOfWeekFromEpochDay(epochDay);
371
+ const workday = isWorkdayDay(cal.days, epochDay, dates);
372
+ const { special } = cal;
373
+ if (special.kind === "table") {
374
+ const override = special.byDay.get(epochDay);
375
+ if (override) return override;
376
+ } else if (special.kind === "function") {
377
+ const date = dates.at(epochDay);
378
+ const result = special.fn(date, workday ? cal.weeklyText[dow] : []);
379
+ if (result !== void 0) return parseDayHours(result, `specialHours(${date.toString()})`);
380
+ }
381
+ return workday ? cal.weekly[dow] : EMPTY;
382
+ }
383
+ var BIG_NS_PER_DAY = BigInt(NS_PER_DAY);
384
+ var BIG_NS_PER_SECOND = BigInt(NS_PER_SECOND);
385
+ function wallFields(day, ns) {
386
+ const [year, month, dd] = isoFromEpochDay(day);
387
+ return {
388
+ year,
389
+ month,
390
+ day: dd,
391
+ hour: Math.floor(ns / NS_PER_HOUR),
392
+ minute: Math.floor(ns % NS_PER_HOUR / NS_PER_MINUTE),
393
+ second: Math.floor(ns % NS_PER_MINUTE / NS_PER_SECOND),
394
+ millisecond: Math.floor(ns % NS_PER_SECOND / 1e6),
395
+ microsecond: Math.floor(ns % 1e6 / 1e3),
396
+ nanosecond: ns % 1e3
397
+ };
398
+ }
399
+ function wallPosition(day, ns) {
400
+ return BigInt(day) * BIG_NS_PER_DAY + BigInt(ns);
401
+ }
402
+ function wallDay(v) {
403
+ return epochDayFromIso(v.year, v.month, v.day);
404
+ }
405
+ function durationFrom(zero, ns) {
406
+ return zero.add({ seconds: Number(ns / BIG_NS_PER_SECOND), nanoseconds: Number(ns % BIG_NS_PER_SECOND) }).round({ largestUnit: "hours" });
407
+ }
408
+ function normalizePeriods(periods) {
409
+ periods.sort((a, b) => a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0);
410
+ const merged = [];
411
+ for (const [start, end] of periods) {
412
+ if (end <= start) continue;
413
+ const last = merged.at(-1);
414
+ if (last && start <= last[1]) {
415
+ if (end > last[1]) last[1] = end;
416
+ } else {
417
+ merged.push([start, end]);
418
+ }
419
+ }
420
+ return merged;
421
+ }
422
+ var PlainTimeline = class {
423
+ origin;
424
+ lookahead = 0n;
425
+ dates;
426
+ #iso;
427
+ #calendarId;
428
+ constructor(value) {
429
+ this.#calendarId = value.calendarId;
430
+ this.#iso = toIso(value);
431
+ this.origin = wallPosition(wallDay(this.#iso), nsOfTime(this.#iso));
432
+ this.dates = new DateFactory(this.#iso.toPlainDate());
433
+ }
434
+ positionOf(value) {
435
+ if (!isPlainDateTime(value)) throw mixedTypesError(value);
436
+ const iso = toIso(value);
437
+ return wallPosition(wallDay(iso), nsOfTime(iso));
438
+ }
439
+ periods(day, intervals) {
440
+ return intervals.map(([start, end]) => [wallPosition(day, start), wallPosition(day, end)]);
441
+ }
442
+ at(position) {
443
+ const day = floorDiv(position, BIG_NS_PER_DAY);
444
+ const fields = wallFields(Number(day), Number(position - day * BIG_NS_PER_DAY));
445
+ return withCalendarId(this.#iso.with(fields), this.#calendarId);
446
+ }
447
+ duration(ns) {
448
+ return durationFrom(this.#iso.until(this.#iso), ns);
449
+ }
450
+ };
451
+ var SEGMENT_MARGIN = 48n * BigInt(NS_PER_HOUR);
452
+ var FOREVER = 2n ** 100n;
453
+ var ZONED_LOOKAHEAD = 48n * BigInt(NS_PER_HOUR);
454
+ var ZonedTimeline = class {
455
+ origin;
456
+ lookahead = ZONED_LOOKAHEAD;
457
+ dates;
458
+ #iso;
459
+ #calendarId;
460
+ #segment;
461
+ constructor(value) {
462
+ this.#calendarId = value.calendarId;
463
+ this.#iso = toIso(value);
464
+ this.origin = this.#iso.epochNanoseconds;
465
+ this.dates = new DateFactory(this.#iso.toPlainDate());
466
+ }
467
+ positionOf(value) {
468
+ if (!isZonedDateTime(value)) throw mixedTypesError(value);
469
+ return value.epochNanoseconds;
470
+ }
471
+ periods(day, intervals) {
472
+ return normalizePeriods(intervals.map(([start, end]) => [this.#exact(day, start), this.#exact(day, end)]));
473
+ }
474
+ /** Exact time of a wall-clock time; skipped times move forward, repeated times use the earlier. */
475
+ #exact(day, ns) {
476
+ const wall = wallPosition(day, ns);
477
+ const segment = this.#segment;
478
+ if (segment) {
479
+ const candidate = wall - segment.offset;
480
+ if (candidate >= segment.start && candidate < segment.end) return candidate;
481
+ }
482
+ const d = Number(floorDiv(wall, BIG_NS_PER_DAY));
483
+ const fields = wallFields(d, Number(wall - BigInt(d) * BIG_NS_PER_DAY));
484
+ const zdt = this.#iso.with(fields, { disambiguation: "compatible", offset: "ignore" });
485
+ this.#segment = segmentAround(zdt);
486
+ return zdt.epochNanoseconds;
487
+ }
488
+ at(position) {
489
+ const delta = position - this.origin;
490
+ const result = this.#iso.add({
491
+ seconds: Number(delta / BIG_NS_PER_SECOND),
492
+ nanoseconds: Number(delta % BIG_NS_PER_SECOND)
493
+ });
494
+ return withCalendarId(result, this.#calendarId);
495
+ }
496
+ duration(ns) {
497
+ return durationFrom(this.#iso.until(this.#iso), ns);
498
+ }
499
+ };
500
+ function segmentAround(zdt) {
501
+ const previous = zdt.add({ nanoseconds: 1 }).getTimeZoneTransition("previous");
502
+ const next = zdt.getTimeZoneTransition("next");
503
+ return {
504
+ start: previous ? previous.epochNanoseconds + SEGMENT_MARGIN : -FOREVER,
505
+ end: next ? next.epochNanoseconds - SEGMENT_MARGIN : FOREVER,
506
+ offset: BigInt(zdt.offsetNanoseconds)
507
+ };
508
+ }
509
+ function addPeriods(pending, periods) {
510
+ const first = periods[0];
511
+ const last = periods.at(-1);
512
+ if (first === void 0 || last === void 0) return pending;
513
+ const pendingFirst = pending[0];
514
+ const pendingLast = pending.at(-1);
515
+ if (pendingFirst === void 0 || pendingLast === void 0) return [...periods];
516
+ if (first[0] > pendingLast[1]) return pending.concat(periods);
517
+ if (last[1] < pendingFirst[0]) return periods.concat(pending);
518
+ return normalizePeriods([...pending, ...periods]);
519
+ }
520
+ function mixedTypesError(value) {
521
+ return typeError(`start and end must be the same Temporal type, got ${describe(value)} for end`);
522
+ }
523
+ function withTimeline(value, run) {
524
+ if (isZonedDateTime(value)) return run(new ZonedTimeline(value));
525
+ if (isPlainDateTime(value)) return run(new PlainTimeline(value));
526
+ throw typeError(
527
+ `expected a Temporal.PlainDateTime or ZonedDateTime, got ${describe(value)}` + (isPlainDate(value) ? " (use the workday functions for dates)" : "")
528
+ );
529
+ }
530
+ function noWorkingTimeError() {
531
+ return rangeError(`no working time found within ${MAX_SCAN_DAYS} days \u2014 check the hours/holiday configuration`);
532
+ }
533
+ function scan(cal, tl, from, dir, visit) {
534
+ let pending = [];
535
+ let day = Number(floorDiv(from - BigInt(dir) * tl.lookahead, BIG_NS_PER_DAY));
536
+ for (let idle = 0; ; day += dir) {
537
+ const periods = tl.periods(day, intervalsOn(cal, day, tl.dates));
538
+ idle = periods.length === 0 ? idle + 1 : 0;
539
+ if (idle > MAX_SCAN_DAYS) throw noWorkingTimeError();
540
+ pending = addPeriods(pending, periods);
541
+ const frontier = dir === 1 ? wallPosition(day + 1, 0) - tl.lookahead : wallPosition(day, 0) + tl.lookahead;
542
+ for (; ; ) {
543
+ const next = dir === 1 ? pending[0] : pending.at(-1);
544
+ if (next === void 0 || (dir === 1 ? next[0] >= frontier : next[1] <= frontier)) break;
545
+ if (dir === 1) pending.shift();
546
+ else pending.pop();
547
+ const result = visit(next);
548
+ if (result !== void 0) return result;
549
+ }
550
+ }
551
+ }
552
+ var CALENDAR_UNITS = ["years", "months", "weeks", "days"];
553
+ var TIME_UNITS = [
554
+ ["hours", BigInt(NS_PER_HOUR)],
555
+ ["minutes", BigInt(NS_PER_MINUTE)],
556
+ ["seconds", BigInt(NS_PER_SECOND)],
557
+ ["milliseconds", 1000000n],
558
+ ["microseconds", 1000n],
559
+ ["nanoseconds", 1n]
560
+ ];
561
+ function durationToNs(duration) {
562
+ if (typeof duration !== "object" || duration === null) {
563
+ throw typeError(`expected a Temporal.Duration or an object like { hours: 4 }, got ${describe(duration)}`);
564
+ }
565
+ let seen = false;
566
+ for (const unit of CALENDAR_UNITS) {
567
+ const value = duration[unit];
568
+ if (value === void 0) continue;
569
+ seen = true;
570
+ if (value !== 0) throw rangeError(`${unit} are not supported in working time; use addWorkdays for whole days`);
571
+ }
572
+ let total = 0n;
573
+ let sign = 0;
574
+ for (const [unit, nsPerUnit] of TIME_UNITS) {
575
+ const value = duration[unit];
576
+ if (value === void 0) continue;
577
+ seen = true;
578
+ if (typeof value !== "number" || !Number.isInteger(value)) {
579
+ throw rangeError(`duration ${unit} must be an integer, got ${describe(value)}`);
580
+ }
581
+ if (value === 0) continue;
582
+ if (sign !== 0 && Math.sign(value) !== sign) throw rangeError("duration fields must all have the same sign");
583
+ sign = Math.sign(value);
584
+ total += BigInt(value) * nsPerUnit;
585
+ }
586
+ if (!seen) throw typeError("duration needs at least one of hours, minutes, seconds, \u2026 (e.g. { hours: 4 })");
587
+ return total;
588
+ }
589
+ function negate(duration) {
590
+ if (typeof duration !== "object" || duration === null) return duration;
591
+ const negated = {};
592
+ for (const unit of [...CALENDAR_UNITS, ...TIME_UNITS.map(([u]) => u)]) {
593
+ const value = duration[unit];
594
+ negated[unit] = typeof value === "number" ? -value : value;
595
+ }
596
+ return negated;
597
+ }
598
+ function isWorkingPosition(cal, tl, position) {
599
+ const first = Number(floorDiv(position - tl.lookahead, BIG_NS_PER_DAY));
600
+ const last = Number(floorDiv(position + tl.lookahead, BIG_NS_PER_DAY));
601
+ for (let day = first; day <= last; day++) {
602
+ for (const [start, end] of tl.periods(day, intervalsOn(cal, day, tl.dates))) {
603
+ if (position >= start && position < end) return true;
604
+ }
605
+ }
606
+ return false;
607
+ }
608
+ function addForward(cal, tl, total) {
609
+ let cursor = tl.origin;
610
+ let remaining = total;
611
+ return scan(cal, tl, tl.origin, 1, ([start, end]) => {
612
+ const from = start > cursor ? start : cursor;
613
+ if (end <= from) return void 0;
614
+ if (remaining <= end - from) return tl.at(from + remaining);
615
+ remaining -= end - from;
616
+ cursor = end;
617
+ return void 0;
618
+ });
619
+ }
620
+ function addBackward(cal, tl, total) {
621
+ let cursor = tl.origin;
622
+ let remaining = total;
623
+ return scan(cal, tl, tl.origin, -1, ([start, end]) => {
624
+ const to = end < cursor ? end : cursor;
625
+ if (to <= start) return void 0;
626
+ if (remaining <= to - start) return tl.at(to - remaining);
627
+ remaining -= to - start;
628
+ cursor = start;
629
+ return void 0;
630
+ });
631
+ }
632
+ function workingNsBetween(cal, tl, a, b) {
633
+ let total = 0n;
634
+ let cursor = a;
635
+ scan(cal, tl, a, 1, ([start, end]) => {
636
+ if (start >= b) return true;
637
+ const from = start > cursor ? start : cursor;
638
+ const to = end < b ? end : b;
639
+ if (to > from) {
640
+ total += to - from;
641
+ cursor = to;
642
+ }
643
+ return void 0;
644
+ });
645
+ return total;
646
+ }
647
+ function rollForwardPosition(cal, tl) {
648
+ if (isWorkingPosition(cal, tl, tl.origin)) return void 0;
649
+ return scan(cal, tl, tl.origin, 1, ([start]) => start > tl.origin ? tl.at(start) : void 0);
650
+ }
651
+ function rollBackwardPosition(cal, tl) {
652
+ if (isWorkingPosition(cal, tl, tl.origin)) return void 0;
653
+ return scan(cal, tl, tl.origin, -1, ([, end]) => end <= tl.origin ? tl.at(end) : void 0);
654
+ }
655
+ function isWorkingTimeWith(cal, dateTime) {
656
+ return withTimeline(dateTime, (tl) => isWorkingPosition(cal, tl, tl.origin));
657
+ }
658
+ function addWorkingTimeWith(cal, dateTime, duration) {
659
+ const total = durationToNs(duration);
660
+ return withTimeline(dateTime, (tl) => {
661
+ if (total === 0n) return dateTime;
662
+ return total > 0n ? addForward(cal, tl, total) : addBackward(cal, tl, -total);
663
+ });
664
+ }
665
+ function subtractWorkingTimeWith(cal, dateTime, duration) {
666
+ return addWorkingTimeWith(cal, dateTime, negate(duration));
667
+ }
668
+ function workingTimeBetweenWith(cal, start, end) {
669
+ return withTimeline(start, (tl) => {
670
+ const a = tl.origin;
671
+ const b = tl.positionOf(end);
672
+ const ns = a <= b ? workingNsBetween(cal, tl, a, b) : -workingNsBetween(cal, tl, b, a);
673
+ return tl.duration(ns);
674
+ });
675
+ }
676
+ function rollForwardToWorkingTimeWith(cal, dateTime) {
677
+ return withTimeline(dateTime, (tl) => rollForwardPosition(cal, tl)) ?? dateTime;
678
+ }
679
+ function rollBackwardToWorkingTimeWith(cal, dateTime) {
680
+ return withTimeline(dateTime, (tl) => rollBackwardPosition(cal, tl)) ?? dateTime;
163
681
  }
164
- function shift(date, days) {
682
+ function getWorkingHoursWith(cal, date) {
683
+ const dates = new DateFactory(toPlainDate(date));
684
+ return formatDayHours(intervalsOn(cal, dates.epochDay, dates));
685
+ }
686
+ function isWorkingTime(dateTime, options) {
687
+ return isWorkingTimeWith(compileHoursCalendar(options), dateTime);
688
+ }
689
+ function addWorkingTime(dateTime, duration, options) {
690
+ return addWorkingTimeWith(compileHoursCalendar(options), dateTime, duration);
691
+ }
692
+ function subtractWorkingTime(dateTime, duration, options) {
693
+ return subtractWorkingTimeWith(compileHoursCalendar(options), dateTime, duration);
694
+ }
695
+ function workingTimeBetween(start, end, options) {
696
+ return workingTimeBetweenWith(compileHoursCalendar(options), start, end);
697
+ }
698
+ function rollForwardToWorkingTime(dateTime, options) {
699
+ return rollForwardToWorkingTimeWith(compileHoursCalendar(options), dateTime);
700
+ }
701
+ function rollBackwardToWorkingTime(dateTime, options) {
702
+ return rollBackwardToWorkingTimeWith(compileHoursCalendar(options), dateTime);
703
+ }
704
+ function getWorkingHours(date, options) {
705
+ return getWorkingHoursWith(compileHoursCalendar(options), date);
706
+ }
707
+
708
+ // src/workdays.ts
709
+ function shiftDays(date, days) {
165
710
  return days === 0 ? date : date.add({ days });
166
711
  }
712
+ function checkAmount(amount) {
713
+ if (!Number.isSafeInteger(amount)) throw rangeError(`amount must be an integer, got ${describe(amount)}`);
714
+ }
715
+ function isWeekendWith(cal, date) {
716
+ return isWeekendDay(cal, new DateFactory(toPlainDate(date)).epochDay);
717
+ }
718
+ function isHolidayWith(cal, date) {
719
+ const dates = new DateFactory(toPlainDate(date));
720
+ return isHolidayDay(cal, dates.epochDay, dates);
721
+ }
722
+ function isWorkdayWith(cal, date) {
723
+ const dates = new DateFactory(toPlainDate(date));
724
+ return isWorkdayDay(cal, dates.epochDay, dates);
725
+ }
726
+ function addWorkdaysWith(cal, date, amount) {
727
+ checkAmount(amount);
728
+ const dates = new DateFactory(toPlainDate(date));
729
+ if (amount === 0) return date;
730
+ const target = stepWorkdays(cal, dates.epochDay, Math.abs(amount), amount > 0 ? 1 : -1, dates);
731
+ return shiftDays(date, target - dates.epochDay);
732
+ }
733
+ function subtractWorkdaysWith(cal, date, amount) {
734
+ checkAmount(amount);
735
+ return addWorkdaysWith(cal, date, -amount);
736
+ }
737
+ function nextWorkdayWith(cal, date) {
738
+ const dates = new DateFactory(toPlainDate(date));
739
+ return shiftDays(date, stepWorkdays(cal, dates.epochDay, 1, 1, dates) - dates.epochDay);
740
+ }
741
+ function previousWorkdayWith(cal, date) {
742
+ const dates = new DateFactory(toPlainDate(date));
743
+ return shiftDays(date, stepWorkdays(cal, dates.epochDay, 1, -1, dates) - dates.epochDay);
744
+ }
745
+ function rollForwardWith(cal, date) {
746
+ return isWorkdayWith(cal, date) ? date : nextWorkdayWith(cal, date);
747
+ }
748
+ function rollBackwardWith(cal, date) {
749
+ return isWorkdayWith(cal, date) ? date : previousWorkdayWith(cal, date);
750
+ }
751
+ function workdaysBetweenWith(cal, start, end) {
752
+ const a = new DateFactory(toPlainDate(start));
753
+ const endDay = new DateFactory(toPlainDate(end)).epochDay;
754
+ return countWorkdays(cal, a.epochDay, endDay, a);
755
+ }
756
+ function* eachWorkdayWith(cal, start, end) {
757
+ const dates = new DateFactory(toPlainDate(start));
758
+ const endDay = new DateFactory(toPlainDate(end)).epochDay;
759
+ for (let day = dates.epochDay; day <= endDay; day++) {
760
+ if (isWorkdayDay(cal, day, dates)) yield shiftDays(start, day - dates.epochDay);
761
+ }
762
+ }
167
763
  function isWeekend(date, options) {
168
- return getCompiled(options).weekend[toPlainDate(date).dayOfWeek] === true;
764
+ return isWeekendWith(compileDayCalendar(options), date);
169
765
  }
170
766
  function isHoliday(date, options) {
171
- const c = getCompiled(options);
172
- const base = toPlainDate(date);
173
- const day = epochDayOfPlainDate(base);
174
- return !c.extra.has(day) && isHolidayDay(c, day, base, day);
767
+ return isHolidayWith(compileDayCalendar(options), date);
175
768
  }
176
769
  function isWorkday(date, options) {
177
- const c = getCompiled(options);
178
- const base = toPlainDate(date);
179
- const day = epochDayOfPlainDate(base);
180
- return isWorkdayDay(c, day, base, day);
770
+ return isWorkdayWith(compileDayCalendar(options), date);
181
771
  }
182
772
  function addWorkdays(date, amount, options) {
183
- checkInteger(amount, "amount");
184
- if (amount === 0) return date;
185
- const c = getCompiled(options);
186
- return shift(date, step(c, toPlainDate(date), Math.abs(amount), amount > 0 ? 1 : -1));
773
+ return addWorkdaysWith(compileDayCalendar(options), date, amount);
187
774
  }
188
775
  function subtractWorkdays(date, amount, options) {
189
- checkInteger(amount, "amount");
190
- return addWorkdays(date, -amount, options);
776
+ return subtractWorkdaysWith(compileDayCalendar(options), date, amount);
191
777
  }
192
778
  function nextWorkday(date, options) {
193
- return shift(date, step(getCompiled(options), toPlainDate(date), 1, 1));
779
+ return nextWorkdayWith(compileDayCalendar(options), date);
194
780
  }
195
781
  function previousWorkday(date, options) {
196
- return shift(date, step(getCompiled(options), toPlainDate(date), 1, -1));
782
+ return previousWorkdayWith(compileDayCalendar(options), date);
197
783
  }
198
784
  function rollForward(date, options) {
199
- return isWorkday(date, options) ? date : nextWorkday(date, options);
785
+ return rollForwardWith(compileDayCalendar(options), date);
200
786
  }
201
787
  function rollBackward(date, options) {
202
- return isWorkday(date, options) ? date : previousWorkday(date, options);
788
+ return rollBackwardWith(compileDayCalendar(options), date);
203
789
  }
204
790
  function workdaysBetween(start, end, options) {
205
- const c = getCompiled(options);
206
- const a = toPlainDate(start);
207
- const b = toPlainDate(end);
208
- const aDay = epochDayOfPlainDate(a);
209
- const bDay = epochDayOfPlainDate(b);
210
- if (aDay === bDay) return 0;
211
- const [lo, hi, base, baseDay, sign] = aDay < bDay ? [aDay, bDay, a, aDay, 1] : [bDay, aDay, b, bDay, -1];
212
- let count = 0;
213
- for (let day = lo + 1; day <= hi; day++) {
214
- if (isWorkdayDay(c, day, base, baseDay)) count++;
215
- }
216
- return sign * count;
791
+ return workdaysBetweenWith(compileDayCalendar(options), start, end);
217
792
  }
218
- function* eachWorkday(start, end, options) {
219
- const c = getCompiled(options);
220
- const base = toPlainDate(start);
221
- const baseDay = epochDayOfPlainDate(base);
222
- const endDay = epochDayOfPlainDate(toPlainDate(end));
223
- for (let day = baseDay; day <= endDay; day++) {
224
- if (isWorkdayDay(c, day, base, baseDay)) yield shift(start, day - baseDay);
225
- }
793
+ function eachWorkday(start, end, options) {
794
+ return eachWorkdayWith(compileDayCalendar(options), start, end);
226
795
  }
796
+
797
+ // src/work-calendar.ts
227
798
  function createWorkCalendar(options = {}) {
228
- const frozen = Object.freeze({
229
- weekend: options.weekend ? Object.freeze([...options.weekend]) : void 0,
230
- holidays: typeof options.holidays === "function" || options.holidays == null ? options.holidays : Object.freeze([...options.holidays]),
231
- extraWorkdays: options.extraWorkdays ? Object.freeze([...options.extraWorkdays]) : void 0
799
+ checkOptionsObject(options);
800
+ const snapshot = snapshotOptions(options);
801
+ const days = compileDayCalendar(snapshot);
802
+ const calendar = {
803
+ options: snapshot,
804
+ isWorkday: (date) => isWorkdayWith(days, date),
805
+ isWeekend: (date) => isWeekendWith(days, date),
806
+ isHoliday: (date) => isHolidayWith(days, date),
807
+ addWorkdays: (date, amount) => addWorkdaysWith(days, date, amount),
808
+ subtractWorkdays: (date, amount) => subtractWorkdaysWith(days, date, amount),
809
+ nextWorkday: (date) => nextWorkdayWith(days, date),
810
+ previousWorkday: (date) => previousWorkdayWith(days, date),
811
+ rollForward: (date) => rollForwardWith(days, date),
812
+ rollBackward: (date) => rollBackwardWith(days, date),
813
+ workdaysBetween: (start, end) => workdaysBetweenWith(days, start, end),
814
+ eachWorkday: (start, end) => eachWorkdayWith(days, start, end)
815
+ };
816
+ if (!hasHours(snapshot)) return Object.freeze(calendar);
817
+ const hours = compileHoursCalendar(snapshot);
818
+ return Object.freeze({
819
+ ...calendar,
820
+ options: snapshot,
821
+ isWorkingTime: (dateTime) => isWorkingTimeWith(hours, dateTime),
822
+ addWorkingTime: (dateTime, duration) => addWorkingTimeWith(hours, dateTime, duration),
823
+ subtractWorkingTime: (dateTime, duration) => subtractWorkingTimeWith(hours, dateTime, duration),
824
+ workingTimeBetween: (start, end) => workingTimeBetweenWith(hours, start, end),
825
+ rollForwardToWorkingTime: (dateTime) => rollForwardToWorkingTimeWith(hours, dateTime),
826
+ rollBackwardToWorkingTime: (dateTime) => rollBackwardToWorkingTimeWith(hours, dateTime),
827
+ getWorkingHours: (date) => getWorkingHoursWith(hours, date)
232
828
  });
233
- cache.set(frozen, compile(frozen));
829
+ }
830
+ function hasHours(options) {
831
+ return options.hours !== void 0;
832
+ }
833
+ function snapshotOptions(options) {
834
+ const copy = (value) => isIterable(value) ? Object.freeze(Array.from(value)) : value;
234
835
  return Object.freeze({
235
- options: frozen,
236
- isWorkday: (d) => isWorkday(d, frozen),
237
- isWeekend: (d) => isWeekend(d, frozen),
238
- isHoliday: (d) => isHoliday(d, frozen),
239
- addWorkdays: (d, n) => addWorkdays(d, n, frozen),
240
- subtractWorkdays: (d, n) => subtractWorkdays(d, n, frozen),
241
- nextWorkday: (d) => nextWorkday(d, frozen),
242
- previousWorkday: (d) => previousWorkday(d, frozen),
243
- rollForward: (d) => rollForward(d, frozen),
244
- rollBackward: (d) => rollBackward(d, frozen),
245
- workdaysBetween: (a, b) => workdaysBetween(a, b, frozen),
246
- eachWorkday: (a, b) => eachWorkday(a, b, frozen)
836
+ ...options,
837
+ weekend: copy(options.weekend),
838
+ holidays: copy(options.holidays),
839
+ extraWorkdays: copy(options.extraWorkdays)
247
840
  });
248
841
  }
249
842
 
843
+ // src/date-holidays.ts
844
+ var ENTRY_DATE_RE = /^(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})(?: ([+-])(\d{2})(\d{2}))?$/;
845
+ var DAY = BigInt(NS_PER_DAY);
846
+ var SNAP = 90n * BigInt(NS_PER_MINUTE);
847
+ function wallSpan(entry) {
848
+ const m = ENTRY_DATE_RE.exec(entry.date);
849
+ if (!m) throw rangeError(`unexpected date-holidays date ${describe(entry.date)}`);
850
+ const n = (i) => Number(m[i]);
851
+ const shiftSign = m[7] === "-" ? -1 : 1;
852
+ const shift = m[7] === void 0 ? 0 : shiftSign * (n(8) * NS_PER_HOUR + n(9) * NS_PER_MINUTE);
853
+ const timeNs = n(4) * NS_PER_HOUR + n(5) * NS_PER_MINUTE + n(6) * 1e9 + shift;
854
+ const start = BigInt(epochDayFromIso(n(1), n(2), n(3))) * DAY + BigInt(timeNs);
855
+ const durationMs = entry.end.getTime() - entry.start.getTime();
856
+ if (!Number.isFinite(durationMs) || durationMs <= 0) {
857
+ throw rangeError(`date-holidays entry ${describe(entry.date)} has no valid start/end`);
858
+ }
859
+ let end = start + BigInt(durationMs) * 1000000n;
860
+ const nearestMidnight = floorDiv(end + DAY / 2n, DAY) * DAY;
861
+ const distance = end - nearestMidnight;
862
+ if (distance !== 0n && distance > -SNAP && distance < SNAP) end = nearestMidnight;
863
+ return [start, end];
864
+ }
865
+ function addSpan(data, [start, end]) {
866
+ for (let day = floorDiv(start, DAY); day * DAY < end; day++) {
867
+ const dayStart = day * DAY;
868
+ const from = start > dayStart ? start - dayStart : 0n;
869
+ const to = end < dayStart + DAY ? end - dayStart : DAY;
870
+ const epochDay = Number(day);
871
+ if (from === 0n && to === DAY) {
872
+ data.full.add(epochDay);
873
+ } else {
874
+ const list = data.partial.get(epochDay) ?? [];
875
+ list.push([Number(from), Number(to)]);
876
+ data.partial.set(epochDay, list);
877
+ }
878
+ }
879
+ }
880
+ function fromDateHolidays(source, options = {}) {
881
+ if (typeof source?.getHolidays !== "function") {
882
+ throw typeError(`expected a date-holidays instance (with getHolidays), got ${describe(source)}`);
883
+ }
884
+ const typesOption = options.types ?? ["public"];
885
+ if (!Array.isArray(typesOption) || !typesOption.every((t) => typeof t === "string")) {
886
+ throw typeError(`types must be an array of strings, got ${describe(typesOption)}`);
887
+ }
888
+ const types = new Set(typesOption);
889
+ const loaded = /* @__PURE__ */ new Set();
890
+ const known = { full: /* @__PURE__ */ new Set(), partial: /* @__PURE__ */ new Map() };
891
+ const ensure = (date) => {
892
+ const iso = toIsoDate(date);
893
+ for (const year of [iso.year - 1, iso.year, iso.year + 1]) {
894
+ if (loaded.has(year)) continue;
895
+ loaded.add(year);
896
+ for (const entry of source.getHolidays(year)) {
897
+ if (types.has(entry.type)) addSpan(known, wallSpan(entry));
898
+ }
899
+ }
900
+ return epochDayOf(iso);
901
+ };
902
+ return {
903
+ holidays: (date) => known.full.has(ensure(date)),
904
+ specialHours: (date, regular) => {
905
+ const day = ensure(date);
906
+ if (known.full.has(day)) return void 0;
907
+ const closures = known.partial.get(day);
908
+ if (!closures) return void 0;
909
+ let open = parseDayHours(regular, "regular hours");
910
+ for (const [from, to] of closures) open = subtractInterval(open, from, to);
911
+ return formatDayHours(open);
912
+ }
913
+ };
914
+ }
915
+
250
916
  exports.addWorkdays = addWorkdays;
917
+ exports.addWorkingTime = addWorkingTime;
251
918
  exports.createWorkCalendar = createWorkCalendar;
252
919
  exports.eachWorkday = eachWorkday;
920
+ exports.fromDateHolidays = fromDateHolidays;
921
+ exports.getWorkingHours = getWorkingHours;
253
922
  exports.isHoliday = isHoliday;
254
923
  exports.isWeekend = isWeekend;
255
924
  exports.isWorkday = isWorkday;
925
+ exports.isWorkingTime = isWorkingTime;
256
926
  exports.nextWorkday = nextWorkday;
257
927
  exports.previousWorkday = previousWorkday;
258
928
  exports.rollBackward = rollBackward;
929
+ exports.rollBackwardToWorkingTime = rollBackwardToWorkingTime;
259
930
  exports.rollForward = rollForward;
931
+ exports.rollForwardToWorkingTime = rollForwardToWorkingTime;
260
932
  exports.subtractWorkdays = subtractWorkdays;
933
+ exports.subtractWorkingTime = subtractWorkingTime;
261
934
  exports.workdaysBetween = workdaysBetween;
935
+ exports.workingTimeBetween = workingTimeBetween;
262
936
  //# sourceMappingURL=index.cjs.map
263
937
  //# sourceMappingURL=index.cjs.map