bizdays-toolkit 0.11.1 → 0.13.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 +55 -1
- package/README.md +44 -13
- package/dist/core/calendar.d.ts +26 -4
- package/dist/core/calendar.js +87 -42
- package/dist/core/diagnose.js +31 -23
- package/dist/core/schedule.d.ts +41 -7
- package/dist/core/schedule.js +67 -21
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/jp/index.d.ts +2 -0
- package/dist/jp/index.js +2 -0
- package/dist/uk/index.d.ts +2 -0
- package/dist/uk/index.js +2 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,58 @@
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [0.13.0] - 2026-10-20
|
|
8
|
+
|
|
9
|
+
### 移行
|
|
10
|
+
|
|
11
|
+
動作は変わりません(スケジュールから営業日カレンダーを作る関数と、判定の理由の新しい値の追加。CI の改善)。版ごとの作業は [docs/migration.md](./docs/migration.md) にまとめた。
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- スケジュールから営業日カレンダーを作る `createCalendarFromSchedule(schedule, isOpen, { maxSearchDays? })` を追加した。`isOpen` が true を返す値の日を営業日とする通常のカレンダーを返し、営業時間表のようなスケジュールが唯一の設定になる。`isOpen` は値だけを受け取る純粋な関数(日付は渡さない。営業日の探索で繰り返し呼ぶため)。`dayInfo` の `reason` はスケジュールの `reason` と営業・休業の組み合わせで、`name`・`holiday`・`shiftedFrom`・`rulePath` はスケジュールの結果を引き継ぐ。祝日・タイムゾーン・`outOfRange` はスケジュールのもの。`createSchedule` が作ったスケジュール以外は `invalid-argument`
|
|
16
|
+
- 判定の理由(`DayReason`)に `"open-annual"`(スケジュールの `annual` の値で営業になる日)を追加した。`createCalendar` の結果には現れない(`closedAnnual` は休業だけ)
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
|
|
20
|
+
- README: 「スケジュールから営業日カレンダーを作る」の節を足した。型定義の確認の範囲を、CI で確かめる TypeScript の版(5.0・5.5・5.8・6.0・7.0)に改めた。サイズの値を測り直した(ルート約 9.0KB・日本の祝日込み約 10.8KB・英国の祝日込み約 10.7KB。0.13.0 で測定)
|
|
21
|
+
- `docs/decisions.md`: 38 番(スケジュールからのカレンダー・`"open-annual"`・利用者の TypeScript の版の確認)を追加した
|
|
22
|
+
|
|
23
|
+
### 開発
|
|
24
|
+
|
|
25
|
+
- CI に `consumer-types` ジョブを追加した。公開する型定義を、利用者が使う TypeScript 5.0.4・5.5.4・5.8.3・7.0.2(ネイティブ版)のそれぞれで、`moduleResolution` の `node16`・`nodenext`・`bundler` で型チェックする。各版は依存にせず、版と SHA-256 を固定した tgz から一時ディレクトリに入れる(7.0 はランナー向けの実行形式 `@typescript/typescript-linux-x64` も固定)。`pnpm check` は従来どおり開発用の 6.0.3 で確かめる
|
|
26
|
+
- `scripts/check-consumer-types.ts` に `--tsc <path>` を足し、わざと型エラーを含む `test/consumer/consumer-error.mts` が各版で失敗として報告されること(検査が機能していること)も確かめるようにした。リポジトリの `@types` を読まないよう `--typeRoots` を空にした(古い TypeScript では `@types/node` の新しい構文を読めないため)。`--ignoreConfig` は 6.0 以降でだけ渡す
|
|
27
|
+
- `createCalendar` の `dayInfo` の変換を `createCalendarFromSchedule` と共用の内部関数に切り出した。結果は変わらない
|
|
28
|
+
|
|
29
|
+
## [0.12.0] - 2026-10-20
|
|
30
|
+
|
|
31
|
+
### 移行
|
|
32
|
+
|
|
33
|
+
動作は変わりません(繰り返しの規則の新しい形・`dayInfo` の新しい項目の追加と、診断・CI の改善)。`bizdays-toolkit/jp`・`bizdays-toolkit/uk` の定数 `dataVersion`・`confirmedThrough` が非推奨になったので、使っていれば提供元の項目(`jp.dataVersion`・`england.confirmedThrough` など)に書き換える。版ごとの作業は [docs/migration.md](./docs/migration.md) にまとめた。
|
|
34
|
+
|
|
35
|
+
### Added
|
|
36
|
+
|
|
37
|
+
- 繰り返しの規則に、祝日の形 `{ holiday: true, kinds?, months? }`(型 `HolidayPattern`)を追加した。祝日の既定の扱い(`holidayPolicy`・スケジュールの `holiday`)の例外を、「2027 年 4 月から祝日も営業」(`openRecurring: [{ holiday: true, effectiveFrom: "2027-04-01" }]`)、「振替休日と国民の休日は営業」(`kinds: ["substitute", "citizens"]`)、「5 月の祝日は特別営業時間」(スケジュールの `recurring`)のように書ける。`kinds` は `months` と同じ絞り込みの配列(省略時はすべての祝日)。判定は「祝日の上」の段階(`"over-holiday"` の規則と同じ)で、`holidayPolicy: "ignore"` でも当てはまる。当てはまる日は祝日なので `onHolidayRule` は指定できない(`invalid-option`)。`kinds` は `holiday` の規則でだけ使える
|
|
38
|
+
- `DayInfo`・`ScheduleDay` に `rulePath`(値を決めた規則の設定の中の位置。規則の診断の `path` と同じ形。例: `"closedRecurring[0]"`)を追加した。`dates`・`annual`・`recurring`(振替先を含む)で決まった日にだけ付き、祝日・曜日で決まった日には付かない。合成したカレンダーは決めたカレンダーの `rulePath` を引き継ぐ
|
|
39
|
+
|
|
40
|
+
### Deprecated
|
|
41
|
+
|
|
42
|
+
- `bizdays-toolkit/jp`・`bizdays-toolkit/uk` の定数 `dataVersion`・`confirmedThrough` を非推奨にした(`@deprecated`)。提供元の項目(`jp.dataVersion`・`jp.confirmedThrough`、英国は `england.dataVersion` など 4 つの提供元で同じ値)と同じ値で、経路を 1 つにするため。1.0.0 で削除する
|
|
43
|
+
|
|
44
|
+
### Changed
|
|
45
|
+
|
|
46
|
+
- 規則の診断で、祝日の規則を扱えるようにした。提供元のない設定、または `kinds`・`months`・有効期間に当てはまる祝日がない規則は `never-matches`。`shadowed` は祝日の規則どうし(`kinds`・`months`・有効期間の包含)でだけ調べ、既定の扱いと同じ向きの規則は `redundant` で見つける
|
|
47
|
+
- 規則の診断の `unknown-option` の候補(did you mean)で、既知のキーの先頭部分(`onHoliday` → `onHolidayRule`)を、短い別のキー(`holiday`)との編集距離より優先するようにした
|
|
48
|
+
- 繰り返しの規則の形の誤りのメッセージを、`"weekday"`・`"day"`・`"dayAfterHoliday"`・`"holiday"` のうち 1 つだけを持つ、に改めた(code は `invalid-option` のまま)
|
|
49
|
+
- README: 繰り返しの規則の節に祝日の規則を、`rulePath` の説明を、合成の節に祝日の提供元の和集合の意味(同じ提供元は 1 つ、包んだ提供元は別物、同じ日付は先のカレンダーの提供元)を足した。`dataVersion`・`confirmedThrough` の案内を提供元の項目に改めた。サイズの値を測り直した(ルート約 8.8KB・日本の祝日込み約 10.6KB・英国の祝日込み約 10.5KB。0.12.0 で測定)
|
|
50
|
+
- `docs/decisions.md`: 4 番に「知っているキーの無効な組み合わせは例外にする」境界を、34 番に和集合の意味を足した。35 番(祝日の規則)・36 番(`rulePath`)・37 番(定数の一本化と性能の検証)を追加した
|
|
51
|
+
|
|
52
|
+
### 開発
|
|
53
|
+
|
|
54
|
+
- CI に `bench` ジョブを追加した。公開済みの前の版(npm の `latest`)と今回の `dist` を同じランナーで `scripts/bench.ts` にかけ、`scripts/bench-compare.ts` で比率を比べる。どれかの計測が 2 倍より遅ければ失敗し、絶対値は Job Summary に残す(`pnpm check` には含めない)。`scripts/bench.ts` に `--package <dir>`・`--json <file>` を足した
|
|
55
|
+
- サイズの上限(`size-limits.json`)を上げた。ルート 19,500 → 20,500、`./jp` 22,500 → 23,500、`./diagnose` 26,000 → 27,000(gzip)。祝日の規則の検証・判定と `rulePath`(ルートで約 0.5KB)、診断の祝日の規則への対応と候補の改善(約 0.7KB)のため
|
|
56
|
+
- 性質のテストに祝日の規則を加えた。規則を取り除いた結果との比較では、位置がずれる `rulePath` を除いて比べる
|
|
57
|
+
- ライブラリ内のテストとスクリプトで、非推奨にした定数 `confirmedThrough` の代わりに提供元の項目を使うようにした
|
|
58
|
+
|
|
7
59
|
## [0.11.1] - 2026-10-19
|
|
8
60
|
|
|
9
61
|
ライブラリのコードは 0.11.0 と同じ。
|
|
@@ -300,7 +352,9 @@
|
|
|
300
352
|
|
|
301
353
|
- 日本: 2026 年 2 月の官報(2027 年の春分・秋分の日)まで反映(`dataVersion: "2026-02"`、`confirmedThrough: 2027`)
|
|
302
354
|
|
|
303
|
-
[Unreleased]: https://github.com/inohan77/bizdays-toolkit/compare/v0.
|
|
355
|
+
[Unreleased]: https://github.com/inohan77/bizdays-toolkit/compare/v0.13.0...HEAD
|
|
356
|
+
[0.13.0]: https://github.com/inohan77/bizdays-toolkit/compare/v0.12.0...v0.13.0
|
|
357
|
+
[0.12.0]: https://github.com/inohan77/bizdays-toolkit/compare/v0.11.1...v0.12.0
|
|
304
358
|
[0.11.1]: https://github.com/inohan77/bizdays-toolkit/compare/v0.11.0...v0.11.1
|
|
305
359
|
[0.11.0]: https://github.com/inohan77/bizdays-toolkit/compare/v0.10.0...v0.11.0
|
|
306
360
|
[0.10.0]: https://github.com/inohan77/bizdays-toolkit/compare/v0.9.1...v0.10.0
|
package/README.md
CHANGED
|
@@ -25,9 +25,10 @@ A business-day calendar that combines public holidays with company or store clos
|
|
|
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
|
+
- **A calendar from a schedule**: `createCalendarFromSchedule` turns a schedule such as store hours into a business-day calendar, so the hours are the only configuration and business-day arithmetic works on them
|
|
28
29
|
- **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
|
|
29
30
|
- **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
|
|
30
|
-
- **Lightweight, zero dependencies**: about
|
|
31
|
+
- **Lightweight, zero dependencies**: about 9.0 KB min+gzip (about 10.8 KB with Japanese holidays, about 10.7 KB with UK bank holidays; measured for 0.13.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
|
|
31
32
|
- **Extensible to other countries and regions**: holiday providers are pluggable through the `HolidayProvider` interface. Japan and the UK are bundled
|
|
32
33
|
|
|
33
34
|
## Installation
|
|
@@ -38,7 +39,7 @@ pnpm add bizdays-toolkit
|
|
|
38
39
|
|
|
39
40
|
Node.js 22.12 or later. CI tests the package on Node.js 22.12, 22, 24 and 26, on Chrome, Firefox and Edge (Linux) and Safari (macOS), and on Cloudflare Workers (workerd, the open-source Workers runtime). The browsers are the latest stable versions on GitHub's runners, so their versions change as the runners are updated. The package is ESM only, but it can also be loaded from CommonJS with `require()` (tested in CI as well; on Node.js 22.12, `require()` prints an experimental-feature warning, which does not affect the result, and Node.js 26 prints none). Other runtimes (such as Deno, Bun or Vercel's Edge Runtime) are not tested.
|
|
40
41
|
|
|
41
|
-
Types are bundled. CI type-checks a consumer project against the published type definitions with `moduleResolution` set to `node16`, `nodenext` and `bundler
|
|
42
|
+
Types are bundled. CI type-checks a consumer project against the published type definitions with TypeScript 5.0, 5.5, 5.8, 6.0 and 7.0 (the native compiler), each with `moduleResolution` set to `node16`, `nodenext` and `bundler`, and confirms that an intentional type error is reported by each.
|
|
42
43
|
|
|
43
44
|
## Usage
|
|
44
45
|
|
|
@@ -114,7 +115,7 @@ shipping.businessDaysBetween("2026-09-01", "2026-10-01"); // business days from
|
|
|
114
115
|
shipping.businessDayOnOrAfter(new Date(), { cutoff: "14:00" });
|
|
115
116
|
|
|
116
117
|
shipping.dayInfo("2027-01-01");
|
|
117
|
-
// { date: "2027-01-01", weekday: 5, businessDay: false, reason: "closed-annual", name: "New Year closure", holiday: {…} }
|
|
118
|
+
// { date: "2027-01-01", weekday: 5, businessDay: false, reason: "closed-annual", name: "New Year closure", holiday: {…}, rulePath: "closedAnnual[0]" }
|
|
118
119
|
shipping.daysBetween("2026-12-30", "2027-01-04").map((d) => d.businessDay); // [true, false, false, false, false, true]
|
|
119
120
|
```
|
|
120
121
|
|
|
@@ -127,7 +128,7 @@ Each day is decided in this order of priority (top first).
|
|
|
127
128
|
1. `openDates` (specific open days)
|
|
128
129
|
2. `closedDates` (specific closed days)
|
|
129
130
|
3. `closedAnnual` (annual closures)
|
|
130
|
-
4. `openRecurring` and `closedRecurring` rules with `onHolidayRule: "over-holiday"` (applied even on holidays)
|
|
131
|
+
4. `openRecurring` and `closedRecurring` rules with `onHolidayRule: "over-holiday"` (applied even on holidays), and holiday rules (`{ holiday: true }`)
|
|
131
132
|
5. Holidays (according to `holidayPolicy`; `"open"` makes them business days even on closed weekdays)
|
|
132
133
|
6. `openRecurring` and `closedRecurring` (the default `"under-holiday"`, and the days moved to by `"after-holiday"`)
|
|
133
134
|
7. `closedWeekdays` (closed weekdays)
|
|
@@ -135,9 +136,9 @@ Each day is decided in this order of priority (top first).
|
|
|
135
136
|
|
|
136
137
|
This order is fixed. Only the relation between a recurring rule and holidays can be chosen per rule with `onHolidayRule` (next section).
|
|
137
138
|
|
|
138
|
-
### Recurring rules (weekly, every other week, nth weekday, day of month, day after a holiday)
|
|
139
|
+
### Recurring rules (weekly, every other week, nth weekday, day of month, holidays, day after a holiday)
|
|
139
140
|
|
|
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`.
|
|
141
|
+
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)", "open on substitute holidays" or "closed on the day after a holiday" go in `closedRecurring` and `openRecurring`.
|
|
141
142
|
|
|
142
143
|
```ts
|
|
143
144
|
const museum = createCalendar({
|
|
@@ -159,7 +160,7 @@ const museum = createCalendar({
|
|
|
159
160
|
museum.isBusinessDay("2026-10-12"); // true (open on Monday, Sports Day)
|
|
160
161
|
museum.dayInfo("2026-10-13");
|
|
161
162
|
// { date: "2026-10-13", weekday: 2, businessDay: false, reason: "closed-recurring",
|
|
162
|
-
// name: "Closed on Mondays", shiftedFrom: "2026-10-12" }
|
|
163
|
+
// name: "Closed on Mondays", shiftedFrom: "2026-10-12", rulePath: "closedRecurring[0]" }
|
|
163
164
|
museum.dayInfo("2026-02-12").name; // "Closed on the day after a holiday" (National Foundation Day is February 11)
|
|
164
165
|
museum.isBusinessDay("2026-05-07"); // false (the day after the May 3–6 run of holidays)
|
|
165
166
|
```
|
|
@@ -170,6 +171,7 @@ museum.isBusinessDay("2026-05-07"); // false (the day after the May 3–6 run of
|
|
|
170
171
|
| `{ weekday: 3, nth: 3 }` | The 3rd Wednesday. `nth` is 1–5 or -1 (last); an array such as `[2, 4]` gives several |
|
|
171
172
|
| `{ 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
173
|
| `{ day: 20 }` | The 20th of every month. `day` is 1–31 or -1 (last day); an array gives several |
|
|
174
|
+
| `{ holiday: true, kinds: ["substitute"] }` | Holidays, optionally narrowed to the given kinds (`Holiday.kind`); judged before `holidayPolicy` (see below) |
|
|
173
175
|
| `{ 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
176
|
| `months: [8, 12]` | Narrows any pattern to these months |
|
|
175
177
|
| `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) |
|
|
@@ -177,6 +179,7 @@ museum.isBusinessDay("2026-05-07"); // false (the day after the May 3–6 run of
|
|
|
177
179
|
- **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
|
|
178
180
|
- **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
|
|
179
181
|
- **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`
|
|
182
|
+
- **Holiday rules** (`{ holiday: true }`) write exceptions to the default holiday handling: "open on holidays from April 2027" is `openRecurring: [{ holiday: true, effectiveFrom: "2027-04-01" }]` with `holidayPolicy: "closed"` kept for the past, "open on substitute holidays and citizens' holidays" is `openRecurring: [{ holiday: true, kinds: ["substitute", "citizens"] }]`, and "special hours on the May holidays" in a schedule is `recurring: [{ holiday: true, months: [5], value: … }]`. `kinds` narrows to those `Holiday.kind` values (see the provider's list), `months` to those months; omit both for all holidays. The rule is judged at the "over the holiday" step, before `holidayPolicy` or a schedule's `holiday`, and also with `holidayPolicy: "ignore"`. Its days are holidays, so `onHolidayRule` cannot be set on it (`invalid-option`); `holiday` still shows the holiday
|
|
180
183
|
- **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
184
|
- 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
185
|
|
|
@@ -214,6 +217,7 @@ shop.isBusinessDay("2027-08-13"); // true (the summer closure ended in 2026)
|
|
|
214
217
|
- With `holidayPolicy: "ignore"` (holidays not used), `"under-holiday"` rules also apply on holidays
|
|
215
218
|
- The day an `"after-holiday"` rule moves to is the first non-holiday on or after that day. After a run of holidays (such as Golden Week) it is the day after the run. Specific open or closed days and annual closures on that day take priority. `dayInfo()` for a moved day includes the original date as `shiftedFrom`
|
|
216
219
|
- The "holidays" used for moving are only those of the holiday provider (such as `jp`), not annual closures
|
|
220
|
+
- `dayInfo()` for a day decided by `openDates`, `closedDates`, `closedAnnual`, `openRecurring` or `closedRecurring` (including the day a rule moved to) includes `rulePath`, the position of that rule in the options in the same form as the diagnostics' `path` (such as `"closedRecurring[0]"`), so rules with the same `name` can be told apart. Days decided by holidays or `closedWeekdays` have no `rulePath`
|
|
217
221
|
|
|
218
222
|
In a schedule, the same patterns with a `value` go in `recurring`.
|
|
219
223
|
|
|
@@ -251,15 +255,15 @@ const bank = createCalendar({
|
|
|
251
255
|
const remittance = combineCalendars([company, bank], { businessDay: "all" });
|
|
252
256
|
remittance.addBusinessDays("2026-12-28", 2); // "2027-01-05"
|
|
253
257
|
remittance.dayInfo("2026-12-29");
|
|
254
|
-
// { date: "2026-12-29", weekday: 2, businessDay: false, reason: "closed-annual", name: "New Year closure", decidedBy: 0 }
|
|
258
|
+
// { date: "2026-12-29", weekday: 2, businessDay: false, reason: "closed-annual", name: "New Year closure", rulePath: "closedAnnual[0]", decidedBy: 0 }
|
|
255
259
|
|
|
256
260
|
// Any of several stores is open
|
|
257
261
|
const anyStore = combineCalendars([company, bank], { businessDay: "any" });
|
|
258
262
|
anyStore.isBusinessDay("2026-12-29"); // true
|
|
259
263
|
```
|
|
260
264
|
|
|
261
|
-
- `dayInfo` reports `reason`, `name` and `
|
|
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
|
|
265
|
+
- `dayInfo` reports `reason`, `name`, `shiftedFrom` and `rulePath` from the first calendar whose result equals the combined result, and the index of that calendar in `decidedBy` (`rulePath` is a position in that calendar's options). 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
|
|
266
|
+
- `holiday`, `isHoliday`, `holidaysInYear` and so on use the union of the calendars' holiday providers: the same provider object counts once, a wrapped provider (such as `jp` with some kinds filtered out) counts as a different one, and when two providers have a holiday on the same date, the provider of the earlier calendar decides the holiday shown. Combining a calendar with a wrapped `jp` and one with `jp` itself therefore keeps the business-day results but still shows the filtered-out holidays in `holiday`. `outOfRange` is `"throw"` if any calendar throws outside its supported years
|
|
263
267
|
- 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
268
|
- A combined calendar can be combined again. Option diagnostics work on options, not on calendars, so diagnose each calendar's options separately
|
|
265
269
|
|
|
@@ -418,7 +422,7 @@ const storeHours = createSchedule<Hours>({
|
|
|
418
422
|
|
|
419
423
|
storeHours.valueOn("2026-11-03"); // { open: "10:00", close: "19:00" } (Culture Day uses Sunday hours)
|
|
420
424
|
storeHours.dayInfo("2027-01-01");
|
|
421
|
-
// { date: "2027-01-01", weekday: 5, value: null, reason: "annual", name: "Closed on New Year's Day", holiday: {…} }
|
|
425
|
+
// { date: "2027-01-01", weekday: 5, value: null, reason: "annual", name: "Closed on New Year's Day", holiday: {…}, rulePath: "annual[0]" }
|
|
422
426
|
|
|
423
427
|
// Holidays in a period with their hours: filter daysBetween by holiday
|
|
424
428
|
storeHours
|
|
@@ -433,7 +437,7 @@ storeHours
|
|
|
433
437
|
.map((d) => d.date); // ["2027-01-11"]
|
|
434
438
|
```
|
|
435
439
|
|
|
436
|
-
`reason` is the rule that decided the value. With `holiday: "weekday"` (holidays follow the weekday rule), a holiday has `reason: "weekday"`, so check `holiday` to know whether a day is a holiday. Filtering by `holiday` alone also keeps holidays whose value is set by `dates` or `annual` (such as special hours on New Year's Day). To list only holidays that follow the holiday handling, also exclude `reason` `"date"` and `"annual"`, as in the last example: `holiday` tells whether the day is a holiday, and `reason` tells which rule decided the value.
|
|
440
|
+
`reason` is the rule that decided the value, and `rulePath` (for `dates`, `annual` and `recurring`) its position in the options. With `holiday: "weekday"` (holidays follow the weekday rule), a holiday has `reason: "weekday"`, so check `holiday` to know whether a day is a holiday. Filtering by `holiday` alone also keeps holidays whose value is set by `dates` or `annual` (such as special hours on New Year's Day). To list only holidays that follow the holiday handling, also exclude `reason` `"date"` and `"annual"`, as in the last example: `holiday` tells whether the day is a holiday, and `reason` tells which rule decided the value.
|
|
437
441
|
|
|
438
442
|
Values are decided in the order `dates` (specific dates and ranges) → `annual` (annual dates and ranges) → `recurring` (`"over-holiday"`) → holidays → `recurring` (default and moved days) → `weekly` (weekday). When rules of the same type overlap, the one written first in the array wins.
|
|
439
443
|
|
|
@@ -447,6 +451,33 @@ const isOpen = hours !== null && hours.open <= time && time < hours.close;
|
|
|
447
451
|
|
|
448
452
|
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.)
|
|
449
453
|
|
|
454
|
+
### A business-day calendar from a schedule
|
|
455
|
+
|
|
456
|
+
When the schedule (store hours, say) is the real configuration, `createCalendarFromSchedule(schedule, isOpen)` makes a business-day calendar from it, so you do not write a second set of rules to keep in sync. `isOpen` decides from the value alone whether the day is a business day; the result is an ordinary calendar with business-day arithmetic, `adjust`, `dueDate` and the rest.
|
|
457
|
+
|
|
458
|
+
```ts
|
|
459
|
+
import { createCalendarFromSchedule } from "bizdays-toolkit";
|
|
460
|
+
|
|
461
|
+
const hours = createSchedule<Hours>({
|
|
462
|
+
holidays: jp,
|
|
463
|
+
weekly: [null, weekday, weekday, weekday, weekday, weekday, { open: "10:00", close: "17:00" }],
|
|
464
|
+
holiday: { sameAs: 0 },
|
|
465
|
+
annual: [
|
|
466
|
+
{ from: "12-31", to: "01-03", value: null, name: "New Year closure" },
|
|
467
|
+
{ date: "01-04", value: { open: "11:00", close: "15:00" }, name: "First day of the year" },
|
|
468
|
+
],
|
|
469
|
+
});
|
|
470
|
+
const calendar = createCalendarFromSchedule(hours, (v) => v !== null);
|
|
471
|
+
calendar.addBusinessDays("2026-12-29", 2); // "2027-01-04"
|
|
472
|
+
calendar.dayInfo("2027-01-04");
|
|
473
|
+
// { date: "2027-01-04", weekday: 1, businessDay: true, reason: "open-annual", name: "First day of the year", rulePath: "annual[1]" }
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
- `isOpen` must be a pure function of the value: it receives only the value (not the date), and the calendar calls it repeatedly while searching for business days, so a function that looks at the date, the time or outside state would make the result change from call to call. Conditions that depend on the date belong in the schedule's rules (`dates`, `recurring`)
|
|
477
|
+
- `dayInfo` maps the schedule's `reason` to the calendar's: `date` → `"open-date"`/`"closed-date"`, `annual` → `"open-annual"`/`"closed-annual"`, `recurring` → `"open-recurring"`/`"closed-recurring"`, `holiday` → `"holiday"` (whether or not the holiday is open, as with `holidayPolicy: "open"`), `weekday` → `"regular"`/`"closed-weekday"`. `"open-annual"` exists only for calendars made this way, since `closedAnnual` of `createCalendar` only closes. `name`, `holiday`, `shiftedFrom` and `rulePath` are the schedule's
|
|
478
|
+
- Holidays, the time zone and `outOfRange` are the schedule's. Diagnose the schedule's options with `diagnoseSchedule`; the calendar itself is not diagnosed. `maxSearchDays` (default 3660) can be given in the options
|
|
479
|
+
- Use `createCalendar` when you only need open or closed; use this when you have a schedule anyway
|
|
480
|
+
|
|
450
481
|
### Date and date-time inputs
|
|
451
482
|
|
|
452
483
|
Every method accepts the following inputs. Returned dates are always `"YYYY-MM-DD"` strings.
|
|
@@ -594,7 +625,7 @@ Versioning follows [Semantic Versioning](https://semver.org/). See [RELEASING.md
|
|
|
594
625
|
|
|
595
626
|
- Official data updates (new, moved and one-off holidays by law, Official Gazette or proclamation, fixed equinox days in Japan, newly published years on GOV.UK) are released as **patch** versions, so they reach you with either `^` or `~`
|
|
596
627
|
- To freeze the holiday results, pin an exact version (for example `"bizdays-toolkit": "0.8.0"`)
|
|
597
|
-
- The reflected data is shown by `dataVersion` (for Japan, the year and month of the Official Gazette reflected; for the UK, the year and month of the GOV.UK data reflected) and `confirmedThrough`
|
|
628
|
+
- The reflected data is shown by the provider's `dataVersion` (for Japan, the year and month of the Official Gazette reflected; for the UK, the year and month of the GOV.UK data reflected) and `confirmedThrough` (`jp.dataVersion`, `england.confirmedThrough` and so on). The module-level constants `dataVersion` and `confirmedThrough` in `bizdays-toolkit/jp` and `bizdays-toolkit/uk` are deprecated since 0.12.0 and will be removed in 1.0.0
|
|
598
629
|
- Before 1.0.0, minor versions may contain breaking changes. What you need to change when moving between minor versions is listed in the [migration guide](https://github.com/inohan77/bizdays-toolkit/blob/main/docs/migration.md) (Japanese) and in the "移行" (migration) part of each version in the CHANGELOG
|
|
599
630
|
- Decision reasons (`reason`), holiday kinds (`kind`), error codes and diagnostic codes may gain values in future versions. When you branch on their values, handle "anything else" too (see "互換性の約束", the compatibility promise, in [RELEASING.md](https://github.com/inohan77/bizdays-toolkit/blob/main/RELEASING.md))
|
|
600
631
|
|
package/dist/core/calendar.d.ts
CHANGED
|
@@ -22,7 +22,7 @@ import { type DateString, type Weekday } from "./date.ts";
|
|
|
22
22
|
import { IssueCollector } from "./errors.ts";
|
|
23
23
|
import { type DateInput } from "./input.ts";
|
|
24
24
|
import { type MonthString } from "./month.ts";
|
|
25
|
-
import { type CompiledSchedule, type DayOfMonth, type EffectivePeriod, type RecurringRule, type HolidayLookup, type HolidaySourceOptions } from "./schedule.ts";
|
|
25
|
+
import { type CompiledSchedule, type DayOfMonth, type EffectivePeriod, type RecurringRule, type HolidayLookup, type HolidaySourceOptions, type Schedule } from "./schedule.ts";
|
|
26
26
|
import type { Holiday } from "./types.ts";
|
|
27
27
|
/**
|
|
28
28
|
* 祝日の扱い。
|
|
@@ -93,7 +93,7 @@ export interface CalendarOptions<Kind extends string = string> extends HolidaySo
|
|
|
93
93
|
* 営業・休業を決めた規則(既知の値)。
|
|
94
94
|
* @public
|
|
95
95
|
*/
|
|
96
|
-
export type KnownDayReason = "open-date" | "closed-date" | "closed-annual" | "open-recurring" | "closed-recurring" | "holiday" | "closed-weekday" | "regular";
|
|
96
|
+
export type KnownDayReason = "open-date" | "closed-date" | "open-annual" | "closed-annual" | "open-recurring" | "closed-recurring" | "holiday" | "closed-weekday" | "regular";
|
|
97
97
|
/**
|
|
98
98
|
* 営業・休業を決めた規則。将来の版で値が増えることがあるので、分岐では「その他」も扱うこと。
|
|
99
99
|
* すべての値を網羅したことを型で確かめたい場合は KnownDayReason を使う。
|
|
@@ -116,9 +116,14 @@ export interface DayInfo<Kind extends string = string> {
|
|
|
116
116
|
readonly holiday?: Holiday<Kind>;
|
|
117
117
|
/** 祝日から振り替えられた繰り返しの規則で決まった場合、振替の元の日付(onHolidayRule: "after-holiday") */
|
|
118
118
|
readonly shiftedFrom?: DateString;
|
|
119
|
+
/**
|
|
120
|
+
* 判定を決めた規則の設定の中の位置(規則の診断の path と同じ形。例: "closedRecurring[0]")。
|
|
121
|
+
* 個別の日付・毎年の休業日・繰り返しの規則(振替先を含む)で決まった日にだけ付き、祝日・定休日の曜日で決まった日には付かない
|
|
122
|
+
*/
|
|
123
|
+
readonly rulePath?: string;
|
|
119
124
|
/**
|
|
120
125
|
* 合成したカレンダー(combineCalendars)で、判定を決めたカレンダーの番号(渡した配列の位置)。
|
|
121
|
-
* reason・name・shiftedFrom はそのカレンダーの結果。合成していないカレンダーでは付かない
|
|
126
|
+
* reason・name・shiftedFrom・rulePath はそのカレンダーの結果。合成していないカレンダーでは付かない
|
|
122
127
|
*/
|
|
123
128
|
readonly decidedBy?: number;
|
|
124
129
|
}
|
|
@@ -241,7 +246,7 @@ export declare function createCalendar<Kind extends string = string>(options?: C
|
|
|
241
246
|
* 作成済みの営業日カレンダーを合成する。"all" はすべてのカレンダーが営業している日、"any" はいずれかが
|
|
242
247
|
* 営業している日を営業日とする。返すのは通常の営業日カレンダーで、営業日の加減算・調整・期日などが合成結果で動く。
|
|
243
248
|
*
|
|
244
|
-
* - dayInfo の reason・name・shiftedFrom は、合成結果と同じ判定をした最初のカレンダーの結果で、
|
|
249
|
+
* - dayInfo の reason・name・shiftedFrom・rulePath は、合成結果と同じ判定をした最初のカレンダーの結果で、
|
|
245
250
|
* その番号を decidedBy で返す。holiday・isHoliday などは、すべてのカレンダーの祝日の提供元の和集合で判定する
|
|
246
251
|
* - タイムゾーンはすべてのカレンダーで同じでなければならない(time-zone-conflict)。
|
|
247
252
|
* 2 つ未満、同じカレンダーを 2 回渡した場合は invalid-argument
|
|
@@ -250,4 +255,21 @@ export declare function createCalendar<Kind extends string = string>(options?: C
|
|
|
250
255
|
* @public
|
|
251
256
|
*/
|
|
252
257
|
export declare function combineCalendars<Kind extends string = string>(calendars: readonly Calendar<Kind>[], options: CombineOptions): Calendar<Kind>;
|
|
258
|
+
/**
|
|
259
|
+
* スケジュール(createSchedule)から営業日カレンダーを作る。isOpen が true を返す値の日を営業日とする。
|
|
260
|
+
* 営業時間表のような日ごとの値が設定の正本で、営業日の計算(加減算・調整・期日など)も同じ設定から行いたいときに使う
|
|
261
|
+
* (営業日カレンダーを別に書いて同期を取る必要がなくなる)。
|
|
262
|
+
*
|
|
263
|
+
* - isOpen は値だけで決まる純粋な関数であること(日付・時刻・外部の状態を見ない)。営業日の探索は isOpen を繰り返し呼ぶので、
|
|
264
|
+
* 結果が変わる関数を渡すと探索の結果が呼ぶたびに変わる。日付で変えたい条件は、スケジュールの規則(dates・recurring)に書く
|
|
265
|
+
* - dayInfo の reason は、スケジュールの reason と営業・休業の組み合わせ(date → "open-date"/"closed-date"、annual → "open-annual"/
|
|
266
|
+
* "closed-annual"、recurring → "open-recurring"/"closed-recurring"、holiday → "holiday"、weekday → "regular"/"closed-weekday")。
|
|
267
|
+
* name・holiday・shiftedFrom・rulePath はスケジュールの結果をそのまま引き継ぐ
|
|
268
|
+
* - 祝日の判定(holiday・isHoliday など)・タイムゾーン・outOfRange はスケジュールのもの。規則の診断(diagnoseSchedule)は
|
|
269
|
+
* スケジュールの設定に対して行う
|
|
270
|
+
* @public
|
|
271
|
+
*/
|
|
272
|
+
export declare function createCalendarFromSchedule<T, Kind extends string = string>(schedule: Schedule<T, Kind>, isOpen: (value: T) => boolean, options?: {
|
|
273
|
+
readonly maxSearchDays?: number;
|
|
274
|
+
}): Calendar<Kind>;
|
|
253
275
|
export {};
|
package/dist/core/calendar.js
CHANGED
|
@@ -22,7 +22,7 @@ import { addDays, daysInMonth, formatDate, fromDayNumber, toDayNumber, } from ".
|
|
|
22
22
|
import { checkUnknownKeys, IssueCollector, plainError, rangeError } from "./errors.js";
|
|
23
23
|
import { parseTimeOfDay, toCivil } from "./input.js";
|
|
24
24
|
import { dayOfMonth, isDayOfMonthValue, parseMonth } from "./month.js";
|
|
25
|
-
import { buildSchedule, compileAnnualRules, compileDateRules, compileHolidaySource, compileRecurringRules, createHolidayLookup, isWeekday, mapDays, RECURRING_RULE_KEYS, } from "./schedule.js";
|
|
25
|
+
import { buildSchedule, compileAnnualRules, compileDateRules, compileHolidaySource, compileRecurringRules, createHolidayLookup, infoOfSchedule, isWeekday, mapDays, RECURRING_RULE_KEYS, } from "./schedule.js";
|
|
26
26
|
const CALENDAR_OPTION_KEYS = [
|
|
27
27
|
"holidays",
|
|
28
28
|
"timeZone",
|
|
@@ -100,52 +100,62 @@ export function createCalendar(options = {}) {
|
|
|
100
100
|
c.throwFirstError();
|
|
101
101
|
const { maxSearchDays } = compiled;
|
|
102
102
|
const schedule = buildSchedule(compiled.schedule);
|
|
103
|
-
|
|
103
|
+
const info = (date) => {
|
|
104
104
|
const day = schedule.dayInfo(date);
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
105
|
+
return dayInfoFromScheduleDay(day, day.value);
|
|
106
|
+
};
|
|
107
|
+
return buildCalendar(schedule, (date) => schedule.valueOn(date), info, maxSearchDays);
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* @internal スケジュールの 1 日分の結果(値は営業日か)を、営業日カレンダーの結果に変換する。
|
|
111
|
+
* reason はスケジュールの reason と営業・休業の組み合わせで決まる。createCalendar の closedAnnual は休業だけなので
|
|
112
|
+
* "open-annual" にはならないが、createCalendarFromSchedule では annual の値で営業になる日がある
|
|
113
|
+
*/
|
|
114
|
+
function dayInfoFromScheduleDay(day, open) {
|
|
115
|
+
let reason;
|
|
116
|
+
// 内部では閉じた型(KnownScheduleReason)で分岐し、値が増えたときの対応漏れを型で検出する
|
|
117
|
+
const known = day.reason;
|
|
118
|
+
switch (known) {
|
|
119
|
+
case "date":
|
|
120
|
+
reason = open ? "open-date" : "closed-date";
|
|
121
|
+
break;
|
|
122
|
+
case "annual":
|
|
123
|
+
reason = open ? "open-annual" : "closed-annual";
|
|
124
|
+
break;
|
|
125
|
+
case "recurring":
|
|
126
|
+
reason = open ? "open-recurring" : "closed-recurring";
|
|
127
|
+
break;
|
|
128
|
+
case "holiday":
|
|
129
|
+
reason = "holiday";
|
|
130
|
+
break;
|
|
131
|
+
case "weekday":
|
|
132
|
+
reason = open ? "regular" : "closed-weekday";
|
|
133
|
+
break;
|
|
134
|
+
default: {
|
|
135
|
+
const unreachable = known;
|
|
136
|
+
throw plainError("invalid-option", `Unexpected schedule reason: ${String(unreachable)}`);
|
|
128
137
|
}
|
|
129
|
-
const result = {
|
|
130
|
-
date: day.date,
|
|
131
|
-
weekday: day.weekday,
|
|
132
|
-
businessDay: day.value,
|
|
133
|
-
reason,
|
|
134
|
-
};
|
|
135
|
-
// 曜日で決まった日には名前を付けない(祝日名は holiday で分かる)
|
|
136
|
-
if (day.name !== undefined && day.reason !== "weekday")
|
|
137
|
-
result.name = day.name;
|
|
138
|
-
if (day.holiday !== undefined)
|
|
139
|
-
result.holiday = day.holiday;
|
|
140
|
-
if (day.shiftedFrom !== undefined)
|
|
141
|
-
result.shiftedFrom = day.shiftedFrom;
|
|
142
|
-
return Object.freeze(result);
|
|
143
138
|
}
|
|
144
|
-
|
|
139
|
+
const result = {
|
|
140
|
+
date: day.date,
|
|
141
|
+
weekday: day.weekday,
|
|
142
|
+
businessDay: open,
|
|
143
|
+
reason,
|
|
144
|
+
};
|
|
145
|
+
// 曜日で決まった日には名前を付けない(祝日名は holiday で分かる)
|
|
146
|
+
if (day.name !== undefined && day.reason !== "weekday")
|
|
147
|
+
result.name = day.name;
|
|
148
|
+
if (day.holiday !== undefined)
|
|
149
|
+
result.holiday = day.holiday;
|
|
150
|
+
if (day.shiftedFrom !== undefined)
|
|
151
|
+
result.shiftedFrom = day.shiftedFrom;
|
|
152
|
+
if (day.rulePath !== undefined)
|
|
153
|
+
result.rulePath = day.rulePath;
|
|
154
|
+
return Object.freeze(result);
|
|
145
155
|
}
|
|
146
156
|
/**
|
|
147
157
|
* @internal 祝日を調べる機能と 1 日分の判定から、営業日カレンダーの機能(探索・調整・月の営業日・期日)を組み立てる。
|
|
148
|
-
* createCalendar
|
|
158
|
+
* createCalendar・combineCalendars・createCalendarFromSchedule で共用する
|
|
149
159
|
*/
|
|
150
160
|
function buildCalendar(lookup, isOpen, info, maxSearchDays) {
|
|
151
161
|
const dateOf = lookup.dateOf;
|
|
@@ -314,7 +324,7 @@ const isCalendarLike = (v) => typeof v === "object" &&
|
|
|
314
324
|
* 作成済みの営業日カレンダーを合成する。"all" はすべてのカレンダーが営業している日、"any" はいずれかが
|
|
315
325
|
* 営業している日を営業日とする。返すのは通常の営業日カレンダーで、営業日の加減算・調整・期日などが合成結果で動く。
|
|
316
326
|
*
|
|
317
|
-
* - dayInfo の reason・name・shiftedFrom は、合成結果と同じ判定をした最初のカレンダーの結果で、
|
|
327
|
+
* - dayInfo の reason・name・shiftedFrom・rulePath は、合成結果と同じ判定をした最初のカレンダーの結果で、
|
|
318
328
|
* その番号を decidedBy で返す。holiday・isHoliday などは、すべてのカレンダーの祝日の提供元の和集合で判定する
|
|
319
329
|
* - タイムゾーンはすべてのカレンダーで同じでなければならない(time-zone-conflict)。
|
|
320
330
|
* 2 つ未満、同じカレンダーを 2 回渡した場合は invalid-argument
|
|
@@ -377,8 +387,43 @@ export function combineCalendars(calendars, options) {
|
|
|
377
387
|
result.holiday = holiday;
|
|
378
388
|
if (base.shiftedFrom !== undefined)
|
|
379
389
|
result.shiftedFrom = base.shiftedFrom;
|
|
390
|
+
if (base.rulePath !== undefined)
|
|
391
|
+
result.rulePath = base.rulePath;
|
|
380
392
|
result.decidedBy = decidedBy;
|
|
381
393
|
return Object.freeze(result);
|
|
382
394
|
}
|
|
383
395
|
return buildCalendar(lookup, isOpen, info, maxSearchDays);
|
|
384
396
|
}
|
|
397
|
+
/**
|
|
398
|
+
* スケジュール(createSchedule)から営業日カレンダーを作る。isOpen が true を返す値の日を営業日とする。
|
|
399
|
+
* 営業時間表のような日ごとの値が設定の正本で、営業日の計算(加減算・調整・期日など)も同じ設定から行いたいときに使う
|
|
400
|
+
* (営業日カレンダーを別に書いて同期を取る必要がなくなる)。
|
|
401
|
+
*
|
|
402
|
+
* - isOpen は値だけで決まる純粋な関数であること(日付・時刻・外部の状態を見ない)。営業日の探索は isOpen を繰り返し呼ぶので、
|
|
403
|
+
* 結果が変わる関数を渡すと探索の結果が呼ぶたびに変わる。日付で変えたい条件は、スケジュールの規則(dates・recurring)に書く
|
|
404
|
+
* - dayInfo の reason は、スケジュールの reason と営業・休業の組み合わせ(date → "open-date"/"closed-date"、annual → "open-annual"/
|
|
405
|
+
* "closed-annual"、recurring → "open-recurring"/"closed-recurring"、holiday → "holiday"、weekday → "regular"/"closed-weekday")。
|
|
406
|
+
* name・holiday・shiftedFrom・rulePath はスケジュールの結果をそのまま引き継ぐ
|
|
407
|
+
* - 祝日の判定(holiday・isHoliday など)・タイムゾーン・outOfRange はスケジュールのもの。規則の診断(diagnoseSchedule)は
|
|
408
|
+
* スケジュールの設定に対して行う
|
|
409
|
+
* @public
|
|
410
|
+
*/
|
|
411
|
+
export function createCalendarFromSchedule(schedule, isOpen, options) {
|
|
412
|
+
const info = infoOfSchedule(schedule);
|
|
413
|
+
if (info === undefined) {
|
|
414
|
+
throw rangeError("invalid-argument", "schedule must be a schedule created by createSchedule");
|
|
415
|
+
}
|
|
416
|
+
if (typeof isOpen !== "function") {
|
|
417
|
+
throw rangeError("invalid-argument", "isOpen must be a function (value) => boolean");
|
|
418
|
+
}
|
|
419
|
+
const maxSearchDays = options?.maxSearchDays ?? 3660;
|
|
420
|
+
if (!Number.isInteger(maxSearchDays) || maxSearchDays < 1) {
|
|
421
|
+
throw rangeError("invalid-option", "maxSearchDays must be a positive integer");
|
|
422
|
+
}
|
|
423
|
+
const open = (date) => isOpen(info(date).value) === true;
|
|
424
|
+
return buildCalendar(schedule, open, (date) => {
|
|
425
|
+
const day = info(date);
|
|
426
|
+
// 変換は value を読まない(営業・休業は open で渡す)ので、boolean の値として扱ってよい
|
|
427
|
+
return dayInfoFromScheduleDay(day, isOpen(day.value) === true);
|
|
428
|
+
}, maxSearchDays);
|
|
429
|
+
}
|
package/dist/core/diagnose.js
CHANGED
|
@@ -51,6 +51,12 @@ function closestKey(key, known) {
|
|
|
51
51
|
const sameCase = known.find((k) => k.toLowerCase() === lower);
|
|
52
52
|
if (sameCase !== undefined)
|
|
53
53
|
return sameCase;
|
|
54
|
+
// 既知のキーの先頭部分("onHoliday" → "onHolidayRule")は、短い別のキー("holiday")との編集距離より優先する
|
|
55
|
+
if (key.length >= 4) {
|
|
56
|
+
const prefixOf = known.find((k) => k.toLowerCase().startsWith(lower));
|
|
57
|
+
if (prefixOf !== undefined)
|
|
58
|
+
return prefixOf;
|
|
59
|
+
}
|
|
54
60
|
const limit = Math.max(1, Math.floor(key.length / 4));
|
|
55
61
|
let best;
|
|
56
62
|
let bestDistance = limit + 1;
|
|
@@ -61,11 +67,7 @@ function closestKey(key, known) {
|
|
|
61
67
|
bestDistance = distance;
|
|
62
68
|
}
|
|
63
69
|
}
|
|
64
|
-
|
|
65
|
-
return best;
|
|
66
|
-
if (key.length >= 4)
|
|
67
|
-
return known.find((k) => k.toLowerCase().startsWith(lower));
|
|
68
|
-
return undefined;
|
|
70
|
+
return best;
|
|
69
71
|
}
|
|
70
72
|
// ------------------------------------------------------------
|
|
71
73
|
// 調べる期間
|
|
@@ -358,48 +360,54 @@ function analyze(c, compiled, isCalendar) {
|
|
|
358
360
|
});
|
|
359
361
|
// --- 繰り返しの規則: 当てはまる日がない・覆われている ---
|
|
360
362
|
const recurringBits = recurring.map(patternBits);
|
|
361
|
-
/**
|
|
362
|
-
const
|
|
363
|
+
/** 祝日・祝日の翌日の規則の判定に使う(提供元の対応年の外は祝日でないとみなす) */
|
|
364
|
+
const holidayOfSafe = (date) => {
|
|
363
365
|
try {
|
|
364
|
-
return lookup.holidayOn(date)
|
|
366
|
+
return lookup.holidayOn(date);
|
|
365
367
|
}
|
|
366
368
|
catch {
|
|
367
|
-
return
|
|
369
|
+
return undefined;
|
|
368
370
|
}
|
|
369
371
|
};
|
|
370
|
-
/**
|
|
371
|
-
const
|
|
372
|
+
/** 祝日に従属する規則(祝日・祝日の翌日)が、調べる期間と有効期間の中で 1 日でも当てはまるか(祝日は年ごとに違うので、全日を調べる) */
|
|
373
|
+
const holidayRuleMatches = (rule) => {
|
|
372
374
|
if (source.providers.length === 0 || !window)
|
|
373
375
|
return false;
|
|
374
376
|
let found = false;
|
|
375
|
-
eachDay(window, (n) => (found = inPeriod(rule, n) && matchPattern(rule, fromDayNumber(n),
|
|
377
|
+
eachDay(window, (n) => (found = inPeriod(rule, n) && matchPattern(rule, fromDayNumber(n), holidayOfSafe)));
|
|
376
378
|
return found;
|
|
377
379
|
};
|
|
380
|
+
/** 集合 a(null はすべて)が集合 b(null はすべて)を含むか */
|
|
381
|
+
const setCovers = (a, b) => a === null || (b !== null && [...b].every((v) => a.has(v)));
|
|
378
382
|
recurring.forEach((rule, j) => {
|
|
379
383
|
const bits = recurringBits[j];
|
|
380
|
-
const
|
|
381
|
-
|
|
382
|
-
: matchesAnyDay(rule, cycle);
|
|
384
|
+
const holidayBased = rule.holiday || rule.dayAfterHoliday;
|
|
385
|
+
const matches = holidayBased ? holidayRuleMatches(rule) : matchesAnyDay(rule, cycle);
|
|
383
386
|
if (!matches) {
|
|
384
387
|
unused.add(rule);
|
|
385
|
-
c.warning("never-matches", rule.path,
|
|
388
|
+
c.warning("never-matches", rule.path, holidayBased
|
|
386
389
|
? source.providers.length === 0
|
|
387
390
|
? "This rule matches no day: there are no holidays without a holiday provider"
|
|
388
|
-
:
|
|
391
|
+
: rule.holiday
|
|
392
|
+
? "This rule matches no day: no holiday of the selected kinds within the selected months and effective period"
|
|
393
|
+
: "This rule matches no day: no day after a holiday within its effective period"
|
|
389
394
|
: rule.effectiveFrom === null && rule.effectiveTo === null
|
|
390
395
|
? "This rule matches no day (for example, a day that does not exist in the selected months)"
|
|
391
396
|
: "This rule matches no day within its effective period");
|
|
392
397
|
return;
|
|
393
398
|
}
|
|
394
|
-
//
|
|
399
|
+
// 祝日・祝日の翌日の規則は祝日次第で年ごとに変わるので、形の包含は同じ形の規則どうし(種別・月の絞り込み)だけで調べる
|
|
395
400
|
const k = recurring.findIndex((r, i) => i < j &&
|
|
396
401
|
canCover(r, rule) &&
|
|
397
402
|
periodCovers(r, rule) &&
|
|
398
|
-
(rule.
|
|
399
|
-
? r.
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
+
(rule.holiday
|
|
404
|
+
? r.holiday && setCovers(r.kinds, rule.kinds) && setCovers(r.months, rule.months)
|
|
405
|
+
: rule.dayAfterHoliday
|
|
406
|
+
? r.dayAfterHoliday && setCovers(r.months, rule.months)
|
|
407
|
+
: !r.holiday &&
|
|
408
|
+
!r.dayAfterHoliday &&
|
|
409
|
+
intervalCovers(r, rule) &&
|
|
410
|
+
isSubset(bits, recurringBits[i])));
|
|
403
411
|
if (k < 0)
|
|
404
412
|
return;
|
|
405
413
|
unused.add(rule);
|
package/dist/core/schedule.d.ts
CHANGED
|
@@ -12,7 +12,8 @@
|
|
|
12
12
|
* "under-holiday" の規則は、祝日の扱いが "weekday"(曜日どおり)なら祝日にも当てはまる。
|
|
13
13
|
* "after-holiday" の規則は、祝日には当てはめず、その日以降で最初の祝日でない日に当てはめる(振替)。
|
|
14
14
|
* 毎年の月日・繰り返しの規則の有効期間(effectiveFrom・effectiveTo)の外では、その規則は書かれていないのと同じ。
|
|
15
|
-
* 祝日の翌日の規則(dayAfterHoliday
|
|
15
|
+
* 祝日の翌日の規則(dayAfterHoliday)は、前日が祝日でその日自身は祝日でない日に当てはまる(提供元の祝日だけで判定)。
|
|
16
|
+
* 祝日の規則(holiday: true。kinds で種別を絞れる)は、「祝日の上」の段階で判定する
|
|
16
17
|
*
|
|
17
18
|
* 営業日カレンダー(createCalendar)は、このスケジュールの真偽値版として作られている。
|
|
18
19
|
*/
|
|
@@ -89,6 +90,7 @@ export interface WeekdayPattern {
|
|
|
89
90
|
readonly months?: readonly Month[];
|
|
90
91
|
readonly day?: never;
|
|
91
92
|
readonly dayAfterHoliday?: never;
|
|
93
|
+
readonly holiday?: never;
|
|
92
94
|
}
|
|
93
95
|
/**
|
|
94
96
|
* 毎月の日の繰り返し(例: 毎月 20 日、毎月末日)。その月にない日(2 月 30 日など)は当てはまらない。
|
|
@@ -104,6 +106,7 @@ export interface DayOfMonthPattern {
|
|
|
104
106
|
readonly interval?: never;
|
|
105
107
|
readonly anchor?: never;
|
|
106
108
|
readonly dayAfterHoliday?: never;
|
|
109
|
+
readonly holiday?: never;
|
|
107
110
|
}
|
|
108
111
|
/**
|
|
109
112
|
* 祝日の翌日(祝日が続く場合は連休が明けた日。その日自身は祝日でない)。「祝日」は提供元の祝日だけ。
|
|
@@ -119,12 +122,33 @@ export interface DayAfterHolidayPattern {
|
|
|
119
122
|
readonly interval?: never;
|
|
120
123
|
readonly anchor?: never;
|
|
121
124
|
readonly day?: never;
|
|
125
|
+
readonly holiday?: never;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* 祝日(提供元の祝日である日)。kinds で祝日の種別を絞れる(省略時はすべての祝日)。
|
|
129
|
+
* 「祝日の上」の段階(holidayPolicy・スケジュールの holiday より先)で判定し、祝日の既定の扱いの例外を書く
|
|
130
|
+
* (例: 「2027 年 4 月から祝日も営業」「振替休日は診療」「5 月の祝日は特別営業時間」)。
|
|
131
|
+
* 当てはまる日は祝日なので、onHolidayRule は指定できない(指定すると invalid-option)。
|
|
132
|
+
* @public
|
|
133
|
+
*/
|
|
134
|
+
export interface HolidayPattern {
|
|
135
|
+
readonly holiday: true;
|
|
136
|
+
/** 当てはめる祝日の種別(Holiday.kind。省略時はすべて) */
|
|
137
|
+
readonly kinds?: readonly string[];
|
|
138
|
+
/** 当てはめる月(省略時はすべての月) */
|
|
139
|
+
readonly months?: readonly Month[];
|
|
140
|
+
readonly weekday?: never;
|
|
141
|
+
readonly nth?: never;
|
|
142
|
+
readonly interval?: never;
|
|
143
|
+
readonly anchor?: never;
|
|
144
|
+
readonly day?: never;
|
|
145
|
+
readonly dayAfterHoliday?: never;
|
|
122
146
|
}
|
|
123
147
|
/**
|
|
124
148
|
* 繰り返しのパターン。
|
|
125
149
|
* @public
|
|
126
150
|
*/
|
|
127
|
-
export type RecurringPattern = WeekdayPattern | DayOfMonthPattern | DayAfterHolidayPattern;
|
|
151
|
+
export type RecurringPattern = WeekdayPattern | DayOfMonthPattern | DayAfterHolidayPattern | HolidayPattern;
|
|
128
152
|
/**
|
|
129
153
|
* 繰り返しの規則(営業日カレンダーの closedRecurring・openRecurring)。
|
|
130
154
|
* @public
|
|
@@ -237,6 +261,11 @@ export interface ScheduleDay<T, Kind extends string = string> {
|
|
|
237
261
|
readonly holiday?: Holiday<Kind>;
|
|
238
262
|
/** 祝日から振り替えられた規則で値が決まった場合、振替の元の日付(onHolidayRule: "after-holiday") */
|
|
239
263
|
readonly shiftedFrom?: DateString;
|
|
264
|
+
/**
|
|
265
|
+
* 値を決めた規則の設定の中の位置(規則の診断の path と同じ形。例: "recurring[0]")。
|
|
266
|
+
* dates・annual・recurring(振替先を含む)で決まった日にだけ付き、祝日・曜日で決まった日には付かない
|
|
267
|
+
*/
|
|
268
|
+
readonly rulePath?: string;
|
|
240
269
|
}
|
|
241
270
|
/**
|
|
242
271
|
* 祝日を調べる機能(スケジュールと営業日カレンダーで共通)。
|
|
@@ -332,6 +361,10 @@ export interface RecurringRuleCompiled<T> extends CompiledPeriod {
|
|
|
332
361
|
readonly days: ReadonlySet<number> | null;
|
|
333
362
|
/** 祝日の翌日の規則か */
|
|
334
363
|
readonly dayAfterHoliday: boolean;
|
|
364
|
+
/** 祝日の規則か */
|
|
365
|
+
readonly holiday: boolean;
|
|
366
|
+
/** 祝日の規則で絞る種別(null はすべての祝日) */
|
|
367
|
+
readonly kinds: ReadonlySet<string> | null;
|
|
335
368
|
/** 月の集合(null はすべての月) */
|
|
336
369
|
readonly months: ReadonlySet<number> | null;
|
|
337
370
|
readonly onHolidayRule: OnHolidayRule;
|
|
@@ -340,16 +373,16 @@ export interface RecurringRuleCompiled<T> extends CompiledPeriod {
|
|
|
340
373
|
}
|
|
341
374
|
/** @internal 繰り返しの規則を検証して取り込む */
|
|
342
375
|
export declare function compileRecurringRules<T>(c: IssueCollector, path: string, entries: unknown, valueOf: (entry: Record<string, unknown>, path: string) => T | undefined, keys: readonly string[]): RecurringRuleCompiled<T>[];
|
|
343
|
-
/** @internal
|
|
344
|
-
export type
|
|
376
|
+
/** @internal その日の祝日を返す関数(祝日・祝日の翌日の規則の判定に使う。提供元の対応年の外は祝日でないとみなすこと) */
|
|
377
|
+
export type HolidayOf = (date: DateString) => Holiday<string> | undefined;
|
|
345
378
|
/**
|
|
346
379
|
* @internal 繰り返しの規則の形(曜日・第 n 回目・周期・日・祝日の翌日・月)がその日に当てはまるか。
|
|
347
380
|
* 有効期間は考えない(規則の診断で、形どうしの包含を調べるために分けている)。
|
|
348
381
|
* 祝日の翌日の規則は isHoliday で判定し、isHoliday を渡さなければ当てはまらない
|
|
349
382
|
*/
|
|
350
|
-
export declare function matchPattern(rule: RecurringRuleCompiled<unknown>, date: DateString,
|
|
383
|
+
export declare function matchPattern(rule: RecurringRuleCompiled<unknown>, date: DateString, holidayOf?: HolidayOf): boolean;
|
|
351
384
|
/** @internal 繰り返しの規則がその日に当てはまるか(有効期間を含む) */
|
|
352
|
-
export declare function matchRecurring(rule: RecurringRuleCompiled<unknown>, date: DateString,
|
|
385
|
+
export declare function matchRecurring(rule: RecurringRuleCompiled<unknown>, date: DateString, holidayOf: HolidayOf): boolean;
|
|
353
386
|
/** @internal 祝日の提供元とタイムゾーンの設定を検証した結果 */
|
|
354
387
|
export interface HolidaySource<Kind extends string> {
|
|
355
388
|
readonly providers: readonly HolidayProvider<Kind>[];
|
|
@@ -396,7 +429,8 @@ type HolidayLookupInternal<Kind extends string> = ReturnType<typeof createHolida
|
|
|
396
429
|
export declare function createResolver<T, Kind extends string>(compiled: CompiledSchedule<T, Kind>, lookup: HolidayLookupInternal<Kind>): (date: DateString) => Resolution<T, Kind>;
|
|
397
430
|
/** @internal 期間(両端を含む)の日ごとに fn を呼んだ結果。from が to より後ならエラー */
|
|
398
431
|
export declare function mapDays<R>(from: DateString, to: DateString, fn: (date: DateString) => R): readonly R[];
|
|
399
|
-
/** @internal
|
|
432
|
+
/** @internal スケジュールの内部の判定を取り出す(createSchedule が作ったものでなければ undefined) */
|
|
433
|
+
export declare function infoOfSchedule<T, Kind extends string>(schedule: Schedule<T, Kind>): ((date: DateString) => ScheduleDay<T, Kind>) | undefined;
|
|
400
434
|
export declare function buildSchedule<T, Kind extends string>(compiled: CompiledSchedule<T, Kind>): Schedule<T, Kind>;
|
|
401
435
|
/**
|
|
402
436
|
* 曜日・祝日・特定日の規則から、日ごとに値を返すスケジュールを作る。
|
package/dist/core/schedule.js
CHANGED
|
@@ -12,7 +12,8 @@
|
|
|
12
12
|
* "under-holiday" の規則は、祝日の扱いが "weekday"(曜日どおり)なら祝日にも当てはまる。
|
|
13
13
|
* "after-holiday" の規則は、祝日には当てはめず、その日以降で最初の祝日でない日に当てはめる(振替)。
|
|
14
14
|
* 毎年の月日・繰り返しの規則の有効期間(effectiveFrom・effectiveTo)の外では、その規則は書かれていないのと同じ。
|
|
15
|
-
* 祝日の翌日の規則(dayAfterHoliday
|
|
15
|
+
* 祝日の翌日の規則(dayAfterHoliday)は、前日が祝日でその日自身は祝日でない日に当てはまる(提供元の祝日だけで判定)。
|
|
16
|
+
* 祝日の規則(holiday: true。kinds で種別を絞れる)は、「祝日の上」の段階で判定する
|
|
16
17
|
*
|
|
17
18
|
* 営業日カレンダー(createCalendar)は、このスケジュールの真偽値版として作られている。
|
|
18
19
|
*/
|
|
@@ -210,10 +211,12 @@ export function compileRecurringRules(c, path, entries, valueOf, keys) {
|
|
|
210
211
|
const hasWeekday = "weekday" in entry;
|
|
211
212
|
const hasDay = "day" in entry;
|
|
212
213
|
const hasAfter = "dayAfterHoliday" in entry;
|
|
213
|
-
|
|
214
|
-
|
|
214
|
+
const hasHoliday = "holiday" in entry;
|
|
215
|
+
if (Number(hasWeekday) + Number(hasDay) + Number(hasAfter) + Number(hasHoliday) !== 1) {
|
|
216
|
+
c.error("invalid-option", p, 'must have exactly one of "weekday", "day", "dayAfterHoliday" or "holiday"');
|
|
215
217
|
return;
|
|
216
218
|
}
|
|
219
|
+
let kinds = null;
|
|
217
220
|
let ok = true;
|
|
218
221
|
let weekday = null;
|
|
219
222
|
let nth = null;
|
|
@@ -278,16 +281,34 @@ export function compileRecurringRules(c, path, entries, valueOf, keys) {
|
|
|
278
281
|
ok = false;
|
|
279
282
|
}
|
|
280
283
|
else {
|
|
281
|
-
|
|
282
|
-
|
|
284
|
+
const key = hasAfter ? "dayAfterHoliday" : "holiday";
|
|
285
|
+
if (entry[key] !== true) {
|
|
286
|
+
c.error("invalid-option", `${p}.${key}`, "must be true");
|
|
283
287
|
ok = false;
|
|
284
288
|
}
|
|
285
289
|
if ("onHolidayRule" in entry) {
|
|
286
|
-
c.error("invalid-option", `${p}.onHolidayRule`,
|
|
290
|
+
c.error("invalid-option", `${p}.onHolidayRule`, hasAfter
|
|
291
|
+
? 'cannot be used with "dayAfterHoliday" (the day after a holiday is never a holiday)'
|
|
292
|
+
: 'cannot be used with "holiday" (the rule applies on holidays; it is judged before holidayPolicy)');
|
|
287
293
|
ok = false;
|
|
288
294
|
}
|
|
295
|
+
if (hasHoliday && entry["kinds"] !== undefined && !Array.isArray(entry["kinds"])) {
|
|
296
|
+
c.error("invalid-option", `${p}.kinds`, "must be a non-empty array of holiday kinds (Holiday.kind)");
|
|
297
|
+
ok = false;
|
|
298
|
+
}
|
|
299
|
+
else if (hasHoliday && entry["kinds"] !== undefined) {
|
|
300
|
+
const set = checkSet(c, `${p}.kinds`, entry["kinds"], (v) => typeof v === "string" && v.length > 0, "a non-empty string (Holiday.kind)");
|
|
301
|
+
if (set)
|
|
302
|
+
kinds = set;
|
|
303
|
+
else
|
|
304
|
+
ok = false;
|
|
305
|
+
}
|
|
289
306
|
}
|
|
290
307
|
}
|
|
308
|
+
if (!hasHoliday && "kinds" in entry) {
|
|
309
|
+
c.error("invalid-option", `${p}.kinds`, '"kinds" can be used only with "holiday"');
|
|
310
|
+
ok = false;
|
|
311
|
+
}
|
|
291
312
|
let months = null;
|
|
292
313
|
if (entry["months"] !== undefined) {
|
|
293
314
|
if (!Array.isArray(entry["months"])) {
|
|
@@ -302,7 +323,8 @@ export function compileRecurringRules(c, path, entries, valueOf, keys) {
|
|
|
302
323
|
ok = false;
|
|
303
324
|
}
|
|
304
325
|
}
|
|
305
|
-
|
|
326
|
+
// 祝日の規則は「祝日の上」の段階で判定する(当てはまる日は祝日なので、祝日の下・振替は意味を持たない)
|
|
327
|
+
const onHoliday = entry["onHolidayRule"] ?? (hasHoliday ? "over-holiday" : "under-holiday");
|
|
306
328
|
if (!ON_HOLIDAY_RULES.includes(onHoliday)) {
|
|
307
329
|
c.error("invalid-option", `${p}.onHolidayRule`, `must be one of ${ON_HOLIDAY_RULES.map((v) => `"${v}"`).join(", ")}`);
|
|
308
330
|
ok = false;
|
|
@@ -317,6 +339,8 @@ export function compileRecurringRules(c, path, entries, valueOf, keys) {
|
|
|
317
339
|
anchor,
|
|
318
340
|
days,
|
|
319
341
|
dayAfterHoliday: hasAfter,
|
|
342
|
+
holiday: hasHoliday,
|
|
343
|
+
kinds,
|
|
320
344
|
months,
|
|
321
345
|
onHolidayRule: onHoliday,
|
|
322
346
|
value,
|
|
@@ -332,12 +356,18 @@ export function compileRecurringRules(c, path, entries, valueOf, keys) {
|
|
|
332
356
|
* 有効期間は考えない(規則の診断で、形どうしの包含を調べるために分けている)。
|
|
333
357
|
* 祝日の翌日の規則は isHoliday で判定し、isHoliday を渡さなければ当てはまらない
|
|
334
358
|
*/
|
|
335
|
-
export function matchPattern(rule, date,
|
|
359
|
+
export function matchPattern(rule, date, holidayOf) {
|
|
336
360
|
const { year, month, day } = parseDate(date);
|
|
337
361
|
if (rule.months && !rule.months.has(month))
|
|
338
362
|
return false;
|
|
363
|
+
if (rule.holiday) {
|
|
364
|
+
const h = holidayOf?.(date);
|
|
365
|
+
return h !== undefined && (rule.kinds === null || rule.kinds.has(h.kind));
|
|
366
|
+
}
|
|
339
367
|
if (rule.dayAfterHoliday) {
|
|
340
|
-
return
|
|
368
|
+
return (holidayOf !== undefined &&
|
|
369
|
+
holidayOf(date) === undefined &&
|
|
370
|
+
holidayOf(addDays(date, -1)) !== undefined);
|
|
341
371
|
}
|
|
342
372
|
const dim = daysInMonth(year, month);
|
|
343
373
|
if (rule.weekday !== null) {
|
|
@@ -352,8 +382,8 @@ export function matchPattern(rule, date, isHoliday) {
|
|
|
352
382
|
return rule.days.has(day) || (rule.days.has(-1) && day === dim);
|
|
353
383
|
}
|
|
354
384
|
/** @internal 繰り返しの規則がその日に当てはまるか(有効期間を含む) */
|
|
355
|
-
export function matchRecurring(rule, date,
|
|
356
|
-
return inPeriod(rule, toDayNumber(date)) && matchPattern(rule, date,
|
|
385
|
+
export function matchRecurring(rule, date, holidayOf) {
|
|
386
|
+
return inPeriod(rule, toDayNumber(date)) && matchPattern(rule, date, holidayOf);
|
|
357
387
|
}
|
|
358
388
|
/** 規則の value を取り出す(value が必須の規則用) */
|
|
359
389
|
function requiredValue(c) {
|
|
@@ -586,6 +616,8 @@ export const RECURRING_RULE_KEYS = [
|
|
|
586
616
|
"anchor",
|
|
587
617
|
"day",
|
|
588
618
|
"dayAfterHoliday",
|
|
619
|
+
"holiday",
|
|
620
|
+
"kinds",
|
|
589
621
|
"months",
|
|
590
622
|
"name",
|
|
591
623
|
"onHolidayRule",
|
|
@@ -648,15 +680,16 @@ export function matchAnnual(rule, date) {
|
|
|
648
680
|
export function createResolver(compiled, lookup) {
|
|
649
681
|
const { weekly, holiday: holidayRule, annual, dates, recurring } = compiled;
|
|
650
682
|
const hasShift = recurring.some((r) => r.onHolidayRule === "after-holiday");
|
|
651
|
-
/**
|
|
652
|
-
function
|
|
683
|
+
/** 振替・祝日の規則の判断に使う祝日。提供元の対応年の外は祝日でないとみなす(振替元をさかのぼる途中で範囲を出る場合のため) */
|
|
684
|
+
function holidayOfSafe(date) {
|
|
653
685
|
try {
|
|
654
|
-
return lookup.holidayOn(date)
|
|
686
|
+
return lookup.holidayOn(date);
|
|
655
687
|
}
|
|
656
688
|
catch {
|
|
657
|
-
return
|
|
689
|
+
return undefined;
|
|
658
690
|
}
|
|
659
691
|
}
|
|
692
|
+
const isHolidaySafe = (date) => holidayOfSafe(date) !== undefined;
|
|
660
693
|
/**
|
|
661
694
|
* "after-holiday" の規則が、date(祝日でない日)に振り替えられてきたか。
|
|
662
695
|
* date の前日からさかのぼって祝日が続く間に、規則に当たる日があれば、その日付を返す
|
|
@@ -666,7 +699,7 @@ export function createResolver(compiled, lookup) {
|
|
|
666
699
|
const prev = addDays(date, -k);
|
|
667
700
|
if (!isHolidaySafe(prev))
|
|
668
701
|
return undefined;
|
|
669
|
-
if (matchRecurring(rule, prev,
|
|
702
|
+
if (matchRecurring(rule, prev, holidayOfSafe))
|
|
670
703
|
return prev;
|
|
671
704
|
}
|
|
672
705
|
return undefined;
|
|
@@ -688,6 +721,8 @@ export function createResolver(compiled, lookup) {
|
|
|
688
721
|
result.holiday = holiday;
|
|
689
722
|
if (shiftedFrom !== undefined)
|
|
690
723
|
result.shiftedFrom = shiftedFrom;
|
|
724
|
+
if (rule !== null)
|
|
725
|
+
result.rulePath = rule.path;
|
|
691
726
|
return { day: Object.freeze(result), rule };
|
|
692
727
|
};
|
|
693
728
|
// 1. 個別の日付 2. 毎年の月日
|
|
@@ -698,8 +733,8 @@ export function createResolver(compiled, lookup) {
|
|
|
698
733
|
if (byAnnual)
|
|
699
734
|
return make(byAnnual.value, "annual", byAnnual, byAnnual.name);
|
|
700
735
|
if (holiday) {
|
|
701
|
-
// 3. 祝日の上("over-holiday"
|
|
702
|
-
const over = recurring.find((r) => r.onHolidayRule === "over-holiday" && matchRecurring(r, date,
|
|
736
|
+
// 3. 祝日の上("over-holiday"。祝日の規則 { holiday: true } もここ)
|
|
737
|
+
const over = recurring.find((r) => r.onHolidayRule === "over-holiday" && matchRecurring(r, date, holidayOfSafe));
|
|
703
738
|
if (over)
|
|
704
739
|
return make(over.value, "recurring", over, over.name);
|
|
705
740
|
// 4. 祝日
|
|
@@ -709,14 +744,14 @@ export function createResolver(compiled, lookup) {
|
|
|
709
744
|
}
|
|
710
745
|
// 祝日の扱いが「曜日どおり」なら、祝日の下("under-holiday")の規則も当てはまる。
|
|
711
746
|
// "after-holiday" の規則は祝日には当てはめない(振り替える)
|
|
712
|
-
const under = recurring.find((r) => r.onHolidayRule === "under-holiday" && matchRecurring(r, date,
|
|
747
|
+
const under = recurring.find((r) => r.onHolidayRule === "under-holiday" && matchRecurring(r, date, holidayOfSafe));
|
|
713
748
|
if (under)
|
|
714
749
|
return make(under.value, "recurring", under, under.name);
|
|
715
750
|
return make(weekly[weekday], "weekday", null, undefined);
|
|
716
751
|
}
|
|
717
752
|
// 5. 繰り返しの規則(祝日でない日。配列で先に書いたものが優先)。振替先も同じ段階で判定する
|
|
718
753
|
for (const rule of recurring) {
|
|
719
|
-
if (matchRecurring(rule, date,
|
|
754
|
+
if (matchRecurring(rule, date, holidayOfSafe))
|
|
720
755
|
return make(rule.value, "recurring", rule, rule.name);
|
|
721
756
|
if (hasShift && rule.onHolidayRule === "after-holiday") {
|
|
722
757
|
const origin = shiftedOrigin(rule, date);
|
|
@@ -739,6 +774,15 @@ export function mapDays(from, to, fn) {
|
|
|
739
774
|
return Object.freeze(result);
|
|
740
775
|
}
|
|
741
776
|
/** @internal 検証済みの規則からスケジュールを作る */
|
|
777
|
+
/**
|
|
778
|
+
* @internal スケジュールの内部の判定(暦日の文字列を直接受け取る)。createCalendarFromSchedule が、公開メソッド
|
|
779
|
+
* (入力の解析を通る)を経由せずに 1 日分の結果を得るために使う。スケジュールごとに作成時に登録する
|
|
780
|
+
*/
|
|
781
|
+
const internalInfo = new WeakMap();
|
|
782
|
+
/** @internal スケジュールの内部の判定を取り出す(createSchedule が作ったものでなければ undefined) */
|
|
783
|
+
export function infoOfSchedule(schedule) {
|
|
784
|
+
return internalInfo.get(schedule);
|
|
785
|
+
}
|
|
742
786
|
export function buildSchedule(compiled) {
|
|
743
787
|
const lookup = createHolidayLookup(compiled.source);
|
|
744
788
|
const resolve = createResolver(compiled, lookup);
|
|
@@ -758,7 +802,9 @@ export function buildSchedule(compiled) {
|
|
|
758
802
|
dayInfo: (input) => info(lookup.dateOf(input)),
|
|
759
803
|
daysBetween: (from, to) => mapDays(lookup.dateOf(from), lookup.dateOf(to), info),
|
|
760
804
|
};
|
|
761
|
-
|
|
805
|
+
Object.freeze(schedule);
|
|
806
|
+
internalInfo.set(schedule, info);
|
|
807
|
+
return schedule;
|
|
762
808
|
}
|
|
763
809
|
/**
|
|
764
810
|
* 曜日・祝日・特定日の規則から、日ごとに値を返すスケジュールを作る。
|
package/dist/index.d.ts
CHANGED
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
* 公開する API には TSDoc の @public(安定)または @beta(マイナー版で変わりうる)を付ける。
|
|
8
8
|
* 公開する名前と型は etc/api.md に記録し、pnpm check で意図しない変化を検出する。
|
|
9
9
|
*/
|
|
10
|
-
export { combineCalendars, createCalendar, type AdjustOptions, type AnnualDate, type AnnualRange, type BusinessDayConvention, type BusinessDayOptions, type Calendar, type CalendarOptions, type CombineOptions, type DateRange, type DayInfo, type DayReason, type DueDateOptions, type HolidayPolicy, type KnownDayReason, type SpecificDate, } from "./core/calendar.ts";
|
|
11
|
-
export { createSchedule, type AnnualRangeValue, type AnnualValue, type CivilDateTimeParts, type DateRangeValue, type DateValue, type DayAfterHolidayPattern, type DayOfMonth, type DayOfMonthPattern, type EffectivePeriod, type HolidayLookup, type HolidaySourceOptions, type HolidayValueRule, type KnownScheduleReason, type Month, type Nth, type OnHolidayRule, type RecurringPattern, type RecurringRule, type RecurringValue, type Schedule, type ScheduleDay, type ScheduleOptions, type ScheduleReason, type WeekdayPattern, } from "./core/schedule.ts";
|
|
10
|
+
export { combineCalendars, createCalendar, createCalendarFromSchedule, type AdjustOptions, type AnnualDate, type AnnualRange, type BusinessDayConvention, type BusinessDayOptions, type Calendar, type CalendarOptions, type CombineOptions, type DateRange, type DayInfo, type DayReason, type DueDateOptions, type HolidayPolicy, type KnownDayReason, type SpecificDate, } from "./core/calendar.ts";
|
|
11
|
+
export { createSchedule, type AnnualRangeValue, type AnnualValue, type CivilDateTimeParts, type DateRangeValue, type DateValue, type DayAfterHolidayPattern, type DayOfMonth, type DayOfMonthPattern, type EffectivePeriod, type HolidayLookup, type HolidayPattern, type HolidaySourceOptions, type HolidayValueRule, type KnownScheduleReason, type Month, type Nth, type OnHolidayRule, type RecurringPattern, type RecurringRule, type RecurringValue, type Schedule, type ScheduleDay, type ScheduleOptions, type ScheduleReason, type WeekdayPattern, } from "./core/schedule.ts";
|
|
12
12
|
export { addDays, diffDays, isValidDate, weekdayOf, type DateString, type Weekday, } from "./core/date.ts";
|
|
13
13
|
export { closingMonth, monthOf, type MonthString } from "./core/month.ts";
|
|
14
14
|
export { isBizdaysError, type ErrorCode, type KnownErrorCode } from "./core/errors.ts";
|
package/dist/index.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* 公開する API には TSDoc の @public(安定)または @beta(マイナー版で変わりうる)を付ける。
|
|
8
8
|
* 公開する名前と型は etc/api.md に記録し、pnpm check で意図しない変化を検出する。
|
|
9
9
|
*/
|
|
10
|
-
export { combineCalendars, createCalendar, } from "./core/calendar.js";
|
|
10
|
+
export { combineCalendars, createCalendar, createCalendarFromSchedule, } from "./core/calendar.js";
|
|
11
11
|
export { createSchedule, } from "./core/schedule.js";
|
|
12
12
|
export { addDays, diffDays, isValidDate, weekdayOf, } from "./core/date.js";
|
|
13
13
|
export { closingMonth, monthOf } from "./core/month.js";
|
package/dist/jp/index.d.ts
CHANGED
|
@@ -67,11 +67,13 @@ export declare function toJapanDateTime(input: DateTimeInput): CivilDateTimePart
|
|
|
67
67
|
export declare function toJapanDateTime(input: DateInput): CivilDateTimeParts;
|
|
68
68
|
/**
|
|
69
69
|
* 反映済みの官報の年月。
|
|
70
|
+
* @deprecated 0.12.0 で非推奨。提供元の項目 jp.dataVersion(同じ値)に移行する(docs/migration.md)。1.0.0 で削除する
|
|
70
71
|
* @public
|
|
71
72
|
*/
|
|
72
73
|
export declare const dataVersion: string;
|
|
73
74
|
/**
|
|
74
75
|
* 春分・秋分の日が官報で確定している最後の年。
|
|
76
|
+
* @deprecated 0.12.0 で非推奨。提供元の項目 jp.confirmedThrough(同じ値)に移行する(docs/migration.md)。1.0.0 で削除する
|
|
75
77
|
* @public
|
|
76
78
|
*/
|
|
77
79
|
export declare const confirmedThrough: number;
|
package/dist/jp/index.js
CHANGED
|
@@ -76,11 +76,13 @@ export function toJapanDateTime(input) {
|
|
|
76
76
|
}
|
|
77
77
|
/**
|
|
78
78
|
* 反映済みの官報の年月。
|
|
79
|
+
* @deprecated 0.12.0 で非推奨。提供元の項目 jp.dataVersion(同じ値)に移行する(docs/migration.md)。1.0.0 で削除する
|
|
79
80
|
* @public
|
|
80
81
|
*/
|
|
81
82
|
export const dataVersion = DATA_VERSION;
|
|
82
83
|
/**
|
|
83
84
|
* 春分・秋分の日が官報で確定している最後の年。
|
|
85
|
+
* @deprecated 0.12.0 で非推奨。提供元の項目 jp.confirmedThrough(同じ値)に移行する(docs/migration.md)。1.0.0 で削除する
|
|
84
86
|
* @public
|
|
85
87
|
*/
|
|
86
88
|
export const confirmedThrough = CONFIRMED_THROUGH;
|
package/dist/uk/index.d.ts
CHANGED
|
@@ -39,11 +39,13 @@ export declare const scotland: HolidayProvider<UkHolidayKind>;
|
|
|
39
39
|
export declare const northernIreland: HolidayProvider<UkHolidayKind>;
|
|
40
40
|
/**
|
|
41
41
|
* 反映済みの GOV.UK のデータの年月。
|
|
42
|
+
* @deprecated 0.12.0 で非推奨。提供元の項目(england.dataVersion など。4 つの提供元で同じ値)に移行する(docs/migration.md)。1.0.0 で削除する
|
|
42
43
|
* @public
|
|
43
44
|
*/
|
|
44
45
|
export declare const dataVersion: string;
|
|
45
46
|
/**
|
|
46
47
|
* GOV.UK が日付を公表している最後の年(それより後は規則による予測で confirmed: false)。
|
|
48
|
+
* @deprecated 0.12.0 で非推奨。提供元の項目(england.confirmedThrough など。4 つの提供元で同じ値)に移行する(docs/migration.md)。1.0.0 で削除する
|
|
47
49
|
* @public
|
|
48
50
|
*/
|
|
49
51
|
export declare const confirmedThrough: number;
|
package/dist/uk/index.js
CHANGED
|
@@ -73,11 +73,13 @@ export const scotland = provider("GB-SCT", "scotland");
|
|
|
73
73
|
export const northernIreland = provider("GB-NIR", "northern-ireland");
|
|
74
74
|
/**
|
|
75
75
|
* 反映済みの GOV.UK のデータの年月。
|
|
76
|
+
* @deprecated 0.12.0 で非推奨。提供元の項目(england.dataVersion など。4 つの提供元で同じ値)に移行する(docs/migration.md)。1.0.0 で削除する
|
|
76
77
|
* @public
|
|
77
78
|
*/
|
|
78
79
|
export const dataVersion = DATA_VERSION;
|
|
79
80
|
/**
|
|
80
81
|
* GOV.UK が日付を公表している最後の年(それより後は規則による予測で confirmed: false)。
|
|
82
|
+
* @deprecated 0.12.0 で非推奨。提供元の項目(england.confirmedThrough など。4 つの提供元で同じ値)に移行する(docs/migration.md)。1.0.0 で削除する
|
|
81
83
|
* @public
|
|
82
84
|
*/
|
|
83
85
|
export const confirmedThrough = CONFIRMED_THROUGH;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bizdays-toolkit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
4
4
|
"description": "Holidays, business-day calendar and day-by-day schedules (e.g. store hours) with time zone support. Japanese holidays verified against the Cabinet Office data; UK bank holidays (England, Wales, Scotland, Northern Ireland) verified against GOV.UK. / 祝日と営業日のカレンダー(タイムゾーン対応・日本の祝日は内閣府、英国の bank holidays は GOV.UK のデータで全日照合)",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"bank-holidays",
|