bizdays-toolkit 0.2.0 → 0.3.1
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 +43 -1
- package/README.md +21 -2
- package/dist/core/calendar.d.ts +63 -10
- package/dist/core/calendar.js +77 -46
- package/dist/core/date.d.ts +24 -6
- package/dist/core/date.js +24 -10
- package/dist/core/errors.d.ts +78 -0
- package/dist/core/errors.js +61 -0
- package/dist/core/input.d.ts +25 -10
- package/dist/core/input.js +41 -21
- package/dist/core/schedule.d.ts +110 -16
- package/dist/core/schedule.js +296 -73
- package/dist/core/types.d.ts +11 -3
- package/dist/index.d.ts +7 -3
- package/dist/index.js +5 -1
- package/dist/jp/index.d.ts +41 -12
- package/dist/jp/index.js +36 -10
- package/dist/jp/rules.d.ts +17 -8
- package/dist/jp/rules.js +8 -7
- package/package.json +8 -4
package/dist/core/schedule.js
CHANGED
|
@@ -9,86 +9,263 @@
|
|
|
9
9
|
*
|
|
10
10
|
* 営業日カレンダー(createCalendar)は、このスケジュールの真偽値版として作られている。
|
|
11
11
|
*/
|
|
12
|
-
import { parseDate, toDayNumber, weekdayOf } from "./date.js";
|
|
12
|
+
import { isValidDate, parseDate, toDayNumber, weekdayOf, } from "./date.js";
|
|
13
|
+
import { checkUnknownKeys, IssueCollector, rangeError } from "./errors.js";
|
|
13
14
|
import { instantToCivil, toCivil, toDateTimeParts, validateTimeZone, } from "./input.js";
|
|
14
15
|
// ------------------------------------------------------------
|
|
15
|
-
//
|
|
16
|
+
// 入力チェック(誤りを集める。作成時は最初の誤りを例外にする)
|
|
16
17
|
// ------------------------------------------------------------
|
|
17
18
|
const MONTH_DAY_RE = /^(\d{2})-(\d{2})$/;
|
|
18
19
|
const MAX_DAYS_IN_MONTH = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
|
|
19
|
-
|
|
20
|
-
|
|
20
|
+
const isObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
21
|
+
/** @internal "MM-DD" として正しいか(2 月 29 日は可) */
|
|
22
|
+
export function isValidMonthDay(value) {
|
|
23
|
+
if (typeof value !== "string")
|
|
24
|
+
return false;
|
|
21
25
|
const m = MONTH_DAY_RE.exec(value);
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
+
if (!m)
|
|
27
|
+
return false;
|
|
28
|
+
const month = Number(m[1]);
|
|
29
|
+
const day = Number(m[2]);
|
|
30
|
+
return month >= 1 && month <= 12 && day >= 1 && day <= MAX_DAYS_IN_MONTH[month - 1];
|
|
31
|
+
}
|
|
32
|
+
/** @internal 曜日の番号として正しいか */
|
|
33
|
+
export const isWeekday = (value) => Number.isInteger(value) && value >= 0 && value <= 6;
|
|
34
|
+
function checkName(c, path, name) {
|
|
35
|
+
if (name === undefined)
|
|
36
|
+
return undefined;
|
|
37
|
+
if (typeof name !== "string") {
|
|
38
|
+
c.error("invalid-option", `${path}.name`, "name must be a string");
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
41
|
+
return name;
|
|
42
|
+
}
|
|
43
|
+
function checkDate(c, path, value) {
|
|
44
|
+
if (!isValidDate(value)) {
|
|
45
|
+
c.error("invalid-date", path, `Invalid date ${JSON.stringify(value)} (expected YYYY-MM-DD)`);
|
|
46
|
+
return undefined;
|
|
47
|
+
}
|
|
48
|
+
return toDayNumber(value);
|
|
49
|
+
}
|
|
50
|
+
function checkMonthDay(c, path, value) {
|
|
51
|
+
if (!isValidMonthDay(value)) {
|
|
52
|
+
c.error("invalid-month-day", path, `Invalid month-day ${JSON.stringify(value)} (expected MM-DD)`);
|
|
53
|
+
return undefined;
|
|
26
54
|
}
|
|
27
55
|
return value;
|
|
28
56
|
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
57
|
+
/**
|
|
58
|
+
* @internal 個別の日付の規則を検証して取り込む。
|
|
59
|
+
* entry は "YYYY-MM-DD"・{ date }・{ from, to } のいずれか。値は valueOf で決める
|
|
60
|
+
*/
|
|
61
|
+
export function compileDateRules(c, path, entries, valueOf, allowString) {
|
|
62
|
+
if (entries === undefined)
|
|
63
|
+
return [];
|
|
64
|
+
if (!Array.isArray(entries)) {
|
|
65
|
+
c.error("invalid-option", path, "must be an array");
|
|
66
|
+
return [];
|
|
67
|
+
}
|
|
68
|
+
const rules = [];
|
|
69
|
+
entries.forEach((raw, i) => {
|
|
70
|
+
const p = `${path}[${i}]`;
|
|
71
|
+
const entry = allowString && typeof raw === "string" ? { date: raw } : raw;
|
|
72
|
+
if (!isObject(entry)) {
|
|
73
|
+
c.error("invalid-option", p, allowString ? "must be a date string or an object" : "must be an object");
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
const value = valueOf(entry, p);
|
|
77
|
+
const name = checkName(c, p, entry["name"]);
|
|
78
|
+
if ("date" in entry) {
|
|
79
|
+
checkUnknownKeys(c, p, entry, ["date", "name", "value"]);
|
|
80
|
+
const n = checkDate(c, `${p}.date`, entry["date"]);
|
|
81
|
+
if (n !== undefined && value !== undefined)
|
|
82
|
+
rules.push({ from: n, to: n, value, name });
|
|
83
|
+
return;
|
|
34
84
|
}
|
|
35
|
-
|
|
36
|
-
|
|
85
|
+
if (!("from" in entry) || !("to" in entry)) {
|
|
86
|
+
c.error("invalid-option", p, 'must have "date" or both "from" and "to"');
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
checkUnknownKeys(c, p, entry, ["from", "to", "name", "value"]);
|
|
90
|
+
const from = checkDate(c, `${p}.from`, entry["from"]);
|
|
91
|
+
const to = checkDate(c, `${p}.to`, entry["to"]);
|
|
92
|
+
if (from === undefined || to === undefined)
|
|
93
|
+
return;
|
|
37
94
|
if (from > to) {
|
|
38
|
-
|
|
95
|
+
c.error("invalid-range", p, `"from" must not be after "to" (${String(entry["from"])} > ${String(entry["to"])})`);
|
|
96
|
+
return;
|
|
39
97
|
}
|
|
40
|
-
|
|
98
|
+
if (value !== undefined)
|
|
99
|
+
rules.push({ from, to, value, name });
|
|
41
100
|
});
|
|
101
|
+
return rules;
|
|
42
102
|
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
103
|
+
/** @internal 毎年の月日の規則を検証して取り込む(entry は "MM-DD"・{ date }・{ from, to }) */
|
|
104
|
+
export function compileAnnualRules(c, path, entries, valueOf, allowString) {
|
|
105
|
+
if (entries === undefined)
|
|
106
|
+
return [];
|
|
107
|
+
if (!Array.isArray(entries)) {
|
|
108
|
+
c.error("invalid-option", path, "must be an array");
|
|
109
|
+
return [];
|
|
110
|
+
}
|
|
111
|
+
const rules = [];
|
|
112
|
+
entries.forEach((raw, i) => {
|
|
113
|
+
const p = `${path}[${i}]`;
|
|
114
|
+
const entry = allowString && typeof raw === "string" ? { date: raw } : raw;
|
|
115
|
+
if (!isObject(entry)) {
|
|
116
|
+
c.error("invalid-option", p, allowString ? "must be a month-day string or an object" : "must be an object");
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
const value = valueOf(entry, p);
|
|
120
|
+
const name = checkName(c, p, entry["name"]);
|
|
121
|
+
if ("date" in entry) {
|
|
122
|
+
checkUnknownKeys(c, p, entry, ["date", "name", "value"]);
|
|
123
|
+
const md = checkMonthDay(c, `${p}.date`, entry["date"]);
|
|
124
|
+
if (md !== undefined && value !== undefined)
|
|
125
|
+
rules.push({ from: md, to: md, value, name });
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
if (!("from" in entry) || !("to" in entry)) {
|
|
129
|
+
c.error("invalid-option", p, 'must have "date" or both "from" and "to"');
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
checkUnknownKeys(c, p, entry, ["from", "to", "name", "value"]);
|
|
133
|
+
const from = checkMonthDay(c, `${p}.from`, entry["from"]);
|
|
134
|
+
const to = checkMonthDay(c, `${p}.to`, entry["to"]);
|
|
135
|
+
if (from !== undefined && to !== undefined && value !== undefined) {
|
|
136
|
+
rules.push({ from, to, value, name });
|
|
48
137
|
}
|
|
49
|
-
return { from: parseMonthDay(e.from), to: parseMonthDay(e.to), value: e.value, name: e.name };
|
|
50
138
|
});
|
|
139
|
+
return rules;
|
|
51
140
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
141
|
+
/** 規則の value を取り出す(value が必須の規則用) */
|
|
142
|
+
function requiredValue(c) {
|
|
143
|
+
return (entry, path) => {
|
|
144
|
+
if (!("value" in entry)) {
|
|
145
|
+
c.error("invalid-option", path, 'must have "value"');
|
|
146
|
+
return undefined;
|
|
147
|
+
}
|
|
148
|
+
return entry["value"];
|
|
149
|
+
};
|
|
56
150
|
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
151
|
+
function validateProvider(c, path, p) {
|
|
152
|
+
const bad = (message) => {
|
|
153
|
+
c.error("invalid-provider", path, message);
|
|
154
|
+
return false;
|
|
155
|
+
};
|
|
156
|
+
if (!isObject(p))
|
|
157
|
+
return bad("must be a holiday provider object");
|
|
158
|
+
if (typeof p["id"] !== "string" || p["id"] === "")
|
|
159
|
+
return bad('"id" must be a non-empty string');
|
|
160
|
+
if (typeof p["timeZone"] !== "string")
|
|
161
|
+
return bad('"timeZone" must be a string');
|
|
162
|
+
try {
|
|
163
|
+
validateTimeZone(p["timeZone"]);
|
|
164
|
+
}
|
|
165
|
+
catch {
|
|
166
|
+
return bad(`"timeZone" is not a valid IANA time zone: ${JSON.stringify(p["timeZone"])}`);
|
|
167
|
+
}
|
|
168
|
+
const years = p["supportedYears"];
|
|
169
|
+
if (!isObject(years) ||
|
|
170
|
+
!Number.isInteger(years["from"]) ||
|
|
171
|
+
!Number.isInteger(years["to"]) ||
|
|
172
|
+
years["from"] < 1000 ||
|
|
173
|
+
years["to"] > 9999 ||
|
|
174
|
+
years["from"] > years["to"]) {
|
|
175
|
+
return bad('"supportedYears" must be { from, to } with integer years in 1000-9999 and from <= to');
|
|
176
|
+
}
|
|
177
|
+
if (typeof p["dataVersion"] !== "string")
|
|
178
|
+
return bad('"dataVersion" must be a string');
|
|
179
|
+
if (p["confirmedThrough"] !== undefined && !Number.isInteger(p["confirmedThrough"])) {
|
|
180
|
+
return bad('"confirmedThrough" must be an integer year');
|
|
181
|
+
}
|
|
182
|
+
if (typeof p["holidaysInYear"] !== "function")
|
|
183
|
+
return bad('"holidaysInYear" must be a function');
|
|
184
|
+
return true;
|
|
185
|
+
}
|
|
186
|
+
/** @internal 祝日の提供元とタイムゾーンの設定を検証する */
|
|
187
|
+
export function compileHolidaySource(c, options) {
|
|
188
|
+
const raw = options["holidays"];
|
|
189
|
+
const list = raw === undefined ? [] : Array.isArray(raw) ? raw : [raw];
|
|
190
|
+
const providers = [];
|
|
191
|
+
list.forEach((p, i) => {
|
|
192
|
+
const path = Array.isArray(raw) ? `holidays[${i}]` : "holidays";
|
|
193
|
+
if (validateProvider(c, path, p))
|
|
194
|
+
providers.push(p);
|
|
195
|
+
});
|
|
196
|
+
let timeZone = "UTC";
|
|
197
|
+
if (options["timeZone"] !== undefined) {
|
|
198
|
+
try {
|
|
199
|
+
timeZone = validateTimeZone(options["timeZone"]);
|
|
200
|
+
}
|
|
201
|
+
catch {
|
|
202
|
+
c.error("invalid-time-zone", "timeZone", `Invalid time zone: ${JSON.stringify(options["timeZone"])}`);
|
|
203
|
+
}
|
|
67
204
|
}
|
|
68
205
|
else {
|
|
69
206
|
const zones = [...new Set(providers.map((p) => validateTimeZone(p.timeZone)))];
|
|
70
207
|
if (zones.length > 1) {
|
|
71
|
-
|
|
208
|
+
c.error("time-zone-conflict", "timeZone", `Holiday providers use different time zones (${zones.join(", ")}); specify timeZone`);
|
|
72
209
|
}
|
|
73
210
|
timeZone = zones[0] ?? "UTC";
|
|
74
211
|
}
|
|
75
|
-
const outOfRange = options
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
const { from, to } = provider.supportedYears;
|
|
79
|
-
if (year < from || year > to) {
|
|
80
|
-
if (outOfRange === "ignore")
|
|
81
|
-
return [];
|
|
82
|
-
throw new RangeError(`Holidays for ${provider.id} are supported for ${from}-${to}; requested ${year}`);
|
|
83
|
-
}
|
|
84
|
-
return provider.holidaysInYear(year);
|
|
212
|
+
const outOfRange = options["outOfRange"] ?? "throw";
|
|
213
|
+
if (outOfRange !== "throw" && outOfRange !== "ignore") {
|
|
214
|
+
c.error("invalid-option", "outOfRange", 'must be "throw" or "ignore"');
|
|
85
215
|
}
|
|
216
|
+
return {
|
|
217
|
+
providers: Object.freeze(providers),
|
|
218
|
+
timeZone,
|
|
219
|
+
outOfRange: outOfRange === "ignore" ? "ignore" : "throw",
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
// ------------------------------------------------------------
|
|
223
|
+
// 祝日を調べる機能
|
|
224
|
+
// ------------------------------------------------------------
|
|
225
|
+
function providerDataError(provider, year, message) {
|
|
226
|
+
return rangeError("invalid-provider-data", `Holiday provider ${provider.id} returned invalid data for ${year}: ${message}`);
|
|
227
|
+
}
|
|
228
|
+
/** 提供元が返したデータを検証し、契約の項目だけを持つ変更できないオブジェクトにする */
|
|
229
|
+
function loadProviderYear(provider, year) {
|
|
230
|
+
const list = provider.holidaysInYear(year);
|
|
231
|
+
if (!Array.isArray(list))
|
|
232
|
+
throw providerDataError(provider, year, "holidaysInYear must return an array");
|
|
233
|
+
const seen = new Set();
|
|
234
|
+
return list.map((h, i) => {
|
|
235
|
+
if (!isObject(h))
|
|
236
|
+
throw providerDataError(provider, year, `item ${i} is not an object`);
|
|
237
|
+
const { date, name, nameEn, kind, confirmed } = h;
|
|
238
|
+
if (!isValidDate(date) || parseDate(date).year !== year) {
|
|
239
|
+
throw providerDataError(provider, year, `item ${i} has an invalid date ${JSON.stringify(date)}`);
|
|
240
|
+
}
|
|
241
|
+
if (seen.has(date))
|
|
242
|
+
throw providerDataError(provider, year, `duplicate date ${date}`);
|
|
243
|
+
seen.add(date);
|
|
244
|
+
if (typeof name !== "string" || typeof nameEn !== "string" || typeof kind !== "string") {
|
|
245
|
+
throw providerDataError(provider, year, `item ${i} (${date}) must have string name, nameEn and kind`);
|
|
246
|
+
}
|
|
247
|
+
if (typeof confirmed !== "boolean") {
|
|
248
|
+
throw providerDataError(provider, year, `item ${i} (${date}) must have boolean confirmed`);
|
|
249
|
+
}
|
|
250
|
+
return Object.freeze({ date, name, nameEn, kind: kind, confirmed });
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
/** @internal 祝日を調べる機能を作る(設定は検証済みであること) */
|
|
254
|
+
export function createHolidayLookup(source) {
|
|
255
|
+
const { providers, timeZone, outOfRange } = source;
|
|
256
|
+
const yearCache = new Map();
|
|
86
257
|
function holidayMap(year) {
|
|
87
258
|
let map = yearCache.get(year);
|
|
88
259
|
if (!map) {
|
|
89
260
|
map = new Map();
|
|
90
261
|
for (const provider of providers) {
|
|
91
|
-
|
|
262
|
+
const { from, to } = provider.supportedYears;
|
|
263
|
+
if (year < from || year > to) {
|
|
264
|
+
if (outOfRange === "ignore")
|
|
265
|
+
continue;
|
|
266
|
+
throw rangeError("out-of-range", `Holidays for ${provider.id} are supported for ${from}-${to}; requested ${year}`);
|
|
267
|
+
}
|
|
268
|
+
for (const h of loadProviderYear(provider, year)) {
|
|
92
269
|
// 同じ日に複数の提供元の祝日がある場合は、先に渡した提供元を優先する
|
|
93
270
|
if (!map.has(h.date))
|
|
94
271
|
map.set(h.date, h);
|
|
@@ -106,7 +283,7 @@ export function createHolidayLookup(options) {
|
|
|
106
283
|
const dateOf = (input) => toCivil(input, timeZone).date;
|
|
107
284
|
function holidaysInYear(year) {
|
|
108
285
|
if (!Number.isInteger(year))
|
|
109
|
-
throw
|
|
286
|
+
throw rangeError("invalid-argument", `Invalid year: ${year}`);
|
|
110
287
|
return Object.freeze([...holidayMap(year).values()].sort((a, b) => a.date < b.date ? -1 : a.date > b.date ? 1 : 0));
|
|
111
288
|
}
|
|
112
289
|
return {
|
|
@@ -123,7 +300,7 @@ export function createHolidayLookup(options) {
|
|
|
123
300
|
const a = dateOf(from);
|
|
124
301
|
const b = dateOf(to);
|
|
125
302
|
if (a > b)
|
|
126
|
-
throw
|
|
303
|
+
throw rangeError("invalid-range", `"from" must not be after "to" (${a} > ${b})`);
|
|
127
304
|
const result = [];
|
|
128
305
|
for (let y = parseDate(a).year; y <= parseDate(b).year; y++) {
|
|
129
306
|
for (const h of holidaysInYear(y))
|
|
@@ -134,28 +311,61 @@ export function createHolidayLookup(options) {
|
|
|
134
311
|
},
|
|
135
312
|
};
|
|
136
313
|
}
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
314
|
+
const SCHEDULE_OPTION_KEYS = [
|
|
315
|
+
"holidays",
|
|
316
|
+
"timeZone",
|
|
317
|
+
"outOfRange",
|
|
318
|
+
"weekly",
|
|
319
|
+
"holiday",
|
|
320
|
+
"annual",
|
|
321
|
+
"dates",
|
|
322
|
+
];
|
|
323
|
+
/** @internal スケジュールの設定を検証して取り込む(誤りは c に集める) */
|
|
324
|
+
export function compileScheduleOptions(c, options) {
|
|
325
|
+
if (!isObject(options)) {
|
|
326
|
+
c.error("invalid-option", "", "options must be an object");
|
|
327
|
+
return undefined;
|
|
328
|
+
}
|
|
329
|
+
checkUnknownKeys(c, "", options, SCHEDULE_OPTION_KEYS);
|
|
330
|
+
const source = compileHolidaySource(c, options);
|
|
331
|
+
const weeklyRaw = options["weekly"];
|
|
332
|
+
let weekly = [];
|
|
333
|
+
if (!Array.isArray(weeklyRaw) || weeklyRaw.length !== 7) {
|
|
334
|
+
c.error("invalid-option", "weekly", "must have 7 values ([Sunday, Monday, ..., Saturday])");
|
|
335
|
+
}
|
|
336
|
+
else {
|
|
337
|
+
weekly = Object.freeze([...weeklyRaw]);
|
|
338
|
+
}
|
|
339
|
+
let holiday = "weekday";
|
|
340
|
+
const holidayRaw = options["holiday"];
|
|
341
|
+
if (holidayRaw === undefined || holidayRaw === "weekday") {
|
|
342
|
+
holiday = "weekday";
|
|
343
|
+
}
|
|
344
|
+
else if (isObject(holidayRaw) && "sameAs" in holidayRaw) {
|
|
345
|
+
if (isWeekday(holidayRaw["sameAs"]))
|
|
346
|
+
holiday = Object.freeze({ sameAs: holidayRaw["sameAs"] });
|
|
347
|
+
else
|
|
348
|
+
c.error("invalid-weekday", "holiday.sameAs", "must be an integer from 0 (Sunday) to 6 (Saturday)");
|
|
349
|
+
}
|
|
350
|
+
else if (isObject(holidayRaw) && "value" in holidayRaw) {
|
|
351
|
+
holiday = Object.freeze({ value: holidayRaw["value"] });
|
|
352
|
+
}
|
|
353
|
+
else {
|
|
354
|
+
c.error("invalid-option", "holiday", 'must be "weekday", { sameAs: weekday } or { value }');
|
|
355
|
+
}
|
|
356
|
+
const annual = compileAnnualRules(c, "annual", options["annual"], requiredValue(c), false);
|
|
357
|
+
const dates = compileDateRules(c, "dates", options["dates"], requiredValue(c), false);
|
|
358
|
+
return { source, weekly, holiday, annual, dates };
|
|
359
|
+
}
|
|
360
|
+
function matchAnnual(rule, monthDay) {
|
|
361
|
+
return rule.from <= rule.to
|
|
362
|
+
? monthDay >= rule.from && monthDay <= rule.to
|
|
363
|
+
: monthDay >= rule.from || monthDay <= rule.to;
|
|
364
|
+
}
|
|
365
|
+
/** @internal 検証済みの規則からスケジュールを作る */
|
|
366
|
+
export function buildSchedule(compiled) {
|
|
367
|
+
const { weekly, holiday: holidayRule, annual, dates } = compiled;
|
|
368
|
+
const lookup = createHolidayLookup(compiled.source);
|
|
159
369
|
function info(date) {
|
|
160
370
|
const dayNumber = toDayNumber(date);
|
|
161
371
|
const weekday = weekdayOf(date);
|
|
@@ -201,3 +411,16 @@ export function createSchedule(options) {
|
|
|
201
411
|
};
|
|
202
412
|
return Object.freeze(schedule);
|
|
203
413
|
}
|
|
414
|
+
/**
|
|
415
|
+
* 曜日・祝日・特定日の規則から、日ごとに値を返すスケジュールを作る。
|
|
416
|
+
*
|
|
417
|
+
* 設定は作る時点で複製されるので、あとから元の配列やオブジェクトを書き換えても結果は変わらない
|
|
418
|
+
* (値 T そのものは複製しない)。設定の誤りは code 付きの RangeError になる。
|
|
419
|
+
* @public
|
|
420
|
+
*/
|
|
421
|
+
export function createSchedule(options) {
|
|
422
|
+
const c = new IssueCollector();
|
|
423
|
+
const compiled = compileScheduleOptions(c, options);
|
|
424
|
+
c.throwFirstError();
|
|
425
|
+
return buildSchedule(compiled);
|
|
426
|
+
}
|
package/dist/core/types.d.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import type { DateString } from "./date.ts";
|
|
2
|
-
/**
|
|
2
|
+
/**
|
|
3
|
+
* 祝日・休日 1 日分。
|
|
4
|
+
* @public
|
|
5
|
+
*/
|
|
3
6
|
export interface Holiday<Kind extends string = string> {
|
|
4
7
|
/** 暦日 "YYYY-MM-DD" */
|
|
5
8
|
readonly date: DateString;
|
|
@@ -18,7 +21,12 @@ export interface Holiday<Kind extends string = string> {
|
|
|
18
21
|
/**
|
|
19
22
|
* 祝日の提供元(国・地域ごとのルール)。
|
|
20
23
|
*
|
|
21
|
-
* 他の国・地域の祝日はこのインターフェースを実装して createCalendar に渡す。
|
|
24
|
+
* 他の国・地域の祝日はこのインターフェースを実装して createCalendar・createSchedule に渡す。
|
|
25
|
+
* holidaysInYear が返すデータは、年ごとに初めて使うときに検証され、誤りがあれば
|
|
26
|
+
* code: "invalid-provider-data" の例外になる。
|
|
27
|
+
*
|
|
28
|
+
* 互換性の約束: このインターフェースに項目を足すときは、メジャー版以外では省略可能な項目に限る。
|
|
29
|
+
* @public
|
|
22
30
|
*/
|
|
23
31
|
export interface HolidayProvider<Kind extends string = string> {
|
|
24
32
|
/** ISO 3166-1 alpha-2(国)または ISO 3166-2(地域)のコード。例: "JP", "US-CA" */
|
|
@@ -34,6 +42,6 @@ export interface HolidayProvider<Kind extends string = string> {
|
|
|
34
42
|
readonly dataVersion: string;
|
|
35
43
|
/** この年までは公式に確定している(それより後は confirmed: false を含みうる) */
|
|
36
44
|
readonly confirmedThrough?: number;
|
|
37
|
-
/**
|
|
45
|
+
/** その年の祝日・休日(同じ日付は 1 件まで。順序は問わない) */
|
|
38
46
|
holidaysInYear(year: number): readonly Holiday<Kind>[];
|
|
39
47
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -3,9 +3,13 @@
|
|
|
3
3
|
* 曜日・祝日・特定日で変わる値(営業時間など)のスケジュール(createSchedule)。
|
|
4
4
|
*
|
|
5
5
|
* 国ごとの祝日はサブパスから読み込む(例: "bizdays-toolkit/jp")。
|
|
6
|
+
*
|
|
7
|
+
* 公開する API には TSDoc の @public(安定)または @beta(マイナー版で変わりうる)を付ける。
|
|
8
|
+
* 公開する名前と型は etc/api.md に記録し、pnpm check で意図しない変化を検出する。
|
|
6
9
|
*/
|
|
7
|
-
export { createCalendar, type AnnualDate, type AnnualRange, type BusinessDayOptions, type Calendar, type CalendarOptions, type DateRange, type DayInfo, type DayReason, type HolidayPolicy, type SpecificDate, } from "./core/calendar.ts";
|
|
8
|
-
export { createSchedule, type AnnualRangeValue, type AnnualValue, type CivilDateTimeParts, type DateRangeValue, type DateValue, type HolidayLookup, type HolidayRule, type HolidaySourceOptions, type Schedule, type ScheduleDay, type ScheduleOptions, type ScheduleReason, } from "./core/schedule.ts";
|
|
9
|
-
export { addDays, diffDays, isValidDate,
|
|
10
|
+
export { createCalendar, type AnnualDate, type AnnualRange, type BusinessDayOptions, type Calendar, type CalendarOptions, type DateRange, type DayInfo, type DayReason, type HolidayPolicy, type KnownDayReason, type SpecificDate, } from "./core/calendar.ts";
|
|
11
|
+
export { createSchedule, type AnnualRangeValue, type AnnualValue, type CivilDateTimeParts, type DateRangeValue, type DateValue, type HolidayLookup, type HolidayRule, type HolidaySourceOptions, type KnownScheduleReason, type Schedule, type ScheduleDay, type ScheduleOptions, type ScheduleReason, } from "./core/schedule.ts";
|
|
12
|
+
export { addDays, diffDays, isValidDate, weekdayOf, type DateString, type Weekday, } from "./core/date.ts";
|
|
13
|
+
export { isBizdaysError, type ErrorCode, type KnownErrorCode } from "./core/errors.ts";
|
|
10
14
|
export { toDateString, toDateTimeParts, type DateInput, type TemporalDateLike, type TemporalInstantLike, } from "./core/input.ts";
|
|
11
15
|
export type { Holiday, HolidayProvider } from "./core/types.ts";
|
package/dist/index.js
CHANGED
|
@@ -3,8 +3,12 @@
|
|
|
3
3
|
* 曜日・祝日・特定日で変わる値(営業時間など)のスケジュール(createSchedule)。
|
|
4
4
|
*
|
|
5
5
|
* 国ごとの祝日はサブパスから読み込む(例: "bizdays-toolkit/jp")。
|
|
6
|
+
*
|
|
7
|
+
* 公開する API には TSDoc の @public(安定)または @beta(マイナー版で変わりうる)を付ける。
|
|
8
|
+
* 公開する名前と型は etc/api.md に記録し、pnpm check で意図しない変化を検出する。
|
|
6
9
|
*/
|
|
7
10
|
export { createCalendar, } from "./core/calendar.js";
|
|
8
11
|
export { createSchedule, } from "./core/schedule.js";
|
|
9
|
-
export { addDays, diffDays, isValidDate,
|
|
12
|
+
export { addDays, diffDays, isValidDate, weekdayOf, } from "./core/date.js";
|
|
13
|
+
export { isBizdaysError } from "./core/errors.js";
|
|
10
14
|
export { toDateString, toDateTimeParts, } from "./core/input.js";
|
package/dist/jp/index.d.ts
CHANGED
|
@@ -14,28 +14,57 @@ import type { DateString } from "../core/date.ts";
|
|
|
14
14
|
import type { DateInput } from "../core/input.ts";
|
|
15
15
|
import type { Holiday, HolidayProvider } from "../core/types.ts";
|
|
16
16
|
import { type JapaneseHolidayKind } from "./rules.ts";
|
|
17
|
-
export type { JapaneseHolidayKind } from "./rules.ts";
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
export type { JapaneseHolidayKind, KnownJapaneseHolidayKind } from "./rules.ts";
|
|
18
|
+
/**
|
|
19
|
+
* 日本の祝日。
|
|
20
|
+
* @public
|
|
21
|
+
*/
|
|
20
22
|
export type JapaneseHoliday = Holiday<JapaneseHolidayKind>;
|
|
21
|
-
/**
|
|
23
|
+
/**
|
|
24
|
+
* 日本の祝日の提供元(createCalendar・createSchedule の holidays に渡す)。
|
|
25
|
+
* @public
|
|
26
|
+
*/
|
|
22
27
|
export declare const jp: HolidayProvider<JapaneseHolidayKind>;
|
|
23
|
-
/**
|
|
28
|
+
/**
|
|
29
|
+
* 祝日か(Date などの瞬間は日本時間で判定する)。
|
|
30
|
+
* @public
|
|
31
|
+
*/
|
|
24
32
|
export declare function isHoliday(input: DateInput): boolean;
|
|
25
|
-
/**
|
|
33
|
+
/**
|
|
34
|
+
* 祝日なら祝日を返す。
|
|
35
|
+
* @public
|
|
36
|
+
*/
|
|
26
37
|
export declare function getHoliday(input: DateInput): JapaneseHoliday | undefined;
|
|
27
|
-
/**
|
|
38
|
+
/**
|
|
39
|
+
* その年の祝日。
|
|
40
|
+
* @public
|
|
41
|
+
*/
|
|
28
42
|
export declare function holidaysInYear(year: number): readonly JapaneseHoliday[];
|
|
29
|
-
/**
|
|
43
|
+
/**
|
|
44
|
+
* 期間内の祝日(両端を含む)。
|
|
45
|
+
* @public
|
|
46
|
+
*/
|
|
30
47
|
export declare function holidaysBetween(from: DateInput, to: DateInput): readonly JapaneseHoliday[];
|
|
31
|
-
/**
|
|
48
|
+
/**
|
|
49
|
+
* 日本時間での暦日。
|
|
50
|
+
* @public
|
|
51
|
+
*/
|
|
32
52
|
export declare function toJapanDate(input: DateInput): DateString;
|
|
33
|
-
/**
|
|
53
|
+
/**
|
|
54
|
+
* 日本時間での暦日と時刻("HH:mm"。暦日だけの入力では null)。
|
|
55
|
+
* @public
|
|
56
|
+
*/
|
|
34
57
|
export declare function toJapanDateTime(input: DateInput): {
|
|
35
58
|
readonly date: DateString;
|
|
36
59
|
readonly time: string | null;
|
|
37
60
|
};
|
|
38
|
-
/**
|
|
61
|
+
/**
|
|
62
|
+
* 反映済みの官報の年月。
|
|
63
|
+
* @public
|
|
64
|
+
*/
|
|
39
65
|
export declare const dataVersion: string;
|
|
40
|
-
/**
|
|
66
|
+
/**
|
|
67
|
+
* 春分・秋分の日が官報で確定している最後の年。
|
|
68
|
+
* @public
|
|
69
|
+
*/
|
|
41
70
|
export declare const confirmedThrough: number;
|
package/dist/jp/index.js
CHANGED
|
@@ -12,9 +12,11 @@
|
|
|
12
12
|
*/
|
|
13
13
|
import { createCalendar } from "../core/calendar.js";
|
|
14
14
|
import { CONFIRMED_THROUGH, DATA_VERSION, SUPPORTED_YEARS, computeJapaneseHolidays, } from "./rules.js";
|
|
15
|
-
export { autumnalEquinoxDay, vernalEquinoxDay } from "./rules.js";
|
|
16
15
|
const cache = new Map();
|
|
17
|
-
/**
|
|
16
|
+
/**
|
|
17
|
+
* 日本の祝日の提供元(createCalendar・createSchedule の holidays に渡す)。
|
|
18
|
+
* @public
|
|
19
|
+
*/
|
|
18
20
|
export const jp = Object.freeze({
|
|
19
21
|
id: "JP",
|
|
20
22
|
timeZone: "Asia/Tokyo",
|
|
@@ -32,31 +34,55 @@ export const jp = Object.freeze({
|
|
|
32
34
|
});
|
|
33
35
|
let defaultCalendar;
|
|
34
36
|
const calendar = () => (defaultCalendar ??= createCalendar({ holidays: jp }));
|
|
35
|
-
/**
|
|
37
|
+
/**
|
|
38
|
+
* 祝日か(Date などの瞬間は日本時間で判定する)。
|
|
39
|
+
* @public
|
|
40
|
+
*/
|
|
36
41
|
export function isHoliday(input) {
|
|
37
42
|
return calendar().isHoliday(input);
|
|
38
43
|
}
|
|
39
|
-
/**
|
|
44
|
+
/**
|
|
45
|
+
* 祝日なら祝日を返す。
|
|
46
|
+
* @public
|
|
47
|
+
*/
|
|
40
48
|
export function getHoliday(input) {
|
|
41
49
|
return calendar().getHoliday(input);
|
|
42
50
|
}
|
|
43
|
-
/**
|
|
51
|
+
/**
|
|
52
|
+
* その年の祝日。
|
|
53
|
+
* @public
|
|
54
|
+
*/
|
|
44
55
|
export function holidaysInYear(year) {
|
|
45
56
|
return calendar().holidaysInYear(year);
|
|
46
57
|
}
|
|
47
|
-
/**
|
|
58
|
+
/**
|
|
59
|
+
* 期間内の祝日(両端を含む)。
|
|
60
|
+
* @public
|
|
61
|
+
*/
|
|
48
62
|
export function holidaysBetween(from, to) {
|
|
49
63
|
return calendar().holidaysBetween(from, to);
|
|
50
64
|
}
|
|
51
|
-
/**
|
|
65
|
+
/**
|
|
66
|
+
* 日本時間での暦日。
|
|
67
|
+
* @public
|
|
68
|
+
*/
|
|
52
69
|
export function toJapanDate(input) {
|
|
53
70
|
return calendar().dateOf(input);
|
|
54
71
|
}
|
|
55
|
-
/**
|
|
72
|
+
/**
|
|
73
|
+
* 日本時間での暦日と時刻("HH:mm"。暦日だけの入力では null)。
|
|
74
|
+
* @public
|
|
75
|
+
*/
|
|
56
76
|
export function toJapanDateTime(input) {
|
|
57
77
|
return calendar().civilOf(input);
|
|
58
78
|
}
|
|
59
|
-
/**
|
|
79
|
+
/**
|
|
80
|
+
* 反映済みの官報の年月。
|
|
81
|
+
* @public
|
|
82
|
+
*/
|
|
60
83
|
export const dataVersion = DATA_VERSION;
|
|
61
|
-
/**
|
|
84
|
+
/**
|
|
85
|
+
* 春分・秋分の日が官報で確定している最後の年。
|
|
86
|
+
* @public
|
|
87
|
+
*/
|
|
62
88
|
export const confirmedThrough = CONFIRMED_THROUGH;
|