bizdays-toolkit 0.11.1 → 0.12.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 CHANGED
@@ -4,6 +4,36 @@
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [0.12.0] - 2026-10-20
8
+
9
+ ### 移行
10
+
11
+ 動作は変わりません(繰り返しの規則の新しい形・`dayInfo` の新しい項目の追加と、診断・CI の改善)。`bizdays-toolkit/jp`・`bizdays-toolkit/uk` の定数 `dataVersion`・`confirmedThrough` が非推奨になったので、使っていれば提供元の項目(`jp.dataVersion`・`england.confirmedThrough` など)に書き換える。版ごとの作業は [docs/migration.md](./docs/migration.md) にまとめた。
12
+
13
+ ### Added
14
+
15
+ - 繰り返しの規則に、祝日の形 `{ 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` の規則でだけ使える
16
+ - `DayInfo`・`ScheduleDay` に `rulePath`(値を決めた規則の設定の中の位置。規則の診断の `path` と同じ形。例: `"closedRecurring[0]"`)を追加した。`dates`・`annual`・`recurring`(振替先を含む)で決まった日にだけ付き、祝日・曜日で決まった日には付かない。合成したカレンダーは決めたカレンダーの `rulePath` を引き継ぐ
17
+
18
+ ### Deprecated
19
+
20
+ - `bizdays-toolkit/jp`・`bizdays-toolkit/uk` の定数 `dataVersion`・`confirmedThrough` を非推奨にした(`@deprecated`)。提供元の項目(`jp.dataVersion`・`jp.confirmedThrough`、英国は `england.dataVersion` など 4 つの提供元で同じ値)と同じ値で、経路を 1 つにするため。1.0.0 で削除する
21
+
22
+ ### Changed
23
+
24
+ - 規則の診断で、祝日の規則を扱えるようにした。提供元のない設定、または `kinds`・`months`・有効期間に当てはまる祝日がない規則は `never-matches`。`shadowed` は祝日の規則どうし(`kinds`・`months`・有効期間の包含)でだけ調べ、既定の扱いと同じ向きの規則は `redundant` で見つける
25
+ - 規則の診断の `unknown-option` の候補(did you mean)で、既知のキーの先頭部分(`onHoliday` → `onHolidayRule`)を、短い別のキー(`holiday`)との編集距離より優先するようにした
26
+ - 繰り返しの規則の形の誤りのメッセージを、`"weekday"`・`"day"`・`"dayAfterHoliday"`・`"holiday"` のうち 1 つだけを持つ、に改めた(code は `invalid-option` のまま)
27
+ - README: 繰り返しの規則の節に祝日の規則を、`rulePath` の説明を、合成の節に祝日の提供元の和集合の意味(同じ提供元は 1 つ、包んだ提供元は別物、同じ日付は先のカレンダーの提供元)を足した。`dataVersion`・`confirmedThrough` の案内を提供元の項目に改めた。サイズの値を測り直した(ルート約 8.8KB・日本の祝日込み約 10.6KB・英国の祝日込み約 10.5KB。0.12.0 で測定)
28
+ - `docs/decisions.md`: 4 番に「知っているキーの無効な組み合わせは例外にする」境界を、34 番に和集合の意味を足した。35 番(祝日の規則)・36 番(`rulePath`)・37 番(定数の一本化と性能の検証)を追加した
29
+
30
+ ### 開発
31
+
32
+ - CI に `bench` ジョブを追加した。公開済みの前の版(npm の `latest`)と今回の `dist` を同じランナーで `scripts/bench.ts` にかけ、`scripts/bench-compare.ts` で比率を比べる。どれかの計測が 2 倍より遅ければ失敗し、絶対値は Job Summary に残す(`pnpm check` には含めない)。`scripts/bench.ts` に `--package <dir>`・`--json <file>` を足した
33
+ - サイズの上限(`size-limits.json`)を上げた。ルート 19,500 → 20,500、`./jp` 22,500 → 23,500、`./diagnose` 26,000 → 27,000(gzip)。祝日の規則の検証・判定と `rulePath`(ルートで約 0.5KB)、診断の祝日の規則への対応と候補の改善(約 0.7KB)のため
34
+ - 性質のテストに祝日の規則を加えた。規則を取り除いた結果との比較では、位置がずれる `rulePath` を除いて比べる
35
+ - ライブラリ内のテストとスクリプトで、非推奨にした定数 `confirmedThrough` の代わりに提供元の項目を使うようにした
36
+
7
37
  ## [0.11.1] - 2026-10-19
