bizdays-toolkit 0.9.1 → 0.11.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 +56 -1
- package/README.md +95 -24
- package/dist/core/calendar.d.ts +42 -8
- package/dist/core/calendar.js +99 -12
- package/dist/core/diagnose.d.ts +4 -2
- package/dist/core/diagnose.js +183 -49
- package/dist/core/input.d.ts +1 -1
- package/dist/core/schedule.d.ts +78 -12
- package/dist/core/schedule.js +138 -29
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/jp/index.d.ts +8 -1
- package/dist/jp/index.js +2 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,59 @@
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [0.11.0] - 2026-10-19
|
|
8
|
+
|
|
9
|
+
### 移行
|
|
10
|
+
|
|
11
|
+
作業は要りません(繰り返しの規則の新しい形と、カレンダーを合成する関数の追加)。
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- 繰り返しの規則(`closedRecurring`・`openRecurring`・スケジュールの `recurring`)に、祝日の翌日の形 `{ dayAfterHoliday: true }` を追加した。前日が祝日でその日自身は祝日でない日(連休なら連休が明けた日)に当てはまる。「祝日」は提供元の祝日だけで、休業日の設定は含めない。当てはまる日は祝日でないので `onHolidayRule` は指定できない(`invalid-option`)。`months`・`name`・有効期間と組み合わせられる。型 `DayAfterHolidayPattern` を追加した
|
|
16
|
+
- 作成済みの営業日カレンダーを合成する `combineCalendars(calendars, { businessDay: "all" | "any", maxSearchDays? })` を追加した。すべてが営業している日(`"all"`)、またはいずれかが営業している日(`"any"`)を営業日とする通常のカレンダーを返し、営業日の加減算・調整・期日などが合成結果で動く。`dayInfo` の `reason`・`name`・`shiftedFrom` は合成結果と同じ判定をした最初のカレンダーの結果で、その番号を新しい項目 `decidedBy` で返す。祝日は各カレンダーの提供元の和集合で判定する。タイムゾーンが違えば `time-zone-conflict`、2 つ未満・同じカレンダーの重複は `invalid-argument`。型 `CombineOptions` を追加した
|
|
17
|
+
- `DayInfo` に省略可能な `decidedBy`(合成したカレンダーで判定を決めたカレンダーの番号)を追加した。合成していないカレンダーでは付かない
|
|
18
|
+
- `HolidayLookup`(営業日カレンダー・スケジュール)に `outOfRange`(設定の値)を追加した。合成するときに引き継ぐため
|
|
19
|
+
|
|
20
|
+
### Changed
|
|
21
|
+
|
|
22
|
+
- 規則の診断で、祝日の翌日の規則を扱えるようにした。提供元のない設定では `never-matches`。`shadowed` は祝日の翌日の規則どうし(`months` と有効期間)でだけ調べ、曜日の規則との重なりは `redundant` で見つける
|
|
23
|
+
- 繰り返しの規則の形の誤りのメッセージを、`"weekday"`・`"day"`・`"dayAfterHoliday"` のうち 1 つだけを持つ、に改めた(code は `invalid-option` のまま)
|
|
24
|
+
- README: 繰り返しの規則の節に祝日の翌日を、新しい節「カレンダーの合成」を足した。サイズの値を測り直した(ルート約 8.6KB・日本の祝日込み約 10.3KB・英国の祝日込み約 10.2KB。0.11.0 で測定)
|
|
25
|
+
|
|
26
|
+
### 開発
|
|
27
|
+
|
|
28
|
+
- サイズの上限(`size-limits.json`)を上げた。ルート 17,500 → 19,500、`./jp` 20,500 → 22,500、`./diagnose` 24,000 → 26,000(gzip)。祝日の翌日の規則と `combineCalendars`(ルートで約 1.7KB)と、診断の祝日の翌日への対応(約 1.9KB)のため
|
|
29
|
+
- `createCalendar` の営業日の探索・調整・月の営業日の実装を、祝日を調べる機能と 1 日分の判定を引数に取る内部関数(`buildCalendar`)に切り出し、`combineCalendars` と共用した。公開 API と結果は変わらない
|
|
30
|
+
- 性質のテストに祝日の翌日の規則を加えた
|
|
31
|
+
|
|
32
|
+
## [0.10.0] - 2026-10-19
|
|
33
|
+
|
|
34
|
+
### 移行
|
|
35
|
+
|
|
36
|
+
動作は変わりません(繰り返しの規則・毎年の月日の規則への省略可能な項目の追加と、診断の改善)。`bizdays-toolkit/jp` の `toJapanDate`・`toJapanDateTime` が非推奨になったので、使っていれば `toDateString(input, "Asia/Tokyo")`・`toDateTimeParts(input, "Asia/Tokyo")`(または営業日カレンダー・スケジュールの `dateOf`・`civilOf`)に書き換える。書き換え方は [docs/migration.md](./docs/migration.md)。
|
|
37
|
+
|
|
38
|
+
### Added
|
|
39
|
+
|
|
40
|
+
- 繰り返しの規則(`closedRecurring`・`openRecurring`・スケジュールの `recurring`)と毎年の月日の規則(`closedAnnual` のオブジェクトの形・スケジュールの `annual`)に、有効期間 `effectiveFrom`・`effectiveTo`(両端を含む `"YYYY-MM-DD"`。一方だけなら、もう一方は期限なし)を付けられるようにした。期間の外では、その規則は書かれていないのと同じに扱う(判定の順番は変わらない)。「2027 年 4 月から定休日を月曜から火曜に変える」「夏季休業を今年限りでやめる」を 1 つのカレンダーで表せ、切り替え日をまたぐ営業日の計算(`addBusinessDays` など)も正しくなる。`"after-holiday"` の振替は振替元の日で期間を判定する。型 `EffectivePeriod` を追加した。`effectiveFrom` が `effectiveTo` より後なら `invalid-range`
|
|
41
|
+
- 曜日の規則に周期 `interval`(週数。2 以上の整数)と起点 `anchor`(`weekday` と同じ曜日の日付)を付けて、「隔週水曜定休」のような規則を書けるようにした。`anchor` と同じ曜日で、`anchor` との日数の差が 7 × `interval` の倍数の日(起点より前の日も)に当てはまる。`nth` とは併用できず、`months`・`onHolidayRule`・有効期間とは組み合わせられる。「第 1・第 3 水曜」(`nth: [1, 3]`)は水曜が 5 回ある月で隔週からずれるため、別の書き方にした。`interval` と `anchor` の片方だけ、`anchor` の曜日違い、`day` の規則への指定は `invalid-option`
|
|
42
|
+
|
|
43
|
+
### Deprecated
|
|
44
|
+
|
|
45
|
+
- `bizdays-toolkit/jp` の `toJapanDate`・`toJapanDateTime` を非推奨にした(`@deprecated`)。同じ変換がルートの `toDateString(input, "Asia/Tokyo")`・`toDateTimeParts(input, "Asia/Tokyo")` と、営業日カレンダー・スケジュールの `dateOf`・`civilOf` にあり、国ごとの関数は日本だけの特別扱い(英国には対応物がない)だったため。少なくとも 1 つのマイナー版は残し、1.0.0 で削除する。`etc/api.md` に `@deprecated` を記録するようにした
|
|
46
|
+
|
|
47
|
+
### Changed
|
|
48
|
+
|
|
49
|
+
- 規則の診断(`bizdays-toolkit/diagnose`)を、有効期間と周期に対応させた。`shadowed` は先の規則の期間が後の規則の期間を含み、周期も含む(毎週は隔週を覆う。位相の違う隔週どうしは覆わない)ときだけ出す。有効期間の中に当てはまる日がない規則は `never-matches`。規則の有効期間が祝日の提供元の対応年の外にあれば `out-of-range`(新しい code は足していない)
|
|
50
|
+
- `redundant`・`no-business-day` の走査の範囲を見直した。周期のある規則があれば、提供元がない設定の 2001〜2028 年を周期がそろう長さ(隔週なら 56 年。上限 200 年)に広げ、規則の有効期間が範囲の外にあれば、その期間も調べる(`outOfRange: "throw"` では対応年の中だけ)。これまでは固定の範囲の外にある期間の規則を「使われない」と誤診した。調べた年は `message` に書く
|
|
51
|
+
- README: 繰り返しの規則の節に、有効期間と隔週の書き方・例を足した。`businessDaysBetween` だけが `to` を含まない理由(区間を足し合わせられ、`addBusinessDays` の逆算になる)を書いた
|
|
52
|
+
- README のサイズの値を測り直した(ルート約 7.9KB・日本の祝日込み約 9.8KB・英国の祝日込み約 9.6KB。0.10.0 で測定)
|
|
53
|
+
- `docs/decisions.md`: 有効期間と周期の設計(30 番)と `businessDaysBetween` の範囲(31 番)を足し、6 番(契約の項目に `names` を追記)・13 番と 16 番(その月にない日の扱いが `dueDate`・`closingMonth` と繰り返しの規則で違うことの相互参照)・14 番(土曜日を避ける方法は個別の日付での上書き、振替と有効期間)・15 番(走査の範囲)の記述を直した
|
|
54
|
+
|
|
55
|
+
### 開発
|
|
56
|
+
|
|
57
|
+
- サイズの上限(`size-limits.json`)を上げた。ルート 16,500 → 17,500、`./jp` 19,500 → 20,500、`./diagnose` 22,000 → 24,000(gzip)。有効期間と周期の検証・判定(ルートで約 0.8KB)と、診断の走査範囲・包含判定の見直し(約 2.3KB)のため
|
|
58
|
+
- 性質のテスト(`test/diagnose-properties.test.ts`)で、有効期間と周期のある規則も作るようにした。固定の走査範囲(2001〜2028 年)の外や、28 年の周期をまたぐ年の日付も比べる
|
|
59
|
+
|
|
7
60
|
## [0.9.1] - 2026-10-18
|
|
8
61
|
|
|
9
62
|
ライブラリのコードは 0.9.0 と同じ。
|
|
@@ -238,7 +291,9 @@
|
|
|
238
291
|
|
|
239
292
|
- 日本: 2026 年 2 月の官報(2027 年の春分・秋分の日)まで反映(`dataVersion: "2026-02"`、`confirmedThrough: 2027`)
|
|
240
293
|
|
|
241
|
-
[Unreleased]: https://github.com/inohan77/bizdays-toolkit/compare/v0.
|
|
294
|
+
[Unreleased]: https://github.com/inohan77/bizdays-toolkit/compare/v0.11.0...HEAD
|
|
295
|
+
[0.11.0]: https://github.com/inohan77/bizdays-toolkit/compare/v0.10.0...v0.11.0
|
|
296
|
+
[0.10.0]: https://github.com/inohan77/bizdays-toolkit/compare/v0.9.1...v0.10.0
|
|
242
297
|
[0.9.1]: https://github.com/inohan77/bizdays-toolkit/compare/v0.9.0...v0.9.1
|
|
243
298
|
[0.9.0]: https://github.com/inohan77/bizdays-toolkit/compare/v0.8.1...v0.9.0
|
|
244
299
|
[0.8.1]: https://github.com/inohan77/bizdays-toolkit/compare/v0.8.0...v0.8.1
|
package/README.md
CHANGED
|
@@ -20,13 +20,14 @@ A business-day calendar that combines public holidays with company or store clos
|
|
|
20
20
|
|
|
21
21
|
- **Japanese holidays verified day by day against the Cabinet Office data**: from 1955 through the latest year announced in the Official Gazette, every date in the Cabinet Office "National Holidays" CSV is checked in the tests (substitute holidays, citizens' holidays, holidays for imperial ceremonies and the Olympic moves included)
|
|
22
22
|
- **UK bank holidays verified day by day against GOV.UK**: choose England, Wales, Scotland or Northern Ireland. From 2012 through the last year GOV.UK has published, every date and name is checked against the GOV.UK data in the tests, and differences are watched daily
|
|
23
|
-
- **Recurring rules**: rules such as "closed on the 3rd Wednesday", "stocktaking on the last day of the month"
|
|
23
|
+
- **Recurring rules**: rules such as "closed on the 3rd Wednesday", "closed every other Wednesday", "stocktaking on the last day of the month", "closed on Mondays (on the next weekday if Monday is a holiday)" or "closed on the day after a holiday", together with what happens when they fall on a holiday (follow the holiday, apply anyway, or move to the next day). Rules can have an effective period, so "closed on Tuesdays instead of Mondays from April 2027" is one calendar
|
|
24
24
|
- **Day-by-day values (e.g. store hours)**: values that change by weekday, holiday and specific date, such as "closed on Tuesdays, Sunday hours on holidays, closed on New Year's Day, short hours on New Year's Eve", are looked up with the same rules
|
|
25
25
|
- **Option diagnostics**: `diagnoseCalendar` and `diagnoseSchedule` from `bizdays-toolkit/diagnose` list rules that can never apply, rules whose removal changes nothing, typos in option keys, calendars with no business day and more, without throwing. One line in your tests keeps a change of settings from silently killing a rule
|
|
26
26
|
- **Month-based due dates**: due dates such as "closing on the 20th, paid on the 10th of the next month (next business day if closed)" or "the last business day of the month", with the four adjustment conventions used in finance (following, preceding, modified-following, modified-preceding)
|
|
27
27
|
- **Business-day arithmetic**: closed weekdays, annual closures (including ranges across the new year), specific closed and open dates, and holidays combine to compute "N business days later", "next business day after the cut-off time" and more
|
|
28
|
+
- **Combining calendars**: `combineCalendars` builds a calendar of the days on which all of several calendars are open (your company and the bank, for a remittance date) or any of them is open (any of several stores), and business-day arithmetic works on the result
|
|
28
29
|
- **Time-zone safe**: instants such as `Date` are converted to a civil date in the calendar's time zone (`Asia/Tokyo` for Japan, `Europe/London` for the UK) before evaluation. A server running in UTC does not treat 00:00–09:00 in Japan as the previous day
|
|
29
|
-
- **Lightweight, zero dependencies**: about
|
|
30
|
+
- **Lightweight, zero dependencies**: about 8.6 KB min+gzip (about 10.3 KB with Japanese holidays, about 10.2 KB with UK bank holidays; measured for 0.11.0). ESM, bundled types and `sideEffects: false`. Written in standard JavaScript and `Intl` only (no Node.js APIs); CI tests it on Node.js, Chrome, Firefox, Edge, Safari and Cloudflare Workers
|
|
30
31
|
- **Extensible to other countries and regions**: holiday providers are pluggable through the `HolidayProvider` interface. Japan and the UK are bundled
|
|
31
32
|
|
|
32
33
|
## Installation
|
|
@@ -117,7 +118,7 @@ shipping.dayInfo("2027-01-01");
|
|
|
117
118
|
shipping.daysBetween("2026-12-30", "2027-01-04").map((d) => d.businessDay); // [true, false, false, false, false, true]
|
|
118
119
|
```
|
|
119
120
|
|
|
120
|
-
`daysBetween(from, to)` returns `dayInfo` for every day in the period (both ends included).
|
|
121
|
+
`daysBetween(from, to)` returns `dayInfo` for every day in the period (both ends included). `listBusinessDays` and `holidaysBetween` include both ends too; only `businessDaysBetween(from, to)` counts from `from` (inclusive) to `to` (exclusive), so that `businessDaysBetween(a, b) + businessDaysBetween(b, c)` equals `businessDaysBetween(a, c)` and it is the inverse of `addBusinessDays`.
|
|
121
122
|
|
|
122
123
|
`createCalendar` and `createSchedule` validate the options when they are created, so the usual pattern is to create them once and reuse them. Creating is also cheap (about 0.02 ms for a calendar and 0.04 ms for a schedule with Japanese holidays on a development machine), so creating one per request is not a problem in practice.
|
|
123
124
|
|
|
@@ -134,9 +135,9 @@ Each day is decided in this order of priority (top first).
|
|
|
134
135
|
|
|
135
136
|
This order is fixed. Only the relation between a recurring rule and holidays can be chosen per rule with `onHolidayRule` (next section).
|
|
136
137
|
|
|
137
|
-
### Recurring rules (weekly, nth weekday, day of month)
|
|
138
|
+
### Recurring rules (weekly, every other week, nth weekday, day of month, day after a holiday)
|
|
138
139
|
|
|
139
|
-
Recurring closed and open days such as "closed on the 3rd Wednesday", "closed on the 2nd and 4th Saturdays", "stocktaking on the last day of the month"
|
|
140
|
+
Recurring closed and open days such as "closed on the 3rd Wednesday", "closed on the 2nd and 4th Saturdays", "closed every other Wednesday", "stocktaking on the last day of the month", "closed on Mondays (on the next weekday if Monday is a holiday)" or "closed on the day after a holiday" go in `closedRecurring` and `openRecurring`.
|
|
140
141
|
|
|
141
142
|
```ts
|
|
142
143
|
const museum = createCalendar({
|
|
@@ -151,6 +152,7 @@ const museum = createCalendar({
|
|
|
151
152
|
name: "Closed on the 3rd Wednesday in February and August",
|
|
152
153
|
},
|
|
153
154
|
{ day: -1, name: "Stocktaking", onHolidayRule: "over-holiday" }, // closed even on holidays
|
|
155
|
+
{ dayAfterHoliday: true, name: "Closed on the day after a holiday" },
|
|
154
156
|
],
|
|
155
157
|
});
|
|
156
158
|
|
|
@@ -158,18 +160,48 @@ museum.isBusinessDay("2026-10-12"); // true (open on Monday, Sports Day)
|
|
|
158
160
|
museum.dayInfo("2026-10-13");
|
|
159
161
|
// { date: "2026-10-13", weekday: 2, businessDay: false, reason: "closed-recurring",
|
|
160
162
|
// name: "Closed on Mondays", shiftedFrom: "2026-10-12" }
|
|
163
|
+
museum.dayInfo("2026-02-12").name; // "Closed on the day after a holiday" (National Foundation Day is February 11)
|
|
164
|
+
museum.isBusinessDay("2026-05-07"); // false (the day after the May 3–6 run of holidays)
|
|
161
165
|
```
|
|
162
166
|
|
|
163
|
-
| Pattern
|
|
164
|
-
|
|
|
165
|
-
| `{ weekday: 1 }`
|
|
166
|
-
| `{ weekday: 3, nth: 3 }`
|
|
167
|
-
| `{
|
|
168
|
-
| `
|
|
167
|
+
| Pattern | Meaning |
|
|
168
|
+
| --------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- |
|
|
169
|
+
| `{ weekday: 1 }` | Every Monday |
|
|
170
|
+
| `{ weekday: 3, nth: 3 }` | The 3rd Wednesday. `nth` is 1–5 or -1 (last); an array such as `[2, 4]` gives several |
|
|
171
|
+
| `{ weekday: 3, interval: 2, anchor: "2026-01-07" }` | Every other Wednesday: the Wednesdays an even number of weeks from `anchor` (2 or more weeks; see below) |
|
|
172
|
+
| `{ day: 20 }` | The 20th of every month. `day` is 1–31 or -1 (last day); an array gives several |
|
|
173
|
+
| `{ dayAfterHoliday: true }` | The day after a holiday: the first non-holiday after a holiday or a run of holidays (holidays of the provider only) |
|
|
174
|
+
| `months: [8, 12]` | Narrows any pattern to these months |
|
|
175
|
+
| `effectiveFrom: "2027-04-01"`, `effectiveTo: "…"` | The period the rule is in effect (both ends included). Outside it, the rule is as if it were not written (see below) |
|
|
169
176
|
|
|
170
177
|
- **The nth weekday** is the nth occurrence of that weekday in the month (days 1–7 hold the 1st). It is not the weekday in the nth row of a printed calendar
|
|
171
178
|
- **Days that do not exist in a month** (`nth: 5` or `day: 31` in a month without one) do not match. Use `day: -1` for the last day
|
|
172
|
-
-
|
|
179
|
+
- **Every other week** (`interval: 2`) is not the same as the 1st and 3rd weekdays (`nth: [1, 3]`): a month with five Wednesdays shifts the latter by a week. `interval` is the number of weeks (an integer of 2 or more) and `anchor` is a date on the same weekday that the cycle passes through; the rule matches that date and every `interval` weeks before and after it. Stores announce "every other Wednesday from January 7", so copy that date. To start at the anchor rather than also matching earlier dates, add `effectiveFrom`. `interval` cannot be combined with `nth`
|
|
180
|
+
- **The day after a holiday** is never itself a holiday, so `onHolidayRule` cannot be set on such a rule (it is an `invalid-option`). "Holidays" are those of the holiday provider, not annual closures. A rule of this kind matches no day without a holiday provider
|
|
181
|
+
- Among recurring rules, the one written first in the array wins, and `openRecurring` wins over `closedRecurring`. The day after Labor Thanksgiving Day on a Monday (2026-11-23) is both the day the Monday rule moves to and the day after a holiday; the rule written first decides, and `dayInfo` shows which
|
|
182
|
+
|
|
183
|
+
**Effective periods** (`effectiveFrom`, `effectiveTo`) let one calendar represent a change of rules and still reproduce past dates. They go on recurring rules and on annual closures (`closedAnnual` in object form).
|
|
184
|
+
|
|
185
|
+
```ts
|
|
186
|
+
const shop = createCalendar({
|
|
187
|
+
holidays: jp,
|
|
188
|
+
closedRecurring: [
|
|
189
|
+
{ weekday: 1, name: "Closed on Mondays", effectiveTo: "2027-03-31" },
|
|
190
|
+
{ weekday: 2, name: "Closed on Tuesdays", effectiveFrom: "2027-04-01" },
|
|
191
|
+
{ weekday: 3, interval: 2, anchor: "2026-01-07", name: "Closed every other Wednesday" },
|
|
192
|
+
],
|
|
193
|
+
closedAnnual: [{ from: "08-13", to: "08-16", name: "Summer closure", effectiveTo: "2026-12-31" }],
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
shop.isBusinessDay("2027-03-29"); // false (Monday, before the change)
|
|
197
|
+
shop.isBusinessDay("2027-04-06"); // false (Tuesday, after the change)
|
|
198
|
+
shop.addBusinessDays("2027-03-30", 3); // "2027-04-03" (counts across the change; March 31 is an every-other-week Wednesday)
|
|
199
|
+
shop.isBusinessDay("2027-08-13"); // true (the summer closure ended in 2026)
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
- Outside its period a rule is simply not applied; the order of priority does not change. A rule with only `effectiveFrom` or only `effectiveTo` is open-ended on the other side
|
|
203
|
+
- An `"after-holiday"` rule is judged by the date it would originally apply to: if that date is within the period, the rule is moved to the next non-holiday even when that day is outside the period
|
|
204
|
+
- Changing the closed weekday is written as two weekly rules with periods, as above, rather than with `closedWeekdays` (which has no period)
|
|
173
205
|
|
|
174
206
|
**What happens on a holiday** (`onHolidayRule`) is chosen per rule.
|
|
175
207
|
|
|
@@ -196,6 +228,43 @@ const hours = createSchedule<Hours>({
|
|
|
196
228
|
});
|
|
197
229
|
```
|
|
198
230
|
|
|
231
|
+
### Combining calendars
|
|
232
|
+
|
|
233
|
+
`combineCalendars` makes one calendar out of several: the days on which **all** of them are open (`businessDay: "all"`), or on which **any** of them is open (`"any"`). The result is an ordinary calendar, so business-day arithmetic, `adjust`, `dueDate` and the rest work on the combination.
|
|
234
|
+
|
|
235
|
+
```ts
|
|
236
|
+
import { combineCalendars, createCalendar } from "bizdays-toolkit";
|
|
237
|
+
import { jp } from "bizdays-toolkit/jp";
|
|
238
|
+
|
|
239
|
+
const company = createCalendar({
|
|
240
|
+
holidays: jp,
|
|
241
|
+
closedWeekdays: [0, 6],
|
|
242
|
+
closedAnnual: [{ from: "12-29", to: "01-03", name: "New Year closure" }],
|
|
243
|
+
});
|
|
244
|
+
const bank = createCalendar({
|
|
245
|
+
holidays: jp,
|
|
246
|
+
closedWeekdays: [0, 6],
|
|
247
|
+
closedAnnual: [{ from: "12-31", to: "01-03", name: "Bank holiday period" }],
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
// A remittance is processed only on days when both the company and the bank are open
|
|
251
|
+
const remittance = combineCalendars([company, bank], { businessDay: "all" });
|
|
252
|
+
remittance.addBusinessDays("2026-12-28", 2); // "2027-01-05"
|
|
253
|
+
remittance.dayInfo("2026-12-29");
|
|
254
|
+
// { date: "2026-12-29", weekday: 2, businessDay: false, reason: "closed-annual", name: "New Year closure", decidedBy: 0 }
|
|
255
|
+
|
|
256
|
+
// Any of several stores is open
|
|
257
|
+
const anyStore = combineCalendars([company, bank], { businessDay: "any" });
|
|
258
|
+
anyStore.isBusinessDay("2026-12-29"); // true
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
- `dayInfo` reports `reason`, `name` and `shiftedFrom` from the first calendar whose result equals the combined result, and the index of that calendar in `decidedBy`. With `"all"`, a closed day is explained by the first calendar that is closed, and an open day by calendar 0 (all are open). With `"any"` it is the other way round
|
|
262
|
+
- `holiday`, `isHoliday`, `holidaysInYear` and so on use the union of the calendars' holiday providers. `outOfRange` is `"throw"` if any calendar throws outside its supported years
|
|
263
|
+
- All calendars must share a time zone (`time-zone-conflict` otherwise). Passing fewer than two calendars, or the same calendar twice, is an `invalid-argument`. `maxSearchDays` (default 3660) can be given in the options
|
|
264
|
+
- A combined calendar can be combined again. Option diagnostics work on options, not on calendars, so diagnose each calendar's options separately
|
|
265
|
+
|
|
266
|
+
Combining options instead would not work: a specific open day (`openDates`) in one set of options would override closures in the other, and wrapping one calendar as a holiday provider of the other would turn its closures into "holidays" (`isHoliday` would be true, and `holidayPolicy: "open"` would open on them).
|
|
267
|
+
|
|
199
268
|
### Common recipes
|
|
200
269
|
|
|
201
270
|
**Treat citizens' holidays (days between two holidays) as business days**: wrap the holiday provider and filter by kind.
|
|
@@ -338,7 +407,7 @@ const hours = storeHours.valueOn(date);
|
|
|
338
407
|
const isOpen = hours !== null && hours.open <= time && time < hours.close;
|
|
339
408
|
```
|
|
340
409
|
|
|
341
|
-
For inputs that always carry a time (a `Date`, Temporal `Instant`, `ZonedDateTime` or `PlainDateTime`, or a `"YYYY-MM-DDTHH:mm"` string), `time` is typed as `string`, so no `null` check is needed. `civilOf` is also on business-day calendars, and the same conversion is available as `toJapanDateTime` in `bizdays-toolkit/jp`
|
|
410
|
+
For inputs that always carry a time (a `Date`, Temporal `Instant`, `ZonedDateTime` or `PlainDateTime`, or a `"YYYY-MM-DDTHH:mm"` string), `time` is typed as `string`, so no `null` check is needed. `civilOf` is also on business-day calendars, and the same conversion is available as `toDateTimeParts(input, timeZone)` in the root. (`toJapanDate` and `toJapanDateTime` in `bizdays-toolkit/jp` are deprecated since 0.10.0: use `toDateString(input, "Asia/Tokyo")`, `toDateTimeParts(input, "Asia/Tokyo")` or `dateOf`/`civilOf` on a calendar instead. They will be removed in 1.0.0.)
|
|
342
411
|
|
|
343
412
|
### Date and date-time inputs
|
|
344
413
|
|
|
@@ -448,22 +517,24 @@ diagnoseCalendar({
|
|
|
448
517
|
// ]
|
|
449
518
|
```
|
|
450
519
|
|
|
451
|
-
| `severity` | `code` | Meaning
|
|
452
|
-
| ---------- | ---------------------------- |
|
|
453
|
-
| `error` | The same codes as exceptions | Errors that make `createCalendar` or `createSchedule` throw. Creation stops at the first one; diagnostics return them all
|
|
454
|
-
| `error` | `invalid-provider-data` | Errors in the data returned by a holiday provider. Creation checks only the years used; diagnostics check every supported year
|
|
455
|
-
| `warning` | `unknown-option` | Unknown keys (ignored at creation). If a close known key exists, `message` suggests it
|
|
456
|
-
| `warning` | `shadowed` | A rule fully covered by an earlier rule of the same type (`relatedPath`) and never used
|
|
457
|
-
| `warning` | `never-matches` | A recurring rule that matches no day (such as `{ day: 30, months: [2] }`)
|
|
458
|
-
| `warning` | `out-of-range` | A specific date outside the supported years of the holiday provider (`relatedPath`)
|
|
459
|
-
| `warning` | `no-business-day` | No business day at all (calendars only; searching for a business day throws)
|
|
460
|
-
| `info` | `redundant` | A rule whose removal changes nothing (covered by a combination of earlier rules, same as a closed weekday, the same value as the weekday in a schedule, and so on)
|
|
520
|
+
| `severity` | `code` | Meaning |
|
|
521
|
+
| ---------- | ---------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
522
|
+
| `error` | The same codes as exceptions | Errors that make `createCalendar` or `createSchedule` throw. Creation stops at the first one; diagnostics return them all |
|
|
523
|
+
| `error` | `invalid-provider-data` | Errors in the data returned by a holiday provider. Creation checks only the years used; diagnostics check every supported year |
|
|
524
|
+
| `warning` | `unknown-option` | Unknown keys (ignored at creation). If a close known key exists, `message` suggests it |
|
|
525
|
+
| `warning` | `shadowed` | A rule fully covered by an earlier rule of the same type (`relatedPath`) and never used |
|
|
526
|
+
| `warning` | `never-matches` | A recurring rule that matches no day (such as `{ day: 30, months: [2] }`, a 5th Wednesday with an effective period that has none, or `dayAfterHoliday` without a holiday provider) |
|
|
527
|
+
| `warning` | `out-of-range` | A specific date, or the effective period of a rule, outside the supported years of the holiday provider (`relatedPath`) |
|
|
528
|
+
| `warning` | `no-business-day` | No business day at all (calendars only; searching for a business day throws) |
|
|
529
|
+
| `info` | `redundant` | A rule whose removal changes nothing (covered by a combination of earlier rules, same as a closed weekday, the same value as the weekday in a schedule, and so on) |
|
|
461
530
|
|
|
462
531
|
- Branch on `code`. `message` is in English and its wording may change. Future versions may add codes
|
|
463
532
|
- When there is an `error`, the rule relation checks (`shadowed`, `redundant` and so on) are skipped
|
|
464
533
|
- `shadowed` compares only rules of the same type (specific dates, annual dates, recurring rules). The order of evaluation is fixed, so rules unused across types are found as `redundant`. Specific open days (`openDates`) and specific closed days (`closedDates`) are of the same type, and a closed day covered by an earlier open day is `shadowed`
|
|
465
534
|
- `shadowed` for recurring rules also considers `onHolidayRule`. For example, an `"under-holiday"` rule does not cover an `"over-holiday"` rule on the same days (the later rule applies on holidays)
|
|
466
|
-
- `
|
|
535
|
+
- `shadowed` for `dayAfterHoliday` rules is checked only against other `dayAfterHoliday` rules (by `months` and period), because which days they match depend on the holidays of each year; overlap with weekday rules is found as `redundant`
|
|
536
|
+
- `shadowed` for rules with effective periods also requires the earlier rule's period to contain the later rule's period, and for `interval` rules the earlier rule's cycle to contain the later one (a weekly rule covers an every-other-week rule; two every-other-week rules with opposite phases do not cover each other)
|
|
537
|
+
- `redundant` compares the results with and without the rule day by day. Specific dates are checked over their range; annual dates and recurring rules are checked, with specific dates removed, over the holiday provider's supported years (1955–2099 for Japan), or 2001–2028 without a provider (28 years cover every combination of weekday and date; with `interval` rules the range is extended to the length of their cycle, such as 56 years for every other week). Effective periods outside that range are checked as well (with `outOfRange: "throw"`, only within the supported years). `message` names the years checked. Schedule values are compared by content for arrays and plain objects, and by identity otherwise (such as `Date`)
|
|
467
538
|
- With Japanese holidays, checking every day takes a few hundred milliseconds. Use it in tests or at startup, not per request. As a development and test tool, it lives in a subpath so it stays out of your app's main bundle
|
|
468
539
|
|
|
469
540
|
Keeping options safe with a test:
|
package/dist/core/calendar.d.ts
CHANGED
|
@@ -15,12 +15,14 @@
|
|
|
15
15
|
* 祝日には当てはめず、その日以降で最初の祝日でない日に当てはめる(振替)
|
|
16
16
|
*
|
|
17
17
|
* 内部では、営業日かどうか(真偽値)を返すスケジュール(createSchedule)として作っている。
|
|
18
|
+
* 営業日の探索・調整・月の営業日などの機能は buildCalendar に切り出し、作成済みのカレンダーを
|
|
19
|
+
* 合成する combineCalendars(両方が営業している日・どちらかが営業している日)でも同じ実装を使う。
|
|
18
20
|
*/
|
|
19
21
|
import { type DateString, type Weekday } from "./date.ts";
|
|
20
22
|
import { IssueCollector } from "./errors.ts";
|
|
21
23
|
import { type DateInput } from "./input.ts";
|
|
22
24
|
import { type MonthString } from "./month.ts";
|
|
23
|
-
import { type CompiledSchedule, type DayOfMonth, type RecurringRule, type HolidayLookup, type HolidaySourceOptions } from "./schedule.ts";
|
|
25
|
+
import { type CompiledSchedule, type DayOfMonth, type EffectivePeriod, type RecurringRule, type HolidayLookup, type HolidaySourceOptions } from "./schedule.ts";
|
|
24
26
|
import type { Holiday } from "./types.ts";
|
|
25
27
|
/**
|
|
26
28
|
* 祝日の扱い。
|
|
@@ -28,18 +30,18 @@ import type { Holiday } from "./types.ts";
|
|
|
28
30
|
*/
|
|
29
31
|
export type HolidayPolicy = "closed" | "open" | "ignore";
|
|
30
32
|
/**
|
|
31
|
-
* 毎年の月日 "MM-DD"
|
|
33
|
+
* 毎年の月日 "MM-DD"(名前付き・有効期間付きも可)。
|
|
32
34
|
* @public
|
|
33
35
|
*/
|
|
34
|
-
export type AnnualDate = string | {
|
|
36
|
+
export type AnnualDate = string | ({
|
|
35
37
|
readonly date: string;
|
|
36
38
|
readonly name?: string;
|
|
37
|
-
};
|
|
39
|
+
} & EffectivePeriod);
|
|
38
40
|
/**
|
|
39
41
|
* 毎年の月日の期間(from > to なら年をまたぐ。例: 12-31 〜 01-03)。
|
|
40
42
|
* @public
|
|
41
43
|
*/
|
|
42
|
-
export interface AnnualRange {
|
|
44
|
+
export interface AnnualRange extends EffectivePeriod {
|
|
43
45
|
readonly from: string;
|
|
44
46
|
readonly to: string;
|
|
45
47
|
readonly name?: string;
|
|
@@ -68,15 +70,16 @@ export interface DateRange {
|
|
|
68
70
|
export interface CalendarOptions<Kind extends string = string> extends HolidaySourceOptions<Kind> {
|
|
69
71
|
/** 定休日の曜日(0 = 日曜日) */
|
|
70
72
|
readonly closedWeekdays?: readonly Weekday[];
|
|
71
|
-
/**
|
|
73
|
+
/** 毎年の休業日(有効期間 effectiveFrom・effectiveTo を付けられる) */
|
|
72
74
|
readonly closedAnnual?: readonly (AnnualDate | AnnualRange)[];
|
|
73
75
|
/** 臨時休業日 */
|
|
74
76
|
readonly closedDates?: readonly (SpecificDate | DateRange)[];
|
|
75
77
|
/** 臨時営業日(ほかのすべての規則より優先) */
|
|
76
78
|
readonly openDates?: readonly (SpecificDate | DateRange)[];
|
|
77
79
|
/**
|
|
78
|
-
*
|
|
79
|
-
* 祝日に当たったときの扱いは規則ごとに onHolidayRule で指定する(既定: "under-holiday"
|
|
80
|
+
* 繰り返しの休業日(毎週・隔週などの周期・毎月の第 n 曜日・毎月の日)。
|
|
81
|
+
* 祝日に当たったときの扱いは規則ごとに onHolidayRule で指定する(既定: "under-holiday")。
|
|
82
|
+
* 有効期間(effectiveFrom・effectiveTo)を付けると、期間の外では書かれていないのと同じに扱う
|
|
80
83
|
*/
|
|
81
84
|
readonly closedRecurring?: readonly RecurringRule[];
|
|
82
85
|
/** 繰り返しの営業日。closedRecurring と同じ日に当たる場合はこちらが優先 */
|
|
@@ -113,6 +116,24 @@ export interface DayInfo<Kind extends string = string> {
|
|
|
113
116
|
readonly holiday?: Holiday<Kind>;
|
|
114
117
|
/** 祝日から振り替えられた繰り返しの規則で決まった場合、振替の元の日付(onHolidayRule: "after-holiday") */
|
|
115
118
|
readonly shiftedFrom?: DateString;
|
|
119
|
+
/**
|
|
120
|
+
* 合成したカレンダー(combineCalendars)で、判定を決めたカレンダーの番号(渡した配列の位置)。
|
|
121
|
+
* reason・name・shiftedFrom はそのカレンダーの結果。合成していないカレンダーでは付かない
|
|
122
|
+
*/
|
|
123
|
+
readonly decidedBy?: number;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* カレンダーの合成のオプション。
|
|
127
|
+
* @public
|
|
128
|
+
*/
|
|
129
|
+
export interface CombineOptions {
|
|
130
|
+
/**
|
|
131
|
+
* 営業日とする条件。"all": すべてのカレンダーが営業日(例: 自社と銀行の両方が営業している日)/
|
|
132
|
+
* "any": いずれかのカレンダーが営業日(例: 複数の店舗のどこかが開いている日)
|
|
133
|
+
*/
|
|
134
|
+
readonly businessDay: "all" | "any";
|
|
135
|
+
/** 営業日を探すときに調べる最大日数(既定: 3660)。超えたら Error */
|
|
136
|
+
readonly maxSearchDays?: number;
|
|
116
137
|
}
|
|
117
138
|
/**
|
|
118
139
|
* 営業日を探すときのオプション。
|
|
@@ -216,4 +237,17 @@ export declare function compileCalendarOptions<Kind extends string>(c: IssueColl
|
|
|
216
237
|
* @public
|
|
217
238
|
*/
|
|
218
239
|
export declare function createCalendar<Kind extends string = string>(options?: CalendarOptions<Kind>): Calendar<Kind>;
|
|
240
|
+
/**
|
|
241
|
+
* 作成済みの営業日カレンダーを合成する。"all" はすべてのカレンダーが営業している日、"any" はいずれかが
|
|
242
|
+
* 営業している日を営業日とする。返すのは通常の営業日カレンダーで、営業日の加減算・調整・期日などが合成結果で動く。
|
|
243
|
+
*
|
|
244
|
+
* - dayInfo の reason・name・shiftedFrom は、合成結果と同じ判定をした最初のカレンダーの結果で、
|
|
245
|
+
* その番号を decidedBy で返す。holiday・isHoliday などは、すべてのカレンダーの祝日の提供元の和集合で判定する
|
|
246
|
+
* - タイムゾーンはすべてのカレンダーで同じでなければならない(time-zone-conflict)。
|
|
247
|
+
* 2 つ未満、同じカレンダーを 2 回渡した場合は invalid-argument
|
|
248
|
+
* - 合成したカレンダーをさらに合成することもできる。規則の診断(diagnoseCalendar)の対象にはならないので、
|
|
249
|
+
* 元のカレンダーの設定をそれぞれ診断する
|
|
250
|
+
* @public
|
|
251
|
+
*/
|
|
252
|
+
export declare function combineCalendars<Kind extends string = string>(calendars: readonly Calendar<Kind>[], options: CombineOptions): Calendar<Kind>;
|
|
219
253
|
export {};
|
package/dist/core/calendar.js
CHANGED
|
@@ -15,12 +15,14 @@
|
|
|
15
15
|
* 祝日には当てはめず、その日以降で最初の祝日でない日に当てはめる(振替)
|
|
16
16
|
*
|
|
17
17
|
* 内部では、営業日かどうか(真偽値)を返すスケジュール(createSchedule)として作っている。
|
|
18
|
+
* 営業日の探索・調整・月の営業日などの機能は buildCalendar に切り出し、作成済みのカレンダーを
|
|
19
|
+
* 合成する combineCalendars(両方が営業している日・どちらかが営業している日)でも同じ実装を使う。
|
|
18
20
|
*/
|
|
19
21
|
import { addDays, daysInMonth, formatDate, fromDayNumber, toDayNumber, } from "./date.js";
|
|
20
22
|
import { checkUnknownKeys, IssueCollector, plainError, rangeError } from "./errors.js";
|
|
21
23
|
import { parseTimeOfDay, toCivil } from "./input.js";
|
|
22
24
|
import { dayOfMonth, isDayOfMonthValue, parseMonth } from "./month.js";
|
|
23
|
-
import { buildSchedule, compileAnnualRules, compileDateRules, compileHolidaySource, compileRecurringRules, isWeekday, mapDays, RECURRING_RULE_KEYS, } from "./schedule.js";
|
|
25
|
+
import { buildSchedule, compileAnnualRules, compileDateRules, compileHolidaySource, compileRecurringRules, createHolidayLookup, isWeekday, mapDays, RECURRING_RULE_KEYS, } from "./schedule.js";
|
|
24
26
|
const CALENDAR_OPTION_KEYS = [
|
|
25
27
|
"holidays",
|
|
26
28
|
"timeZone",
|
|
@@ -139,8 +141,14 @@ export function createCalendar(options = {}) {
|
|
|
139
141
|
result.shiftedFrom = day.shiftedFrom;
|
|
140
142
|
return Object.freeze(result);
|
|
141
143
|
}
|
|
142
|
-
|
|
143
|
-
|
|
144
|
+
return buildCalendar(schedule, (date) => schedule.valueOn(date), info, maxSearchDays);
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* @internal 祝日を調べる機能と 1 日分の判定から、営業日カレンダーの機能(探索・調整・月の営業日・期日)を組み立てる。
|
|
148
|
+
* createCalendar と combineCalendars で共用する
|
|
149
|
+
*/
|
|
150
|
+
function buildCalendar(lookup, isOpen, info, maxSearchDays) {
|
|
151
|
+
const dateOf = lookup.dateOf;
|
|
144
152
|
function search(start, step) {
|
|
145
153
|
let n = toDayNumber(start);
|
|
146
154
|
for (let i = 0; i < maxSearchDays; i++, n += step) {
|
|
@@ -226,22 +234,23 @@ export function createCalendar(options = {}) {
|
|
|
226
234
|
return found;
|
|
227
235
|
}
|
|
228
236
|
const calendar = {
|
|
229
|
-
timeZone:
|
|
230
|
-
providers:
|
|
237
|
+
timeZone: lookup.timeZone,
|
|
238
|
+
providers: lookup.providers,
|
|
239
|
+
outOfRange: lookup.outOfRange,
|
|
231
240
|
dateOf,
|
|
232
|
-
civilOf:
|
|
233
|
-
today:
|
|
234
|
-
isHoliday:
|
|
235
|
-
getHoliday:
|
|
236
|
-
holidaysInYear:
|
|
237
|
-
holidaysBetween:
|
|
241
|
+
civilOf: lookup.civilOf,
|
|
242
|
+
today: lookup.today,
|
|
243
|
+
isHoliday: lookup.isHoliday,
|
|
244
|
+
getHoliday: lookup.getHoliday,
|
|
245
|
+
holidaysInYear: lookup.holidaysInYear,
|
|
246
|
+
holidaysBetween: lookup.holidaysBetween,
|
|
238
247
|
dayInfo: (input) => info(dateOf(input)),
|
|
239
248
|
daysBetween: (from, to) => mapDays(dateOf(from), dateOf(to), info),
|
|
240
249
|
isBusinessDay: (input) => isOpen(dateOf(input)),
|
|
241
250
|
nextBusinessDay: (input) => search(addDays(dateOf(input), 1), 1),
|
|
242
251
|
previousBusinessDay: (input) => search(addDays(dateOf(input), -1), -1),
|
|
243
252
|
businessDayOnOrAfter(input, opts = {}) {
|
|
244
|
-
const civil = toCivil(input,
|
|
253
|
+
const civil = toCivil(input, lookup.timeZone);
|
|
245
254
|
let start = civil.date;
|
|
246
255
|
if (opts.cutoff !== undefined) {
|
|
247
256
|
const cutoff = parseTimeOfDay(opts.cutoff);
|
|
@@ -295,3 +304,81 @@ export function createCalendar(options = {}) {
|
|
|
295
304
|
};
|
|
296
305
|
return Object.freeze(calendar);
|
|
297
306
|
}
|
|
307
|
+
const isCalendarLike = (v) => typeof v === "object" &&
|
|
308
|
+
v !== null &&
|
|
309
|
+
typeof v.dayInfo === "function" &&
|
|
310
|
+
typeof v.isBusinessDay === "function" &&
|
|
311
|
+
typeof v.timeZone === "string" &&
|
|
312
|
+
Array.isArray(v.providers);
|
|
313
|
+
/**
|
|
314
|
+
* 作成済みの営業日カレンダーを合成する。"all" はすべてのカレンダーが営業している日、"any" はいずれかが
|
|
315
|
+
* 営業している日を営業日とする。返すのは通常の営業日カレンダーで、営業日の加減算・調整・期日などが合成結果で動く。
|
|
316
|
+
*
|
|
317
|
+
* - dayInfo の reason・name・shiftedFrom は、合成結果と同じ判定をした最初のカレンダーの結果で、
|
|
318
|
+
* その番号を decidedBy で返す。holiday・isHoliday などは、すべてのカレンダーの祝日の提供元の和集合で判定する
|
|
319
|
+
* - タイムゾーンはすべてのカレンダーで同じでなければならない(time-zone-conflict)。
|
|
320
|
+
* 2 つ未満、同じカレンダーを 2 回渡した場合は invalid-argument
|
|
321
|
+
* - 合成したカレンダーをさらに合成することもできる。規則の診断(diagnoseCalendar)の対象にはならないので、
|
|
322
|
+
* 元のカレンダーの設定をそれぞれ診断する
|
|
323
|
+
* @public
|
|
324
|
+
*/
|
|
325
|
+
export function combineCalendars(calendars, options) {
|
|
326
|
+
if (!Array.isArray(calendars) || calendars.length < 2) {
|
|
327
|
+
throw rangeError("invalid-argument", "calendars must be an array of two or more calendars");
|
|
328
|
+
}
|
|
329
|
+
calendars.forEach((c, i) => {
|
|
330
|
+
if (!isCalendarLike(c))
|
|
331
|
+
throw rangeError("invalid-argument", `calendars[${i}] is not a calendar`);
|
|
332
|
+
if (calendars.indexOf(c) !== i) {
|
|
333
|
+
throw rangeError("invalid-argument", `calendars[${i}] is the same calendar as calendars[${calendars.indexOf(c)}]`);
|
|
334
|
+
}
|
|
335
|
+
});
|
|
336
|
+
const mode = options?.businessDay;
|
|
337
|
+
if (mode !== "all" && mode !== "any") {
|
|
338
|
+
throw rangeError("invalid-option", 'businessDay must be "all" or "any"');
|
|
339
|
+
}
|
|
340
|
+
const maxSearchDays = options.maxSearchDays ?? 3660;
|
|
341
|
+
if (!Number.isInteger(maxSearchDays) || maxSearchDays < 1) {
|
|
342
|
+
throw rangeError("invalid-option", "maxSearchDays must be a positive integer");
|
|
343
|
+
}
|
|
344
|
+
const zones = [...new Set(calendars.map((c) => c.timeZone))];
|
|
345
|
+
if (zones.length > 1) {
|
|
346
|
+
throw rangeError("time-zone-conflict", `Calendars use different time zones (${zones.join(", ")}); combine calendars with the same time zone`);
|
|
347
|
+
}
|
|
348
|
+
const providers = [];
|
|
349
|
+
for (const c of calendars)
|
|
350
|
+
for (const p of c.providers)
|
|
351
|
+
if (!providers.includes(p))
|
|
352
|
+
providers.push(p);
|
|
353
|
+
const lookup = createHolidayLookup({
|
|
354
|
+
providers: Object.freeze(providers),
|
|
355
|
+
timeZone: zones[0],
|
|
356
|
+
outOfRange: calendars.every((c) => c.outOfRange === "ignore") ? "ignore" : "throw",
|
|
357
|
+
});
|
|
358
|
+
const isOpen = (date) => mode === "all"
|
|
359
|
+
? calendars.every((c) => c.isBusinessDay(date))
|
|
360
|
+
: calendars.some((c) => c.isBusinessDay(date));
|
|
361
|
+
function info(date) {
|
|
362
|
+
const results = calendars.map((c) => c.dayInfo(date));
|
|
363
|
+
const open = mode === "all" ? results.every((r) => r.businessDay) : results.some((r) => r.businessDay);
|
|
364
|
+
// 合成結果と同じ判定をした最初のカレンダー("all" で休業なら最初に休みのもの、営業ならすべて営業なので 0 番)
|
|
365
|
+
const decidedBy = results.findIndex((r) => r.businessDay === open);
|
|
366
|
+
const base = results[decidedBy];
|
|
367
|
+
const result = {
|
|
368
|
+
date,
|
|
369
|
+
weekday: base.weekday,
|
|
370
|
+
businessDay: open,
|
|
371
|
+
reason: base.reason,
|
|
372
|
+
};
|
|
373
|
+
if (base.name !== undefined)
|
|
374
|
+
result.name = base.name;
|
|
375
|
+
const holiday = lookup.holidayOn(date);
|
|
376
|
+
if (holiday !== undefined)
|
|
377
|
+
result.holiday = holiday;
|
|
378
|
+
if (base.shiftedFrom !== undefined)
|
|
379
|
+
result.shiftedFrom = base.shiftedFrom;
|
|
380
|
+
result.decidedBy = decidedBy;
|
|
381
|
+
return Object.freeze(result);
|
|
382
|
+
}
|
|
383
|
+
return buildCalendar(lookup, isOpen, info, maxSearchDays);
|
|
384
|
+
}
|
package/dist/core/diagnose.d.ts
CHANGED
|
@@ -8,14 +8,16 @@
|
|
|
8
8
|
* - warning … 作れるが、意図と違う可能性が高いもの
|
|
9
9
|
* unknown-option … 知らないキー(作成時は無視される)。近い既知のキーがあれば message に候補を挙げる
|
|
10
10
|
* shadowed … 同じ種類の先の規則に完全に覆われ、決して使われない(relatedPath に先の規則)
|
|
11
|
-
* never-matches … 当てはまる日がない繰り返しの規則(2 月 30
|
|
12
|
-
* out-of-range …
|
|
11
|
+
* never-matches … 当てはまる日がない繰り返しの規則(2 月 30 日、有効期間の中に第 5 水曜日がない、提供元のない設定の祝日の翌日、など)
|
|
12
|
+
* out-of-range … 個別の日付、または規則の有効期間が祝日の提供元の対応年の外にある(relatedPath に提供元)
|
|
13
13
|
* no-business-day … 営業日が 1 日もない設定(営業日カレンダーのみ)
|
|
14
14
|
* - info … redundant: 取り除いても結果が変わらない規則
|
|
15
15
|
*
|
|
16
16
|
* 判定の順番(個別の日付 → 毎年の月日 → 繰り返し(over)→ 祝日 → 繰り返し(under・振替先)→ 曜日)は
|
|
17
17
|
* 固定なので、shadowed は同じ種類の規則どうしに限って調べる。種類をまたいで使われない規則は
|
|
18
18
|
* redundant(取り除いても結果が変わらない)として見つかる。
|
|
19
|
+
* 有効期間(effectiveFrom・effectiveTo)のある規則は期間の包含も、周期(interval)のある規則は周期の包含も
|
|
20
|
+
* shadowed の条件にする。redundant の走査の範囲は、周期がそろう長さと、範囲の外にある有効期間まで広げる
|
|
19
21
|
*/
|
|
20
22
|
import { type CalendarOptions } from "./calendar.ts";
|
|
21
23
|
import { type KnownErrorCode } from "./errors.ts";
|