temporal-workdays 0.0.0-stage → 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/CHANGELOG.md +30 -0
- package/LICENSE +21 -0
- package/README.md +204 -2
- package/dist/index.cjs +937 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +320 -0
- package/dist/index.d.ts +320 -0
- package/dist/index.js +916 -0
- package/dist/index.js.map +1 -0
- package/package.json +94 -4
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,937 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/epoch.ts
|
|
4
|
+
function epochDayFromIso(year, month, day) {
|
|
5
|
+
const y = month <= 2 ? year - 1 : year;
|
|
6
|
+
const era = Math.floor(y / 400);
|
|
7
|
+
const yoe = y - era * 400;
|
|
8
|
+
const mp = (month + 9) % 12;
|
|
9
|
+
const doy = Math.floor((153 * mp + 2) / 5) + day - 1;
|
|
10
|
+
const doe = yoe * 365 + Math.floor(yoe / 4) - Math.floor(yoe / 100) + doy;
|
|
11
|
+
return era * 146097 + doe - 719468;
|
|
12
|
+
}
|
|
13
|
+
function isoFromEpochDay(epochDay) {
|
|
14
|
+
const z = epochDay + 719468;
|
|
15
|
+
const era = Math.floor(z / 146097);
|
|
16
|
+
const doe = z - era * 146097;
|
|
17
|
+
const yoe = Math.floor((doe - Math.floor(doe / 1460) + Math.floor(doe / 36524) - Math.floor(doe / 146096)) / 365);
|
|
18
|
+
const doy = doe - (365 * yoe + Math.floor(yoe / 4) - Math.floor(yoe / 100));
|
|
19
|
+
const mp = Math.floor((5 * doy + 2) / 153);
|
|
20
|
+
const day = doy - Math.floor((153 * mp + 2) / 5) + 1;
|
|
21
|
+
const month = mp < 10 ? mp + 3 : mp - 9;
|
|
22
|
+
const year = yoe + era * 400 + (month <= 2 ? 1 : 0);
|
|
23
|
+
return [year, month, day];
|
|
24
|
+
}
|
|
25
|
+
function dayOfWeekFromEpochDay(epochDay) {
|
|
26
|
+
return ((epochDay + 3) % 7 + 7) % 7 + 1;
|
|
27
|
+
}
|
|
28
|
+
function isLeapYear(year) {
|
|
29
|
+
return year % 4 === 0 && year % 100 !== 0 || year % 400 === 0;
|
|
30
|
+
}
|
|
31
|
+
function daysInMonth(year, month) {
|
|
32
|
+
if (month === 2) return isLeapYear(year) ? 29 : 28;
|
|
33
|
+
return month === 4 || month === 6 || month === 9 || month === 11 ? 30 : 31;
|
|
34
|
+
}
|
|
35
|
+
function floorDiv(a, b) {
|
|
36
|
+
const q = a / b;
|
|
37
|
+
return a % b < 0n ? q - 1n : q;
|
|
38
|
+
}
|
|
39
|
+
|
|
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();
|
|
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);
|
|
94
|
+
}
|
|
95
|
+
function epochDayOf(date) {
|
|
96
|
+
const iso = toIsoDate(date);
|
|
97
|
+
return epochDayFromIso(iso.year, iso.month, iso.day);
|
|
98
|
+
}
|
|
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
|
+
}
|
|
129
|
+
}
|
|
130
|
+
function parseDate(value) {
|
|
131
|
+
if (typeof value === "string") {
|
|
132
|
+
const m = DATE_RE.exec(value);
|
|
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)}`);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
function compileDayCalendar(options = {}) {
|
|
174
|
+
checkOptionsObject(options);
|
|
175
|
+
checkOptionKeys(options, OPTION_KEYS);
|
|
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)}`);
|
|
179
|
+
for (const d of weekendDays) {
|
|
180
|
+
if (!Number.isInteger(d) || d < 1 || d > 7) {
|
|
181
|
+
throw rangeError(`weekend days must be integers 1 (Mon) \u2013 7 (Sun), got ${describe(d)}`);
|
|
182
|
+
}
|
|
183
|
+
weekend[d] = true;
|
|
184
|
+
}
|
|
185
|
+
const dates = /* @__PURE__ */ new Set();
|
|
186
|
+
const annual = /* @__PURE__ */ new Set();
|
|
187
|
+
let predicate;
|
|
188
|
+
const holidays = options.holidays;
|
|
189
|
+
if (typeof holidays === "function") {
|
|
190
|
+
predicate = holidays;
|
|
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);
|
|
194
|
+
}
|
|
195
|
+
const extra = /* @__PURE__ */ new Set();
|
|
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));
|
|
200
|
+
}
|
|
201
|
+
return { weekend, dates, annual, predicate, extra };
|
|
202
|
+
}
|
|
203
|
+
function isWeekendDay(cal, epochDay) {
|
|
204
|
+
return cal.weekend[dayOfWeekFromEpochDay(epochDay)] === true;
|
|
205
|
+
}
|
|
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;
|
|
211
|
+
}
|
|
212
|
+
return Boolean(cal.predicate?.(dates.at(epochDay)));
|
|
213
|
+
}
|
|
214
|
+
function isHolidayDay(cal, epochDay, dates) {
|
|
215
|
+
return !cal.extra.has(epochDay) && isListedHoliday(cal, epochDay, dates);
|
|
216
|
+
}
|
|
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);
|
|
221
|
+
}
|
|
222
|
+
function noWorkdayError() {
|
|
223
|
+
return rangeError(`no workday found within ${MAX_SCAN_DAYS} days \u2014 check the weekend/holiday configuration`);
|
|
224
|
+
}
|
|
225
|
+
function stepWorkdays(cal, from, count, dir, dates) {
|
|
226
|
+
let day = from;
|
|
227
|
+
let remaining = count;
|
|
228
|
+
let gap = 0;
|
|
229
|
+
while (remaining > 0) {
|
|
230
|
+
day += dir;
|
|
231
|
+
if (isWorkdayDay(cal, day, dates)) {
|
|
232
|
+
remaining--;
|
|
233
|
+
gap = 0;
|
|
234
|
+
} else if (++gap > MAX_SCAN_DAYS) {
|
|
235
|
+
throw noWorkdayError();
|
|
236
|
+
}
|
|
237
|
+
}
|
|
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;
|
|
681
|
+
}
|
|
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) {
|
|
710
|
+
return days === 0 ? date : date.add({ days });
|
|
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
|
+
}
|
|
763
|
+
function isWeekend(date, options) {
|
|
764
|
+
return isWeekendWith(compileDayCalendar(options), date);
|
|
765
|
+
}
|
|
766
|
+
function isHoliday(date, options) {
|
|
767
|
+
return isHolidayWith(compileDayCalendar(options), date);
|
|
768
|
+
}
|
|
769
|
+
function isWorkday(date, options) {
|
|
770
|
+
return isWorkdayWith(compileDayCalendar(options), date);
|
|
771
|
+
}
|
|
772
|
+
function addWorkdays(date, amount, options) {
|
|
773
|
+
return addWorkdaysWith(compileDayCalendar(options), date, amount);
|
|
774
|
+
}
|
|
775
|
+
function subtractWorkdays(date, amount, options) {
|
|
776
|
+
return subtractWorkdaysWith(compileDayCalendar(options), date, amount);
|
|
777
|
+
}
|
|
778
|
+
function nextWorkday(date, options) {
|
|
779
|
+
return nextWorkdayWith(compileDayCalendar(options), date);
|
|
780
|
+
}
|
|
781
|
+
function previousWorkday(date, options) {
|
|
782
|
+
return previousWorkdayWith(compileDayCalendar(options), date);
|
|
783
|
+
}
|
|
784
|
+
function rollForward(date, options) {
|
|
785
|
+
return rollForwardWith(compileDayCalendar(options), date);
|
|
786
|
+
}
|
|
787
|
+
function rollBackward(date, options) {
|
|
788
|
+
return rollBackwardWith(compileDayCalendar(options), date);
|
|
789
|
+
}
|
|
790
|
+
function workdaysBetween(start, end, options) {
|
|
791
|
+
return workdaysBetweenWith(compileDayCalendar(options), start, end);
|
|
792
|
+
}
|
|
793
|
+
function eachWorkday(start, end, options) {
|
|
794
|
+
return eachWorkdayWith(compileDayCalendar(options), start, end);
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
// src/work-calendar.ts
|
|
798
|
+
function createWorkCalendar(options = {}) {
|
|
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)
|
|
828
|
+
});
|
|
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;
|
|
835
|
+
return Object.freeze({
|
|
836
|
+
...options,
|
|
837
|
+
weekend: copy(options.weekend),
|
|
838
|
+
holidays: copy(options.holidays),
|
|
839
|
+
extraWorkdays: copy(options.extraWorkdays)
|
|
840
|
+
});
|
|
841
|
+
}
|
|
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
|
+
|
|
916
|
+
exports.addWorkdays = addWorkdays;
|
|
917
|
+
exports.addWorkingTime = addWorkingTime;
|
|
918
|
+
exports.createWorkCalendar = createWorkCalendar;
|
|
919
|
+
exports.eachWorkday = eachWorkday;
|
|
920
|
+
exports.fromDateHolidays = fromDateHolidays;
|
|
921
|
+
exports.getWorkingHours = getWorkingHours;
|
|
922
|
+
exports.isHoliday = isHoliday;
|
|
923
|
+
exports.isWeekend = isWeekend;
|
|
924
|
+
exports.isWorkday = isWorkday;
|
|
925
|
+
exports.isWorkingTime = isWorkingTime;
|
|
926
|
+
exports.nextWorkday = nextWorkday;
|
|
927
|
+
exports.previousWorkday = previousWorkday;
|
|
928
|
+
exports.rollBackward = rollBackward;
|
|
929
|
+
exports.rollBackwardToWorkingTime = rollBackwardToWorkingTime;
|
|
930
|
+
exports.rollForward = rollForward;
|
|
931
|
+
exports.rollForwardToWorkingTime = rollForwardToWorkingTime;
|
|
932
|
+
exports.subtractWorkdays = subtractWorkdays;
|
|
933
|
+
exports.subtractWorkingTime = subtractWorkingTime;
|
|
934
|
+
exports.workdaysBetween = workdaysBetween;
|
|
935
|
+
exports.workingTimeBetween = workingTimeBetween;
|
|
936
|
+
//# sourceMappingURL=index.cjs.map
|
|
937
|
+
//# sourceMappingURL=index.cjs.map
|