8
38
 
9
39
  ライブラリのコードは 0.11.0 と同じ。
@@ -300,7 +330,8 @@
300
330
 
301
331
  - 日本: 2026 年 2 月の官報(2027 年の春分・秋分の日)まで反映(`dataVersion: "2026-02"`、`confirmedThrough: 2027`)
302
332
 
303
- [Unreleased]: https://github.com/inohan77/bizdays-toolkit/compare/v0.11.1...HEAD
333
+ [Unreleased]: https://github.com/inohan77/bizdays-toolkit/compare/v0.12.0...HEAD
334
+ [0.12.0]: https://github.com/inohan77/bizdays-toolkit/compare/v0.11.1...v0.12.0
304
335
  [0.11.1]: https://github.com/inohan77/bizdays-toolkit/compare/v0.11.0...v0.11.1
305
336
  [0.11.0]: https://github.com/inohan77/bizdays-toolkit/compare/v0.10.0...v0.11.0
306
337
  [0.10.0]: https://github.com/inohan77/bizdays-toolkit/compare/v0.9.1...v0.10.0
package/README.md CHANGED
@@ -27,7 +27,7 @@ A business-day calendar that combines public holidays with company or store clos
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
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
29
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
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
+ - **Lightweight, zero dependencies**: about 8.8 KB min+gzip (about 10.6 KB with Japanese holidays, about 10.5 KB with UK bank holidays; measured for 0.12.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
31
  - **Extensible to other countries and regions**: holiday providers are pluggable through the `HolidayProvider` interface. Japan and the UK are bundled
32
32
 
33
33
  ## Installation
@@ -114,7 +114,7 @@ shipping.businessDaysBetween("2026-09-01", "2026-10-01"); // business days from
114
114
  shipping.businessDayOnOrAfter(new Date(), { cutoff: "14:00" });
115
115
 
116
116
  shipping.dayInfo("2027-01-01");
117
- // { date: "2027-01-01", weekday: 5, businessDay: false, reason: "closed-annual", name: "New Year closure", holiday: {…} }
117
+ // { date: "2027-01-01", weekday: 5, businessDay: false, reason: "closed-annual", name: "New Year closure", holiday: {…}, rulePath: "closedAnnual[0]" }
118
118
  shipping.daysBetween("2026-12-30", "2027-01-04").map((d) => d.businessDay); // [true, false, false, false, false, true]
119
119
  ```
120
120
 
@@ -127,7 +127,7 @@ Each day is decided in this order of priority (top first).
127
127
  1. `openDates` (specific open days)
128
128
  2. `closedDates` (specific closed days)
129
129
  3. `closedAnnual` (annual closures)
130
- 4. `openRecurring` and `closedRecurring` rules with `onHolidayRule: "over-holiday"` (applied even on holidays)
130
+ 4. `openRecurring` and `closedRecurring` rules with `onHolidayRule: "over-holiday"` (applied even on holidays), and holiday rules (`{ holiday: true }`)
131
131
  5. Holidays (according to `holidayPolicy`; `"open"` makes them business days even on closed weekdays)
132
132
  6. `openRecurring` and `closedRecurring` (the default `"under-holiday"`, and the days moved to by `"after-holiday"`)
133
133
  7. `closedWeekdays` (closed weekdays)
@@ -135,9 +135,9 @@ Each day is decided in this order of priority (top first).
135
135
 
136
136
  This order is fixed. Only the relation between a recurring rule and holidays can be chosen per rule with `onHolidayRule` (next section).
137
137
 
138
- ### Recurring rules (weekly, every other week, nth weekday, day of month, day after a holiday)
138
+ ### Recurring rules (weekly, every other week, nth weekday, day of month, holidays, day after a holiday)
139
139
 
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
+ 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
141
 
142
142
  ```ts
143
143
  const museum = createCalendar({
@@ -159,7 +159,7 @@ const museum = createCalendar({
159
159
  museum.isBusinessDay("2026-10-12"); // true (open on Monday, Sports Day)
160
160
  museum.dayInfo("2026-10-13");
161
161
  // { date: "2026-10-13", weekday: 2, businessDay: false, reason: "closed-recurring",
162
- // name: "Closed on Mondays", shiftedFrom: "2026-10-12" }
162
+ // name: "Closed on Mondays", shiftedFrom: "2026-10-12", rulePath: "closedRecurring[0]" }
163
163
  museum.dayInfo("2026-02-12").name; // "Closed on the day after a holiday" (National Foundation Day is February 11)
164
164
  museum.isBusinessDay("2026-05-07"); // false (the day after the May 3–6 run of holidays)
165
165
  ```
@@ -170,6 +170,7 @@ museum.isBusinessDay("2026-05-07"); // false (the day after the May 3–6 run of
170
170
  | `{ weekday: 3, nth: 3 }` | The 3rd Wednesday. `nth` is 1–5 or -1 (last); an array such as `[2, 4]` gives several |
171
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
172
  | `{ day: 20 }` | The 20th of every month. `day` is 1–31 or -1 (last day); an array gives several |
173
+ | `{ holiday: true, kinds: ["substitute"] }` | Holidays, optionally narrowed to the given kinds (`Holiday.kind`); judged before `holidayPolicy` (see below) |
173
174
  | `{ 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
175
  | `months: [8, 12]` | Narrows any pattern to these months |
175
176
  | `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 +178,7 @@ museum.isBusinessDay("2026-05-07"); // false (the day after the May 3–6 run of
177
178
  - **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
179
  - **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
180
  - **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`
181
+ - **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
182
  - **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
183
  - 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
184
 
@@ -214,6 +216,7 @@ shop.isBusinessDay("2027-08-13"); // true (the summer closure ended in 2026)
214
216
  - With `holidayPolicy: "ignore"` (holidays not used), `"under-holiday"` rules also apply on holidays
215
217
  - 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
218
  - The "holidays" used for moving are only those of the holiday provider (such as `jp`), not annual closures
219
+ - `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
220
 
218
221
  In a schedule, the same patterns with a `value` go in `recurring`.
219
222
 
@@ -251,15 +254,15 @@ const bank = createCalendar({
251
254
  const remittance = combineCalendars([company, bank], { businessDay: "all" });
252
255
  remittance.addBusinessDays("2026-12-28", 2); // "2027-01-05"
253
256
  remittance.dayInfo("2026-12-29");
254
- // { date: "2026-12-29", weekday: 2, businessDay: false, reason: "closed-annual", name: "New Year closure", decidedBy: 0 }
257
+ // { date: "2026-12-29", weekday: 2, businessDay: false, reason: "closed-annual", name: "New Year closure", rulePath: "closedAnnual[0]", decidedBy: 0 }
255
258
 
256
259
  // Any of several stores is open
257
260
  const anyStore = combineCalendars([company, bank], { businessDay: "any" });
258
261
  anyStore.isBusinessDay("2026-12-29"); // true
259
262
  ```
260
263
 
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
264
+ - `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
265
+ - `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
266
  - 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
267
  - A combined calendar can be combined again. Option diagnostics work on options, not on calendars, so diagnose each calendar's options separately
265
268
 
@@ -418,7 +421,7 @@ const storeHours = createSchedule<Hours>({
418
421
 
419
422
  storeHours.valueOn("2026-11-03"); // { open: "10:00", close: "19:00" } (Culture Day uses Sunday hours)
420
423
  storeHours.dayInfo("2027-01-01");
421
- // { date: "2027-01-01", weekday: 5, value: null, reason: "annual", name: "Closed on New Year's Day", holiday: {…} }
424
+ // { date: "2027-01-01", weekday: 5, value: null, reason: "annual", name: "Closed on New Year's Day", holiday: {…}, rulePath: "annual[0]" }
422
425
 
423
426
  // Holidays in a period with their hours: filter daysBetween by holiday
424
427
  storeHours
@@ -433,7 +436,7 @@ storeHours
433
436
  .map((d) => d.date); // ["2027-01-11"]
434
437
  ```
435
438
 
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.
439
+ `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
440
 
438
441
  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
442
 
@@ -594,7 +597,7 @@ Versioning follows [Semantic Versioning](https://semver.org/). See [RELEASING.md
594
597
 
595
598
  - 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
599
  - 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`
600
+ - 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
601
  - 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
602
  - 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
603
 
@@ -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
@@ -139,6 +139,8 @@ export function createCalendar(options = {}) {
139
139
  result.holiday = day.holiday;
140
140
  if (day.shiftedFrom !== undefined)
141
141
  result.shiftedFrom = day.shiftedFrom;
142
+ if (day.rulePath !== undefined)
143
+ result.rulePath = day.rulePath;
142
144
  return Object.freeze(result);
143
145
  }
144
146
  return buildCalendar(schedule, (date) => schedule.valueOn(date), info, maxSearchDays);
@@ -314,7 +316,7 @@ const isCalendarLike = (v) => typeof v === "object" &&
314
316
  * 作成済みの営業日カレンダーを合成する。"all" はすべてのカレンダーが営業している日、"any" はいずれかが
315
317
  * 営業している日を営業日とする。返すのは通常の営業日カレンダーで、営業日の加減算・調整・期日などが合成結果で動く。
316
318
  *
317
- * - dayInfo の reason・name・shiftedFrom は、合成結果と同じ判定をした最初のカレンダーの結果で、
319
+ * - dayInfo の reason・name・shiftedFrom・rulePath は、合成結果と同じ判定をした最初のカレンダーの結果で、
318
320
  * その番号を decidedBy で返す。holiday・isHoliday などは、すべてのカレンダーの祝日の提供元の和集合で判定する
319
321
  * - タイムゾーンはすべてのカレンダーで同じでなければならない(time-zone-conflict)。
320
322
  * 2 つ未満、同じカレンダーを 2 回渡した場合は invalid-argument
@@ -377,6 +379,8 @@ export function combineCalendars(calendars, options) {
377
379
  result.holiday = holiday;
378
380
  if (base.shiftedFrom !== undefined)
379
381
  result.shiftedFrom = base.shiftedFrom;
382
+ if (base.rulePath !== undefined)
383
+ result.rulePath = base.rulePath;
380
384
  result.decidedBy = decidedBy;
381
385
  return Object.freeze(result);
382
386
  }
@@ -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
- if (best !== undefined)
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 isHolidaySafe = (date) => {
363
+ /** 祝日・祝日の翌日の規則の判定に使う(提供元の対応年の外は祝日でないとみなす) */
364
+ const holidayOfSafe = (date) => {
363
365
  try {
364
- return lookup.holidayOn(date) !== undefined;
366
+ return lookup.holidayOn(date);
365
367
  }
366
368
  catch {
367
- return false;
369
+ return undefined;
368
370
  }
369
371
  };
370
- /** 祝日の翌日の規則が、調べる期間と有効期間の中で 1 日でも当てはまるか(祝日は年ごとに違うので、全日を調べる) */
371
- const dayAfterHolidayMatches = (rule) => {
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), isHolidaySafe)));
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 matches = rule.dayAfterHoliday
381
- ? dayAfterHolidayMatches(rule)
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, rule.dayAfterHoliday
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
- : "This rule matches no day: no day after a holiday within its effective period"
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.dayAfterHoliday
399
- ? r.dayAfterHoliday &&
400
- (r.months === null ||
401
- (rule.months !== null && [...rule.months].every((m) => r.months.has(m))))
402
- : !r.dayAfterHoliday && intervalCovers(r, rule) && isSubset(bits, recurringBits[i])));
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);
@@ -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 IsHoliday = (date: DateString) => boolean;
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, isHoliday?: IsHoliday): boolean;
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, isHoliday: IsHoliday): boolean;
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>[];
@@ -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
- if (Number(hasWeekday) + Number(hasDay) + Number(hasAfter) !== 1) {
214
- c.error("invalid-option", p, 'must have exactly one of "weekday", "day" or "dayAfterHoliday"');
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
- if (entry["dayAfterHoliday"] !== true) {
282
- c.error("invalid-option", `${p}.dayAfterHoliday`, "must be true");
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`, 'cannot be used with "dayAfterHoliday" (the day after a holiday is never a holiday)');
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
- const onHoliday = entry["onHolidayRule"] ?? "under-holiday";
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, isHoliday) {
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 isHoliday !== undefined && !isHoliday(date) && isHoliday(addDays(date, -1));
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, isHoliday) {
356
- return inPeriod(rule, toDayNumber(date)) && matchPattern(rule, date, isHoliday);
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 isHolidaySafe(date) {
683
+ /** 振替・祝日の規則の判断に使う祝日。提供元の対応年の外は祝日でないとみなす(振替元をさかのぼる途中で範囲を出る場合のため) */
684
+ function holidayOfSafe(date) {
653
685
  try {
654
- return lookup.holidayOn(date) !== undefined;
686
+ return lookup.holidayOn(date);
655
687
  }
656
688
  catch {
657
- return false;
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, isHolidaySafe))
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, isHolidaySafe));
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, isHolidaySafe));
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, isHolidaySafe))
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);
package/dist/index.d.ts CHANGED
@@ -8,7 +8,7 @@
8
8
  * 公開する名前と型は etc/api.md に記録し、pnpm check で意図しない変化を検出する。
9
9
  */
10
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";
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";
@@ -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;
@@ -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.11.1",
3
+ "version": "0.12.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",