bizdays-toolkit 0.7.0 → 0.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,48 @@
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [0.8.1] - 2026-10-18
8
+
9
+ ライブラリのコードは 0.8.0 と同じ。動作を確かめる環境を広げ、README の記述を確かめた範囲に合わせた。
10
+
11
+ ### Changed
12
+
13
+ - README の動作環境の記述を、CI で確かめた範囲に合わせた。「Node.js・ブラウザー・エッジランタイムで動作」を、確かめている環境(Node.js・Chrome・Firefox・Edge・Safari・Cloudflare Workers)の明記に改め、確かめていない環境(Deno・Bun・Vercel の Edge Runtime など)も書いた。Temporal は形で判別していることと、本物の Temporal を備えた環境では CI で確かめていることを書いた。サイズの値に測った版(0.8.1)を添えた。エラーコードの一覧に `invalid-input` を足した
14
+ - README に、CI で確かめている Node.js の版(22.12・22・24・26)と、CommonJS の `require()` で読み込めることを CI で確かめていることを書いた。Node.js 22.12 では `require()` での読み込みに実験的な機能の警告が出ることも書いた(結果には影響しない)。`engines`(`>=22.12`)は変わらない
15
+
16
+ ### 開発
17
+
18
+ - 公開する `dist` を ESM(`import`)と CommonJS(`require()`)で確かめるテスト(`test/compat`、`pnpm test:compat`)を追加し、`pnpm check` に含めた。CI に `compat` ジョブを追加し、engines の下限の Node.js 22.12.0 と 26.12.0 でも確かめる(これらの版ではテストの `.ts` と npm 12 が動かないため、ビルドは Node.js 24 で行い、Node.js を切り替えて `dist` だけを確かめる)
19
+
20
+ - CI に、公開する `dist` をブラウザーと Cloudflare Workers で確かめるジョブを追加した。`browsers`(Chrome・Firefox・Edge。ランナーのドライバーを WebDriver で操作する `scripts/test-browsers.ts`)・`safari`(macOS)・`workerd`(版と SHA-256 を固定した公式のリリースの workerd で動かす `scripts/test-workerd.ts`)。確認の中身は `test/compat/smoke.mjs` に共通化し、本物の Temporal を備えた環境では Temporal のオブジェクトでも確かめる
21
+ - `pnpm check` で、README(英語・日本語)のコード例を実行し、コメントに書いた結果と比べるようにした(`scripts/check-readme-examples.ts`)
22
+ - `pnpm check`(`scripts/check-package.ts`)で、`src` が Node.js 固有の機能(`node:` のモジュール・`process`・`Buffer` など)を使っていないことを確かめるようにした
23
+ - 自前のスクリプトとテストで Node.js の警告が出たら失敗にするようにした(`scripts/strict-warnings.ts`。`test/compat` は対象外)
24
+ - `pnpm check` の最初に、開発用のツールの版を確かめる `scripts/check-toolchain.ts` を追加した(ワークフローどうしの Node.js の版、compat の行列の engines の下限、実行している Node.js・npm・pnpm と固定版の比較。固定版との違いは CI ではエラー、手元では注意の表示)
25
+ - GOV.UK の監視のワークフローに、手動実行の入力 `simulate` を追加した。架空の祝日を 1 件足して照合し(`pnpm verify:govuk --simulate`)、ラベル `govuk-data-simulation` の issue で差分の経路を確かめられる
26
+ - `pnpm check` のパッケージの中身の検査(`npm pack --dry-run`)を、Windows で Node.js の非推奨の警告(DEP0190)が出ない形で npm を起動するようにした
27
+
28
+ ## [0.8.0] - 2026-10-17
29
+
30
+ ### Added
31
+
32
+ - 英国の bank holidays の提供元を `bizdays-toolkit/uk` に追加した。地域ごとに `england`(`GB-ENG`)・`wales`(`GB-WLS`)・`scotland`(`GB-SCT`)・`northernIreland`(`GB-NIR`)の 4 つで、タイムゾーンは `Europe/London`、対応年は 2012〜2099 年。2012 年から GOV.UK が公表している最後の年(2028 年)までのすべての日付と名称が、GOV.UK のデータ(現在の `https://www.gov.uk/bank-holidays.json` と、それより前の年は GOV.UK の公式リポジトリ alphagov/calendars・alphagov/frontend の履歴)と一致することをテストで確かめている。それより後の年は規則による予測(`confirmed: false`)。GOV.UK では England と Wales が 1 つの区分だが、別の提供元にした(日付は現在同じ)。Wales の祝日には、GOV.UK のウェールズ語版の名称を `names.cy` に付ける。種別(`kind`)は `"regular"`(毎年の祝日。布告で移動した年を含む)・`"substitute"`(振替)・`"special"`(布告による臨時の祝日)で、型は `UkHolidayKind`・`KnownUkHolidayKind`・`UkHoliday`
33
+ - `pnpm verify:govuk`(`scripts/verify-govuk.ts`)を追加した。GOV.UK の JSON とウェールズ語の名称(alphagov/frontend の翻訳ファイル)を取得し、ライブラリの計算結果・テスト用フィクスチャと照合する
34
+
35
+ ### Changed
36
+
37
+ - 公式データの反映(法改正・官報公表・布告による祝日の新設・移動・臨時の祝日、春分・秋分の日の確定、GOV.UK の新しい年の公表)を、マイナー版ではなく**パッチ版**で公開する方針に改めた(RELEASING.md)。0.x の期間は `^0.8.0` が 0.9.0 を受け取らないため、マイナー版では臨時の祝日が利用者に届かない。パッチ版なら 0.x でも 1.0.0 以降でも、`^`・`~` のどちらの指定にも届く
38
+ - README を英語を既定にした(`README.md`)。日本語版は `docs/README.ja.md` に移し、それぞれの冒頭に切り替えのリンクを置いた。npm に公開するファイルは変わらない(日本語版はパッケージに含めない。npm はルートの `README*` を `files` の指定にかかわらず含めるため、`docs/` に置く)。README に書いている minify + gzip のサイズを測り直し、日本の祝日込みを約 8.5KB から約 9.0KB に直した(0.7.0 までの記載が実際より小さかった)。英国の祝日込みは約 9.0KB
39
+
40
+ ### 開発
41
+
42
+ - `bizdays-toolkit/uk` のサイズの上限(gzip 6,700 バイト)を `size-limits.json` に追加した(エントリーの追加。計測は 6,225 バイト)。README の minify + gzip のサイズは、ルートのすべてと国の提供元(`jp`、または英国の 4 つ)を読み込む入口を esbuild で bundle・minify して gzip で測った(ルート 7,394 バイト、日本 8,953 バイト、英国 9,043 バイト)
43
+ - GOV.UK の bank holidays を毎日照合するワークフロー(`.github/workflows/govuk-watch.yml`)を追加した。差分があれば `pnpm verify:govuk` の結果と alphagov/frontend の該当ファイルの最新のコミットを本文にした issue(ラベル `govuk-data`)を立て、同じ issue が開いていれば内容が変わったときだけ本文を更新してコメントする。取得に失敗した場合も、監視が機能していないことを issue で知らせる。60 日間動きのないリポジトリで定期実行が止まらないよう、実行のたびにワークフローを有効化し直す。フィクスチャはワークフローでは更新せず、規則の修正と同じコミットで更新する
44
+ - CI に、ワークフローを actionlint(1.7.12。版と SHA-256 を固定した公式のリリースを取得)で検査するジョブを追加した。`run:` のシェルスクリプトもランナーの shellcheck で検査する。ワークフローの誤りは `pnpm check` では見つからないため
45
+ - CI・リリース・監視のワークフローの Node.js を 24.21.0 から 24.22.0 に、pnpm を 12.11.2 から 12.14.0 に上げた(手元の開発環境に合わせる)
46
+ - `pnpm check`(`scripts/check-package.ts`)で、`README.md` と `docs/README.ja.md` の見出しの数(段ごと)とコードブロックの数が一致し、互いへの切り替えのリンクがあることを確かめるようにした
47
+ - `pnpm check` が、パッケージの中身を `files` の指定からの推定ではなく、実際の `npm pack --dry-run --json` の結果で確かめるようにした(npm が指定にかかわらず含めるファイルを見落とさないため)。ルートに `README.ja.md` があれば失敗する
48
+
7
49
  ## [0.7.0] - 2026-10-16
8
50
 
9
51
  ### Added
@@ -158,7 +200,9 @@
158
200
 
159
201
  - 日本: 2026 年 2 月の官報(2027 年の春分・秋分の日)まで反映(`dataVersion: "2026-02"`、`confirmedThrough: 2027`)
160
202
 
161
- [Unreleased]: https://github.com/inohan77/bizdays-toolkit/compare/v0.7.0...HEAD
203
+ [Unreleased]: https://github.com/inohan77/bizdays-toolkit/compare/v0.8.1...HEAD
204
+ [0.8.1]: https://github.com/inohan77/bizdays-toolkit/compare/v0.8.0...v0.8.1
205
+ [0.8.0]: https://github.com/inohan77/bizdays-toolkit/compare/v0.7.0...v0.8.0
162
206
  [0.7.0]: https://github.com/inohan77/bizdays-toolkit/compare/v0.6.0...v0.7.0
163
207
  [0.6.0]: https://github.com/inohan77/bizdays-toolkit/compare/v0.5.0...v0.6.0
164
208
  [0.5.0]: https://github.com/inohan77/bizdays-toolkit/compare/v0.4.0...v0.5.0
package/README.md CHANGED
@@ -14,31 +14,32 @@
14
14
  <img src="https://img.shields.io/badge/types-included-blue" alt="TypeScript types included">
15
15
  </p>
16
16
 
17
- 祝日と、会社・店舗の休業日を組み合わせた営業日カレンダーです。
17
+ <p align="center"><b>English</b> | <a href="https://github.com/inohan77/bizdays-toolkit/blob/main/docs/README.ja.md">日本語</a></p>
18
18
 
19
- - **日本の祝日を内閣府のデータで全日照合**: 1955 年〜官報公表済みの年まで、内閣府「国民の祝日」CSV のすべての日付と一致することをテストで確かめています(振替休日・国民の休日・皇室行事による休日・五輪特措法による移動を含む)
20
- - **繰り返しの規則**: 「第 3 水曜定休」「毎月末日は棚卸」「月曜休館(祝日の場合は翌平日)」のような規則を、祝日に当たったときの扱い(祝日に従う・祝日でも当てはめる・翌日に振り替える)とあわせて指定できます
21
- - **日ごとの値(営業時間など)**: 「火曜定休、祝日は日曜の営業時間、元日は休業、大晦日は短縮営業」のように、曜日・祝日・特定日で変わる値を同じ規則で引けます
22
- - **設定の診断**: `bizdays-toolkit/diagnose` の `diagnoseCalendar`・`diagnoseSchedule` が、決して使われない規則・取り除いても結果が変わらない規則・キーの打ち間違い・営業日が 1 日もない設定などを、例外を投げずに一覧で返します。テストに 1 行足せば、設定の変更で規則が死んでいないかを確かめられます
23
- - **月の期日**: 「20 日締め翌月 10 日払い(休業日なら翌営業日)」「月末の最終営業日」のような期日を、金融で使われる 4 つの調整方法(following・preceding・modified-following・modified-preceding)で計算します
24
- - **営業日の計算**: 定休日の曜日・毎年の休業日(年末年始のような年をまたぐ期間も可)・臨時休業日・臨時営業日と祝日を組み合わせ、「N 営業日後」「締め時刻を過ぎたら翌営業日」などを計算します
25
- - **タイムゾーンを間違えない**: `Date` などの瞬間は、カレンダーのタイムゾーン(日本は `Asia/Tokyo`)で暦日に変換してから判定します。UTC で動くサーバーでも、日本時間 0〜9 時が前日扱いになりません
26
- - **軽量・依存ゼロ**: minify + gzip で約 7.4KB(日本の祝日込みで約 8.5KB)。ESM・型定義同梱・`sideEffects: false` で、Node.js・ブラウザー・Edge ランタイムで動作します
27
- - **他の国・地域に拡張できる**: 祝日の提供元はインターフェース(`HolidayProvider`)で差し替えられます。現在は日本のみ同梱しています
19
+ A business-day calendar that combines public holidays with company or store closures.
28
20
 
29
- [English](#english)
21
+ - **Japanese holidays verified day by day against the Cabinet Office data**: from 1955 through the latest year announced in the Official Gazette, every date in the Cabinet Office "National Holidays" CSV is checked in the tests (substitute holidays, citizens' holidays, holidays for imperial ceremonies and the Olympic moves included)
22
+ - **UK bank holidays verified day by day against GOV.UK**: choose England, Wales, Scotland or Northern Ireland. From 2012 through the last year GOV.UK has published, every date and name is checked against the GOV.UK data in the tests, and differences are watched daily
23
+ - **Recurring rules**: rules such as "closed on the 3rd Wednesday", "stocktaking on the last day of the month" or "closed on Mondays (on the next weekday if Monday is a holiday)", together with what happens when they fall on a holiday (follow the holiday, apply anyway, or move to the next day)
24
+ - **Day-by-day values (e.g. store hours)**: values that change by weekday, holiday and specific date, such as "closed on Tuesdays, Sunday hours on holidays, closed on New Year's Day, short hours on New Year's Eve", are looked up with the same rules
25
+ - **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
+ - **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
+ - **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
+ - **Time-zone safe**: instants such as `Date` are converted to a civil date in the calendar's time zone (`Asia/Tokyo` for Japan, `Europe/London` for the UK) before evaluation. A server running in UTC does not treat 00:00–09:00 in Japan as the previous day
29
+ - **Lightweight, zero dependencies**: about 7.4 KB min+gzip (about 9.0 KB with Japanese holidays, about 9.0 KB with UK bank holidays; measured for 0.8.1). 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
+ - **Extensible to other countries and regions**: holiday providers are pluggable through the `HolidayProvider` interface. Japan and the UK are bundled
30
31
 
31
- ## インストール
32
+ ## Installation
32
33
 
33
34
  ```sh
34
35
  pnpm add bizdays-toolkit
35
36
  ```
36
37
 
37
- Node.js 22.12 以上。ESM のみですが、Node.js 22.12 以降は CommonJS からも `require()` で読み込めます。
38
+ 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.
38
39
 
39
- ## 使い方
40
+ ## Usage
40
41
 
41
- ### 祝日を調べる
42
+ ### Looking up holidays
42
43
 
43
44
  ```ts
44
45
  import { getHoliday, holidaysInYear, isHoliday } from "bizdays-toolkit/jp";
@@ -48,15 +49,46 @@ getHoliday("2026-09-22");
48
49
  // { date: "2026-09-22", name: "国民の休日", nameEn: "Citizens' Holiday", kind: "citizens", confirmed: true }
49
50
  holidaysInYear(2027);
50
51
 
51
- // 瞬間(Date)は日本時間で判定する
52
- isHoliday(new Date("2026-11-22T15:30:00Z")); // true(日本時間では 11/23)
52
+ // Instants (Date) are evaluated in Japan time
53
+ isHoliday(new Date("2026-11-22T15:30:00Z")); // true (Nov 23 in Japan time)
54
+ ```
55
+
56
+ `kind` is one of `national` (national holiday), `substitute` (substitute holiday), `citizens` (citizens' holiday, a day between two holidays) and `special` (holidays for imperial ceremonies and similar events).
57
+
58
+ `confirmed` tells whether the date is officially fixed. The vernal and autumnal equinox days are fixed in the Official Gazette in February of the previous year, so later years are predictions from an approximation formula (`confirmed: false`).
59
+
60
+ ### UK bank holidays
61
+
62
+ ```ts
63
+ import { createCalendar } from "bizdays-toolkit";
64
+ import { england, northernIreland, scotland, wales } from "bizdays-toolkit/uk";
65
+
66
+ const edinburgh = createCalendar({ holidays: scotland, closedWeekdays: [0, 6] });
67
+ edinburgh.isBusinessDay("2026-01-02"); // false (2nd January)
68
+ edinburgh.addBusinessDays("2026-06-12", 1); // "2026-06-16" (June 15 is the World Cup bank holiday)
69
+
70
+ scotland.holidaysInYear(2026)[0];
71
+ // { date: "2026-01-01", name: "New Year’s Day", nameEn: "New Year’s Day", kind: "regular", confirmed: true }
72
+ wales.holidaysInYear(2026)[0]?.names; // { cy: "Dydd Calan" }
73
+
74
+ // Across several regions, use the union (a holiday in any of them is a holiday)
75
+ const ukWide = createCalendar({ holidays: [england, scotland, northernIreland] });
53
76
  ```
54
77
 
55
- `kind` は `national`(国民の祝日)・`substitute`(振替休日)・`citizens`(国民の休日)・`special`(皇室行事などによる休日)のいずれかです。
78
+ | Provider | ID | GOV.UK division | Notes |
79
+ | ----------------- | -------- | ----------------- | ------------------------------------------------ |
80
+ | `england` | `GB-ENG` | England and Wales | |
81
+ | `wales` | `GB-WLS` | England and Wales | Welsh names (`names.cy`) included |
82
+ | `scotland` | `GB-SCT` | Scotland | 2nd January, St Andrew’s Day and others |
83
+ | `northernIreland` | `GB-NIR` | Northern Ireland | St Patrick’s Day, Battle of the Boyne and others |
56
84
 
57
- `confirmed` は、その日付が公式に確定しているかを表します。春分・秋分の日は前年 2 月の官報で確定するため、それより先の年は近似式による予測(`confirmed: false`)です。
85
+ - `kind` is one of `regular` (an annual bank holiday, including years when a proclamation moved it), `substitute` (a substitute day for a bank holiday on a Saturday or Sunday) and `special` (a one-off bank holiday by proclamation, such as a jubilee or a state funeral). A substitute day has the name of the original holiday, as on GOV.UK
86
+ - England and Wales are one division on GOV.UK and currently have the same dates, but they are separate providers, so your code keeps working if they ever differ
87
+ - Supported years are 2012–2099. Years after the last year GOV.UK has published (`confirmedThrough`, currently 2028) are predictions from the rules (`confirmed: false`). One-off bank holidays are unknown until announced, so predicted years never contain them
88
+ - Local holidays set by Scottish councils are not included. Whether a bank holiday is a day off depends on the employment contract and similar arrangements
89
+ - One-off bank holidays may be announced only about ten days ahead. Differences from GOV.UK are watched daily and released as patch versions
58
90
 
59
- ### 営業日カレンダー
91
+ ### Business-day calendars
60
92
 
61
93
  ```ts
62
94
  import { createCalendar } from "bizdays-toolkit";
@@ -64,97 +96,102 @@ import { jp } from "bizdays-toolkit/jp";
64
96
 
65
97
  const shipping = createCalendar({
66
98
  holidays: jp,
67
- closedWeekdays: [0], // 日曜日(0 = 日曜 … 6 = 土曜)
68
- closedAnnual: [{ from: "12-31", to: "01-03", name: "年末年始" }],
69
- closedDates: [{ from: "2027-08-12", to: "2027-08-16", name: "夏季休業" }],
70
- openDates: ["2026-12-27"], // 臨時営業日
71
- holidayPolicy: "closed", // 祝日は休み("open": 営業 / "ignore": 判定に使わない)
99
+ closedWeekdays: [0], // Sunday (0 = Sunday … 6 = Saturday)
100
+ closedAnnual: [{ from: "12-31", to: "01-03", name: "New Year closure" }],
101
+ closedDates: [{ from: "2027-08-12", to: "2027-08-16", name: "Summer closure" }],
102
+ openDates: ["2026-12-27"], // a specific open day
103
+ holidayPolicy: "closed", // closed on holidays ("open": open / "ignore": not used)
72
104
  });
73
105
 
74
- shipping.isBusinessDay("2026-11-23"); // false(勤労感謝の日)
106
+ shipping.isBusinessDay("2026-11-23"); // false (Labor Thanksgiving Day)
75
107
  shipping.addBusinessDays("2026-12-30", 1); // "2027-01-04"
76
- shipping.businessDaysBetween("2026-09-01", "2026-10-01"); // from 以上 to 未満の営業日数
108
+ shipping.businessDaysBetween("2026-09-01", "2026-10-01"); // business days from (inclusive) to (exclusive)
77
109
 
78
- // 14 時より前の注文は当日、それ以降は翌営業日に出荷
110
+ // Orders before 14:00 ship today, later orders on the next business day
79
111
  shipping.businessDayOnOrAfter(new Date(), { cutoff: "14:00" });
80
112
 
81
113
  shipping.dayInfo("2027-01-01");
82
- // { date: "2027-01-01", weekday: 5, businessDay: false, reason: "closed-annual", name: "年末年始", holiday: {…} }
114
+ // { date: "2027-01-01", weekday: 5, businessDay: false, reason: "closed-annual", name: "New Year closure", holiday: {…} }
83
115
  ```
84
116
 
85
- 1 日の判定は次の優先順位で行います(上が優先)。
117
+ Each day is decided in this order of priority (top first).
86
118
 
87
- 1. `openDates`(臨時営業日)
88
- 2. `closedDates`(臨時休業日)
89
- 3. `closedAnnual`(毎年の休業日)
90
- 4. `openRecurring`・`closedRecurring` のうち `onHolidayRule: "over-holiday"` の規則(祝日でも当てはめる)
91
- 5. 祝日(`holidayPolicy` に従う。`"open"` は定休日の曜日より優先して営業日にする)
92
- 6. `openRecurring`・`closedRecurring`(既定の `"under-holiday"` と、`"after-holiday"` の振替先)
93
- 7. `closedWeekdays`(定休日の曜日)
94
- 8. それ以外は営業日
119
+ 1. `openDates` (specific open days)
120
+ 2. `closedDates` (specific closed days)
121
+ 3. `closedAnnual` (annual closures)
122
+ 4. `openRecurring` and `closedRecurring` rules with `onHolidayRule: "over-holiday"` (applied even on holidays)
123
+ 5. Holidays (according to `holidayPolicy`; `"open"` makes them business days even on closed weekdays)
124
+ 6. `openRecurring` and `closedRecurring` (the default `"under-holiday"`, and the days moved to by `"after-holiday"`)
125
+ 7. `closedWeekdays` (closed weekdays)
126
+ 8. Otherwise a business day
95
127
 
96
- この順番は固定です。繰り返しの規則と祝日の関係だけは、規則ごとに `onHolidayRule` で選べます(次の節)。
128
+ This order is fixed. Only the relation between a recurring rule and holidays can be chosen per rule with `onHolidayRule` (next section).
97
129
 
98
- ### 繰り返しの規則(毎週・毎月の第 n 曜日・毎月の日)
130
+ ### Recurring rules (weekly, nth weekday, day of month)
99
131
 
100
- 「第 3 水曜定休」「第 2・第 4 土曜休業」「毎月末日は棚卸」「月曜休館(祝日の場合は翌平日)」のような繰り返しの休業日・営業日は、`closedRecurring`・`openRecurring` で指定します。
132
+ Recurring closed and open days such as "closed on the 3rd Wednesday", "closed on the 2nd and 4th Saturdays", "stocktaking on the last day of the month" or "closed on Mondays (on the next weekday if Monday is a holiday)" go in `closedRecurring` and `openRecurring`.
101
133
 
102
134
  ```ts
103
135
  const museum = createCalendar({
104
136
  holidays: jp,
105
- holidayPolicy: "open", // 祝日は開館
137
+ holidayPolicy: "open", // open on holidays
106
138
  closedRecurring: [
107
- { weekday: 1, name: "月曜休館", onHolidayRule: "after-holiday" }, // 祝日の場合は翌平日に休館
108
- { weekday: 3, nth: 3, months: [2, 8], name: "2 月・8 月の第 3 水曜は臨時休館" },
109
- { day: -1, name: "月末棚卸", onHolidayRule: "over-holiday" }, // 祝日でも休館
139
+ { weekday: 1, name: "Closed on Mondays", onHolidayRule: "after-holiday" }, // closed on the next weekday instead
140
+ {
141
+ weekday: 3,
142
+ nth: 3,
143
+ months: [2, 8],
144
+ name: "Closed on the 3rd Wednesday in February and August",
145
+ },
146
+ { day: -1, name: "Stocktaking", onHolidayRule: "over-holiday" }, // closed even on holidays
110
147
  ],
111
148
  });
112
149
 
113
- museum.isBusinessDay("2026-10-12"); // true(スポーツの日の月曜は開館)
150
+ museum.isBusinessDay("2026-10-12"); // true (open on Monday, Sports Day)
114
151
  museum.dayInfo("2026-10-13");
115
152
  // { date: "2026-10-13", weekday: 2, businessDay: false, reason: "closed-recurring",
116
- // name: "月曜休館", shiftedFrom: "2026-10-12" }
153
+ // name: "Closed on Mondays", shiftedFrom: "2026-10-12" }
117
154
  ```
118
155
 
119
- | 書き方 | 意味 |
120
- | ------------------------ | ------------------------------------------------------------------------------- |
121
- | `{ weekday: 1 }` | 毎週月曜日 |
122
- | `{ weekday: 3, nth: 3 }` | 第 3 水曜日。`nth` は 1〜5 と -1(最終)。`[2, 4]` のように配列で複数指定できる |
123
- | `{ day: 20 }` | 毎月 20 日。`day` は 1〜31 と -1(末日)。配列で複数指定できる |
124
- | `months: [8, 12]` | どの書き方にも付けられる、月の絞り込み |
156
+ | Pattern | Meaning |
157
+ | ------------------------ | ------------------------------------------------------------------------------------- |
158
+ | `{ weekday: 1 }` | Every Monday |
159
+ | `{ weekday: 3, nth: 3 }` | The 3rd Wednesday. `nth` is 1–5 or -1 (last); an array such as `[2, 4]` gives several |
160
+ | `{ day: 20 }` | The 20th of every month. `day` is 1–31 or -1 (last day); an array gives several |
161
+ | `months: [8, 12]` | Narrows any pattern to these months |
125
162
 
126
- - **第 n 何曜日**は「その月で n 回目のその曜日」です(1〜7 日が第 1)。カレンダーの第 n 週の行にある曜日ではありません
127
- - **その月にない日**(`nth: 5` がない月、`day: 31` がない月)は当てはまりません。末日は `day: -1` で指定します
128
- - 繰り返しの規則どうしは配列で先に書いたものが優先で、`openRecurring` は `closedRecurring` より優先です
163
+ - **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
164
+ - **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
165
+ - Among recurring rules, the one written first in the array wins, and `openRecurring` wins over `closedRecurring`
129
166
 
130
- **祝日に当たったときの扱い**(`onHolidayRule`)は、規則ごとに選べます。
167
+ **What happens on a holiday** (`onHolidayRule`) is chosen per rule.
131
168
 
132
- | 値 | 意味 | 店舗の告知での書き方 |
133
- | ------------------------- | -------------------------------------------------------- | ------------------------------------- |
134
- | `"under-holiday"`(既定) | 祝日の下。祝日の扱い(`holidayPolicy`)に従う | 「第 3 水曜定休(祝日の場合は営業)」 |
135
- | `"over-holiday"` | 祝日の上。祝日でも当てはめる | 「第 3 水曜定休(祝日も休み)」 |
136
- | `"after-holiday"` | 祝日の後。祝日には当てはめず、祝日でない翌日に振り替える | 「月曜休館(祝日の場合は翌平日)」 |
169
+ | Value | Meaning | How a store would announce it |
170
+ | --------------------------- | ------------------------------------------------------------------------- | ------------------------------------------------------- |
171
+ | `"under-holiday"` (default) | Under the holiday: follows the holiday handling (`holidayPolicy`) | "Closed on the 3rd Wednesday (open if it is a holiday)" |
172
+ | `"over-holiday"` | Over the holiday: applies even on holidays | "Closed on the 3rd Wednesday (holidays included)" |
173
+ | `"after-holiday"` | After the holiday: not applied on holidays, moved to the next non-holiday | "Closed on Mondays (on the next weekday if a holiday)" |
137
174
 
138
- - `"under-holiday"` の規則は、`holidayPolicy: "ignore"`(祝日を判定に使わない)なら祝日にも当てはまります
139
- - `"after-holiday"` の振替先は「その日以降で最初の祝日でない日」です。祝日が続く場合(GW など)は連休が明けた日になります。振替先の日に臨時営業日・臨時休業日・毎年の休業日があれば、そちらが優先です。振替で決まった日の `dayInfo()` には、振替の元の日付 `shiftedFrom` が付きます
140
- - 振替の判断に使う「祝日」は、祝日の提供元(`jp` など)の祝日だけです(毎年の休業日などは含みません)
175
+ - With `holidayPolicy: "ignore"` (holidays not used), `"under-holiday"` rules also apply on holidays
176
+ - 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`
177
+ - The "holidays" used for moving are only those of the holiday provider (such as `jp`), not annual closures
141
178
 
142
- スケジュールでは、同じ書き方の規則に `value` を付けて `recurring` に指定します。
179
+ In a schedule, the same patterns with a `value` go in `recurring`.
143
180
 
144
181
  ```ts
145
182
  const hours = createSchedule<Hours>({
146
183
  holidays: jp,
147
184
  weekly: [sunday, weekday, weekday, weekday, weekday, weekday, weekday],
148
185
  recurring: [
149
- { weekday: 1, value: null, name: "月曜休業", onHolidayRule: "after-holiday" },
150
- { day: -1, value: { open: "10:00", close: "17:00" }, name: "月末は短縮営業" },
186
+ { weekday: 1, value: null, name: "Closed on Mondays", onHolidayRule: "after-holiday" },
187
+ { day: -1, value: { open: "10:00", close: "17:00" }, name: "Short hours at month end" },
151
188
  ],
152
189
  });
153
190
  ```
154
191
 
155
- ### よくある設定の例
192
+ ### Common recipes
156
193
 
157
- **国民の休日(祝日に挟まれた日)を営業日として扱う**:祝日の提供元を包んで、種別で除外します。
194
+ **Treat citizens' holidays (days between two holidays) as business days**: wrap the holiday provider and filter by kind.
158
195
 
159
196
  ```ts
160
197
  import { jp } from "bizdays-toolkit/jp";
@@ -166,7 +203,7 @@ const jpWithoutCitizensHoliday = {
166
203
  const calendar = createCalendar({ holidays: jpWithoutCitizensHoliday, closedWeekdays: [0, 6] });
167
204
  ```
168
205
 
169
- **共通の設定から店舗ごとのカレンダーを作る**:設定は普通のオブジェクトなので、展開して足します。
206
+ **Build per-store calendars from shared options**: options are plain objects, so spread and extend them.
170
207
 
171
208
  ```ts
172
209
  const base = {
@@ -178,9 +215,9 @@ const shibuya = createCalendar({ ...base, openDates: ["2026-12-27"] });
178
215
  const yokohama = createCalendar({ ...base, closedRecurring: [{ weekday: 3, nth: 3 }] });
179
216
  ```
180
217
 
181
- ### 月の期日と日付の調整
218
+ ### Month-based due dates and date adjustment
182
219
 
183
- 休業日に当たった日付を、指定した方法で営業日に調整します。月の第 n 営業日や、「毎月 n 日(休業日なら翌営業日)」のような期日も求められます。
220
+ Dates that fall on closed days are adjusted to business days with the convention you choose. The nth business day of a month and due dates such as "the nth of every month (next business day if closed)" are also available.
184
221
 
185
222
  ```ts
186
223
  import { closingMonth, createCalendar, monthOf } from "bizdays-toolkit";
@@ -189,113 +226,115 @@ import { jp } from "bizdays-toolkit/jp";
189
226
  const calendar = createCalendar({
190
227
  holidays: jp,
191
228
  closedWeekdays: [0, 6],
192
- closedAnnual: [{ from: "12-31", to: "01-03", name: "年末年始" }],
229
+ closedAnnual: [{ from: "12-31", to: "01-03", name: "New Year closure" }],
193
230
  });
194
231
 
195
- calendar.adjust("2026-10-31", { convention: "following" }); // "2026-11-02"(10/31 は土曜日)
196
- calendar.adjust("2026-10-31", { convention: "modified-following" }); // "2026-10-30"(月が変わるので前へ)
232
+ calendar.adjust("2026-10-31", { convention: "following" }); // "2026-11-02" (Oct 31 is a Saturday)
233
+ calendar.adjust("2026-10-31", { convention: "modified-following" }); // "2026-10-30" (the month would change, so backwards)
197
234
 
198
235
  calendar.firstBusinessDayOfMonth("2027-01"); // "2027-01-04"
199
236
  calendar.lastBusinessDayOfMonth("2026-12"); // "2026-12-30"
200
- calendar.nthBusinessDayOfMonth("2026-10", 5); // "2026-10-07"(-1 は最終、-2 は最後から 2 番目)
237
+ calendar.nthBusinessDayOfMonth("2026-10", 5); // "2026-10-07" (-1 is the last, -2 the second to last)
201
238
 
202
- // 毎月 10 日払い(休業日なら翌営業日)
203
- calendar.dueDate("2026-10", { day: 10, convention: "following" }); // "2026-10-13"(12 日はスポーツの日)
239
+ // Paid on the 10th of every month (next business day if closed)
240
+ calendar.dueDate("2026-10", { day: 10, convention: "following" }); // "2026-10-13" (Oct 12 is Sports Day)
204
241
 
205
- // 20 日締め・翌月末払い(休業日なら前営業日)
206
- const billing = closingMonth("2026-10-21", 20); // "2026-11"(10/21〜11/20 が 11 月分)
242
+ // Closing on the 20th, paid at the end of the next month (previous business day if closed)
243
+ const billing = closingMonth("2026-10-21", 20); // "2026-11" (Oct 21 to Nov 20 belongs to November)
207
244
  calendar.dueDate(monthOf(billing, 1), { day: -1, convention: "preceding" }); // "2026-12-30"
208
245
  ```
209
246
 
210
- **調整の方法**(`convention`、必須)
247
+ **Adjustment conventions** (`convention`, required)
211
248
 
212
- | 方法 | 休業日のとき |
213
- | ---------------------- | -------------------------------------- |
214
- | `"following"` | 翌営業日 |
215
- | `"preceding"` | 前営業日 |
216
- | `"modified-following"` | 翌営業日。ただし月が変わるなら前営業日 |
217
- | `"modified-preceding"` | 前営業日。ただし月が変わるなら翌営業日 |
249
+ | Convention | On a closed day |
250
+ | ---------------------- | -------------------------------------------------------------------- |
251
+ | `"following"` | The next business day |
252
+ | `"preceding"` | The previous business day |
253
+ | `"modified-following"` | The next business day, or the previous one if the month would change |
254
+ | `"modified-preceding"` | The previous business day, or the next one if the month would change |
218
255
 
219
- - 営業日ならどの方法でもその日のままです
220
- - 月が変わるかは、**調整する前の日付の月**と比べます。`dueDate` では、末日に読み替えた後の日付の月です(例: 2 月の 31 日払いは 2 月末日を基準にし、`"modified-following"` なら 2 月の中で調整します)
221
- - `modified-…` で、同じ月に反対向きの営業日もない(その月が全部休業日)ときは、月が変わっても元の向きに調整します
222
- - 「最も近い営業日」(nearest)はありません。前後が同じ距離のときの決め方が業種ごとに違うためです
256
+ - On a business day, every convention returns the same day
257
+ - Whether the month changes is judged against **the month of the date before adjustment**. For `dueDate` it is the date after mapping to the last day of the month (for example, payment on the 31st in February uses the last day of February, and `"modified-following"` adjusts within February)
258
+ - With `modified-…`, if the month has no business day in the opposite direction either (the whole month is closed), the date is adjusted in the original direction even though the month changes
259
+ - There is no "nearest business day" (nearest), because the tie-breaking rule for equal distances differs by industry
223
260
 
224
- **月の計算**(ルートの関数。営業日は考えません)
261
+ **Month helpers** (root functions; business days are not considered)
225
262
 
226
- - `monthOf(dateOrMonth, offset?)`: 日付または月 `"YYYY-MM"` の、`offset` か月後(負なら前)の月
227
- - `closingMonth(date, closingDay)`: 締め日(1〜31、`-1` は末日)で区切ったときに日付が属する月。締め日の翌日から次の締め日までを、次の締め日の月とします。締め日がその月にない日(31 日締めの 2 月など)は末日締めです
263
+ - `monthOf(dateOrMonth, offset?)`: the month `offset` months after (before, if negative) a date or a month `"YYYY-MM"`
264
+ - `closingMonth(date, closingDay)`: the month a date belongs to when months are closed on `closingDay` (1–31, `-1` for the last day). The day after a closing day up to the next closing day belongs to the month of the next closing day. If the closing day does not exist in a month (closing on the 31st in February), the last day is used
228
265
 
229
- `dueDate` の `day` も、その月にない日(2 月 30 日など)は末日に読み替えます。繰り返しの規則(`{ day: 31 }`)はその月にない日に当てはまらないので、扱いが異なります。期日は「その月のどこか」に必ず決まる必要があるためです。
266
+ The `day` of `dueDate` also maps days that do not exist in a month (such as February 30) to the last day. Recurring rules (`{ day: 31 }`) do not match such days, so the two behave differently: a due date must always fall somewhere in its month.
230
267
 
231
- ### 日ごとの値(営業時間など)
268
+ ### Day-by-day values (e.g. store hours)
232
269
 
233
- `createSchedule` は、曜日・祝日・特定日の規則から、日ごとに任意の値を返します。値の型は自由なので、営業時間のほか、受付枠や配送料の区分などにも使えます。
270
+ `createSchedule` returns any value per day from weekday, holiday and specific-date rules. The value type is up to you, so it also fits booking slots, delivery fee classes and more.
234
271
 
235
272
  ```ts
236
273
  import { createSchedule } from "bizdays-toolkit";
237
274
  import { jp } from "bizdays-toolkit/jp";
238
275
 
239
- type Hours = { open: string; close: string } | null; // null は休業
276
+ type Hours = { open: string; close: string } | null; // null means closed
240
277
  const weekday = { open: "10:00", close: "21:00" };
241
278
  const sunday = { open: "10:00", close: "19:00" };
242
279
 
243
280
  const storeHours = createSchedule<Hours>({
244
281
  holidays: jp,
245
- weekly: [sunday, weekday, null, weekday, weekday, weekday, weekday], // [日, 月, …, 土]。火曜定休
246
- holiday: { sameAs: 0 }, // 祝日は日曜の値("weekday": 曜日どおり / { value: … }: 指定の値)
247
- annual: [{ date: "01-01", value: null, name: "元日休業" }], // 毎年の月日(期間も可)
248
- dates: [{ date: "2026-12-31", value: { open: "10:00", close: "17:00" }, name: "大晦日" }],
282
+ weekly: [sunday, weekday, null, weekday, weekday, weekday, weekday], // [Sun, Mon, …, Sat]; closed on Tuesdays
283
+ holiday: { sameAs: 0 }, // Sunday hours on holidays ("weekday": by weekday / { value: … }: a fixed value)
284
+ annual: [{ date: "01-01", value: null, name: "Closed on New Year's Day" }], // annual dates (ranges too)
285
+ dates: [{ date: "2026-12-31", value: { open: "10:00", close: "17:00" }, name: "New Year's Eve" }],
249
286
  });
250
287
 
251
- storeHours.valueOn("2026-11-03"); // { open: "10:00", close: "19:00" }(文化の日は日曜の営業時間)
288
+ storeHours.valueOn("2026-11-03"); // { open: "10:00", close: "19:00" } (Culture Day uses Sunday hours)
252
289
  storeHours.dayInfo("2027-01-01");
253
- // { date: "2027-01-01", weekday: 5, value: null, reason: "annual", name: "元日休業", holiday: {…} }
290
+ // { date: "2027-01-01", weekday: 5, value: null, reason: "annual", name: "Closed on New Year's Day", holiday: {…} }
254
291
  ```
255
292
 
256
- 値は `dates`(個別の日付・期間)→ `annual`(毎年の月日・期間)→ `recurring`(`"over-holiday"`)→ 祝日 → `recurring`(既定・振替先)→ `weekly`(曜日)の順に決まります。同じ種類の規則が重なる場合は、配列で先に書いたものが優先です。
293
+ 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.
257
294
 
258
- 「今、営業時間内か」のような時刻の範囲の判定は、業種ごとの決まり(深夜をまたぐ営業、休憩時間など)があるためライブラリでは行いません。`civilOf` でタイムゾーンでの暦日と時刻を取り出せば、アプリ側で簡単に書けます。
295
+ Checking a time range such as "is the store open now" is left to your app, because industries differ (opening past midnight, breaks and so on). With `civilOf` you get the civil date and time in the time zone, and the check is short.
259
296
 
260
297
  ```ts
261
- const { date, time } = storeHours.civilOf(new Date()); // { date: "2026-10-14", time: "16:30" }(日本時間)
298
+ const { date, time } = storeHours.civilOf(new Date()); // { date: "2026-10-14", time: "16:30" } (Japan time)
262
299
  const hours = storeHours.valueOn(date);
263
300
  const isOpen = hours !== null && time !== null && hours.open <= time && time < hours.close;
264
301
  ```
265
302
 
266
- `civilOf` は営業日カレンダーにもあり、`bizdays-toolkit/jp` の `toJapanDateTime`、ルートの `toDateTimeParts(input, timeZone)` でも同じ変換ができます。
303
+ `civilOf` is also on business-day calendars, and the same conversion is available as `toJapanDateTime` in `bizdays-toolkit/jp` and `toDateTimeParts(input, timeZone)` in the root.
304
+
305
+ ### Date and date-time inputs
267
306
 
268
- ### 日付・日時の入力
307
+ Every method accepts the following inputs. Returned dates are always `"YYYY-MM-DD"` strings.
269
308
 
270
- すべてのメソッドは次の入力を受け付けます。戻り値の日付は常に `"YYYY-MM-DD"` 文字列です。
309
+ | Input | Treated as |
310
+ | ---------------------------------------------------- | ----------------------------------------------------------------- |
311
+ | `"2026-11-23"` | A civil date |
312
+ | `"2026-11-23T10:00"` | A date and time in the calendar's time zone (wall clock) |
313
+ | `"2026-11-22T15:30:00Z"`, `"2026-11-23T00:30+09:00"` | An instant, converted to a civil date in the calendar's time zone |
314
+ | `Date` | An instant |
315
+ | Temporal `PlainDate`, `PlainDateTime` | A civil date or date and time (no dependency on Temporal) |
316
+ | Temporal `Instant`, `ZonedDateTime` | An instant |
271
317
 
272
- | 入力 | 扱い |
273
- | ---------------------------------------------------- | ---------------------------------------------- |
274
- | `"2026-11-23"` | 暦日 |
275
- | `"2026-11-23T10:00"` | カレンダーのタイムゾーンでの日時(壁時計) |
276
- | `"2026-11-22T15:30:00Z"`・`"2026-11-23T00:30+09:00"` | 瞬間。カレンダーのタイムゾーンで暦日に変換する |
277
- | `Date` | 瞬間 |
278
- | Temporal の `PlainDate`・`PlainDateTime` | 暦日・日時(Temporal への依存はありません) |
279
- | Temporal の `Instant`・`ZonedDateTime` | 瞬間 |
318
+ Temporal objects are recognized by their shape, so the package does not depend on Temporal. CI also checks real Temporal objects in the runtimes that provide Temporal (the CI log shows whether each runtime had it).
280
319
 
281
- カレンダーのタイムゾーンは、`timeZone` オプション、祝日の提供元のタイムゾーン、`"UTC"` の順に決まります。
320
+ The calendar's time zone is the `timeZone` option, then the holiday provider's time zone, then `"UTC"`.
282
321
 
283
- ### 他の国・地域の祝日
322
+ ### Holidays of other countries and regions
284
323
 
285
- `HolidayProvider` を実装して `holidays` に渡します。複数渡すと和集合になります(タイムゾーンが異なる場合は `timeZone` の指定が必要です)。
324
+ Implement `HolidayProvider` and pass it as `holidays`. Several providers give their union (if their time zones differ, `timeZone` is required).
286
325
 
287
326
  ```ts
288
327
  import { createCalendar, type HolidayProvider } from "bizdays-toolkit";
289
328
 
290
329
  const myCountry: HolidayProvider = {
291
330
  id: "XX",
292
- timeZone: "Europe/London",
331
+ timeZone: "Europe/Paris",
293
332
  supportedYears: { from: 2000, to: 2100 },
294
333
  dataVersion: "2026-10",
295
334
  holidaysInYear: (year) => [
296
335
  {
297
336
  date: `${year}-12-25`,
298
- name: "Christmas Day",
337
+ name: "Noël",
299
338
  nameEn: "Christmas Day",
300
339
  kind: "public",
301
340
  confirmed: true,
@@ -305,12 +344,12 @@ const myCountry: HolidayProvider = {
305
344
  const calendar = createCalendar({ holidays: myCountry, closedWeekdays: [0, 6] });
306
345
  ```
307
346
 
308
- 祝日には、ほかの言語の名称を `names` で付けられます。キーは BCP 47 の言語タグの正規の形(`"zh-Hant"`・`"ko"` など)です。同梱の日本の祝日は `names` を持ちません(公式の出典がある日本語・英語だけを同梱しています)。必要な場合は、提供元を包んで付けます。
347
+ Holidays can carry names in other languages in `names`. The keys are canonical BCP 47 language tags (`"zh-Hant"`, `"ko"` and so on). The bundled Japanese holidays have no `names` (only the officially sourced Japanese and English names are bundled); among the UK providers, only Wales has Welsh names. If you need more, wrap the provider.
309
348
 
310
349
  ```ts
311
350
  import { jp } from "bizdays-toolkit/jp";
312
351
 
313
- const zhHant: Record<string, string> = { "2026-01-01": "元日" /* … 利用者が用意した訳 */ };
352
+ const zhHant: Record<string, string> = { "2026-01-01": "元日" /* … your own translations */ };
314
353
  const jpWithNames = {
315
354
  ...jp,
316
355
  holidaysInYear: (year: number) =>
@@ -322,13 +361,13 @@ const jpWithNames = {
322
361
  createCalendar({ holidays: jpWithNames }).getHoliday("2026-01-01")?.names; // { "zh-Hant": "元日" }
323
362
  ```
324
363
 
325
- ### 対応年の外
364
+ ### Outside the supported years
326
365
 
327
- 祝日の提供元の対応年(日本は 1955〜2099 年)を外れた日付を判定すると `RangeError` になります。祝日がない日として扱いたい場合は `outOfRange: "ignore"` を指定してください。
366
+ Evaluating a date outside the holiday provider's supported years (1955–2099 for Japan, 2012–2099 for the UK) throws a `RangeError`. To treat such dates as having no holidays, set `outOfRange: "ignore"`.
328
367
 
329
- ### エラー
368
+ ### Errors
330
369
 
331
- 設定や入力の誤りは、従来どおり `RangeError`・`TypeError` などの例外になり、`code` を持ちます。メッセージの文言は変わることがあるので、判定には `code` を使ってください。
370
+ Invalid options and inputs throw ordinary exceptions such as `RangeError` and `TypeError`, with a `code`. Messages may change, so branch on `code`.
332
371
 
333
372
  ```ts
334
373
  import { createCalendar, isBizdaysError } from "bizdays-toolkit";
@@ -337,16 +376,16 @@ try {
337
376
  createCalendar({ closedDates: ["2026-12-32"] });
338
377
  } catch (error) {
339
378
  if (isBizdaysError(error) && error.code === "invalid-date") {
340
- // 日付の誤り(メッセージには設定の中の位置 "closedDates[0].date" が入る)
379
+ // An invalid date (the message includes the location in the options, "closedDates[0].date")
341
380
  }
342
381
  }
343
382
  ```
344
383
 
345
- 主なコード: `invalid-date`・`invalid-month`・`invalid-month-day`・`invalid-time`・`invalid-range`・`invalid-weekday`・`invalid-time-zone`・`invalid-option`・`out-of-range`・`invalid-provider`・`invalid-provider-data`・`no-business-day`。将来の版でコードが増えることがあります。
384
+ Main codes: `invalid-date`, `invalid-input`, `invalid-month`, `invalid-month-day`, `invalid-time`, `invalid-range`, `invalid-weekday`, `invalid-time-zone`, `invalid-option`, `out-of-range`, `invalid-provider`, `invalid-provider-data`, `no-business-day`. Future versions may add codes.
346
385
 
347
- ### 設定の診断
386
+ ### Option diagnostics
348
387
 
349
- `bizdays-toolkit/diagnose` の `diagnoseCalendar`・`diagnoseSchedule` は、設定を調べて問題を一覧で返します。例外は投げません(問題がなければ空の配列)。
388
+ `diagnoseCalendar` and `diagnoseSchedule` from `bizdays-toolkit/diagnose` examine options and return a list of problems. They never throw (an empty array means no problems).
350
389
 
351
390
  ```ts
352
391
  import { diagnoseCalendar } from "bizdays-toolkit/diagnose";
@@ -356,12 +395,12 @@ diagnoseCalendar({
356
395
  holidays: jp,
357
396
  closedWeekdays: [0],
358
397
  closedRecurring: [
359
- { weekday: 3, nth: 3, name: "第 3 水曜定休" },
360
- { weekday: 3, nth: 3, months: [8], name: "8 月の第 3 水曜" }, // 先の規則に含まれる
361
- { weekday: 0, name: "日曜休み" }, // 定休日の曜日と同じ
398
+ { weekday: 3, nth: 3, name: "Closed on the 3rd Wednesday" },
399
+ { weekday: 3, nth: 3, months: [8], name: "3rd Wednesday in August" }, // covered by the previous rule
400
+ { weekday: 0, name: "Closed on Sundays" }, // same as the closed weekday
362
401
  ],
363
- closedDates: ["2100-01-05"], // 日本の祝日の対応年(1955〜2099 年)の外
364
- closeAnnual: ["12-31"], // 打ち間違い
402
+ closedDates: ["2100-01-05"], // outside the supported years of Japanese holidays (1955–2099)
403
+ closeAnnual: ["12-31"], // a typo
365
404
  });
366
405
  // [
367
406
  // { severity: "warning", code: "unknown-option", path: "closeAnnual", message: '… (did you mean "closedAnnual"?)' },
@@ -371,25 +410,25 @@ diagnoseCalendar({
371
410
  // ]
372
411
  ```
373
412
 
374
- | 重さ(`severity`) | `code` | 内容 |
375
- | ------------------ | ----------------------- | -------------------------------------------------------------------------------------------------------------------------- |
376
- | `error` | 例外と同じコード | `createCalendar`・`createSchedule` で例外になる誤り。作成時は最初の 1 件で止まりますが、診断ではすべて返します |
377
- | `error` | `invalid-provider-data` | 祝日の提供元が返したデータの誤り。作成時は使った年だけを調べますが、診断では対応年のすべての年を調べます |
378
- | `warning` | `unknown-option` | 知らないキー(作成時は無視されます)。近い既知のキーがあれば `message` に候補を挙げます |
379
- | `warning` | `shadowed` | 同じ種類の先の規則(`relatedPath`)に完全に含まれ、決して使われない規則 |
380
- | `warning` | `never-matches` | 当てはまる日がない繰り返しの規則(`{ day: 30, months: [2] }` など) |
381
- | `warning` | `out-of-range` | 個別の日付が祝日の提供元(`relatedPath`)の対応年の外 |
382
- | `warning` | `no-business-day` | 営業日が 1 日もない設定(営業日カレンダーのみ。営業日を探すと例外になります) |
383
- | `info` | `redundant` | 取り除いても結果が変わらない規則(先の規則の組み合わせに覆われる、定休日の曜日と同じ、スケジュールで曜日の値と同じ値など) |
384
-
385
- - 判定には `code` を使ってください。`message` は英語で、文言は変わることがあります。`code` は将来の版で増えることがあります
386
- - `error` があるときは、規則どうしの診断(`shadowed`・`redundant` など)は行いません
387
- - `shadowed` は同じ種類の規則どうし(個別の日付どうし、毎年の月日どうし、繰り返しの規則どうし)だけを調べます。判定の順番は固定なので、種類をまたいで使われない規則は `redundant` として見つかります。臨時営業日(`openDates`)と臨時休業日(`closedDates`)は同じ種類で、先の臨時営業日に含まれる臨時休業日は `shadowed` です
388
- - 繰り返しの規則の `shadowed` は、祝日の扱い(`onHolidayRule`)も見ます。たとえば `"under-holiday"` の規則は、同じ日に当たる `"over-holiday"` の規則を覆いません(祝日には後の規則が当たるため)
389
- - `redundant` は、規則を取り除いた結果と 1 日ずつ比べます。個別の日付はその期間を、毎年の月日と繰り返しの規則は個別の日付を除いた設定で、祝日の提供元の対応年(日本は 1955〜2099 年)を、提供元がなければ 2001〜2028 年(曜日と月日の組み合わせが一巡する 28 年)を調べます。スケジュールの値は、配列と普通のオブジェクトなら中身で、それ以外(`Date` など)は同じものかで比べます
390
- - 日本の祝日を使う設定では、全日を調べるため数百ミリ秒かかります。リクエストごとではなく、テストや起動時の確認で使ってください。開発・テスト用の道具なので、アプリの本体のバンドルに入らないようサブパスに分けています
391
-
392
- テストで設定を守る例:
413
+ | `severity` | `code` | Meaning |
414
+ | ---------- | ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
415
+ | `error` | The same codes as exceptions | Errors that make `createCalendar` or `createSchedule` throw. Creation stops at the first one; diagnostics return them all |
416
+ | `error` | `invalid-provider-data` | Errors in the data returned by a holiday provider. Creation checks only the years used; diagnostics check every supported year |
417
+ | `warning` | `unknown-option` | Unknown keys (ignored at creation). If a close known key exists, `message` suggests it |
418
+ | `warning` | `shadowed` | A rule fully covered by an earlier rule of the same type (`relatedPath`) and never used |
419
+ | `warning` | `never-matches` | A recurring rule that matches no day (such as `{ day: 30, months: [2] }`) |
420
+ | `warning` | `out-of-range` | A specific date outside the supported years of the holiday provider (`relatedPath`) |
421
+ | `warning` | `no-business-day` | No business day at all (calendars only; searching for a business day throws) |
422
+ | `info` | `redundant` | A rule whose removal changes nothing (covered by a combination of earlier rules, same as a closed weekday, the same value as the weekday in a schedule, and so on) |
423
+
424
+ - Branch on `code`. `message` is in English and its wording may change. Future versions may add codes
425
+ - When there is an `error`, the rule relation checks (`shadowed`, `redundant` and so on) are skipped
426
+ - `shadowed` compares only rules of the same type (specific dates, annual dates, recurring rules). The order of evaluation is fixed, so rules unused across types are found as `redundant`. Specific open days (`openDates`) and specific closed days (`closedDates`) are of the same type, and a closed day covered by an earlier open day is `shadowed`
427
+ - `shadowed` for recurring rules also considers `onHolidayRule`. For example, an `"under-holiday"` rule does not cover an `"over-holiday"` rule on the same days (the later rule applies on holidays)
428
+ - `redundant` compares the results with and without the rule day by day. Specific dates are checked over their range; annual dates and recurring rules are checked, with specific dates removed, over the holiday provider's supported years (1955–2099 for Japan), or 2001–2028 without a provider (28 years cover every combination of weekday and date). Schedule values are compared by content for arrays and plain objects, and by identity otherwise (such as `Date`)
429
+ - With Japanese holidays, checking every day takes a few hundred milliseconds. Use it in tests or at startup, not per request. As a development and test tool, it lives in a subpath so it stays out of your app's main bundle
430
+
431
+ Keeping options safe with a test:
393
432
 
394
433
  ```ts
395
434
  import assert from "node:assert/strict";
@@ -397,54 +436,29 @@ import { test } from "node:test";
397
436
  import { diagnoseCalendar } from "bizdays-toolkit/diagnose";
398
437
  import { shippingOptions } from "../src/calendar.ts";
399
438
 
400
- test("出荷カレンダーの設定に問題がない", () => {
439
+ test("the shipping calendar options have no problems", () => {
401
440
  assert.deepEqual(diagnoseCalendar(shippingOptions), []);
402
441
  });
403
442
  ```
404
443
 
405
- ## バージョン番号とデータの更新
444
+ ## Versioning and data updates
406
445
 
407
- [Semantic Versioning](https://semver.org/lang/ja/) に従います。詳しくは [RELEASING.md](./RELEASING.md) を参照してください。
446
+ Versioning follows [Semantic Versioning](https://semver.org/). See [RELEASING.md](https://github.com/inohan77/bizdays-toolkit/blob/main/RELEASING.md) (Japanese) for details.
408
447
 
409
- - 法改正・官報公表の反映(祝日の新設・移動、春分・秋分の日の確定)は**マイナー版**です。`^` で指定していれば自動で届きます
410
- - 祝日の判定結果を固定したい場合は、版を完全一致で指定してください(例: `"bizdays-toolkit": "0.1.0"`)
411
- - 反映済みのデータは `dataVersion`(日本は反映済みの官報の年月)と `confirmedThrough` で確認できます
412
- - 1.0.0 までは、マイナー版で破壊的変更を行うことがあります
413
- - 判定の理由(`reason`)・祝日の種別(`kind`)・エラーコード・診断のコードは、将来の版で値が増えることがあります。値を列挙して分岐するときは「その他」も扱ってください([RELEASING.md](./RELEASING.md) の「互換性の約束」)
448
+ - 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 `~`
449
+ - To freeze the holiday results, pin an exact version (for example `"bizdays-toolkit": "0.8.0"`)
450
+ - 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`
451
+ - Before 1.0.0, minor versions may contain breaking changes
452
+ - 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))
414
453
 
415
- ## 出典
454
+ ## Data sources
416
455
 
417
- 日本の祝日の検証には、内閣府ホームページで公開されている「国民の祝日」の CSV(<https://www8.cao.go.jp/chosei/shukujitsu/syukujitsu.csv>) を利用しています(テスト用フィクスチャのみ。パッケージには含みません)。
456
+ Japanese holidays are verified with the "National Holidays" CSV published on the Cabinet Office website (<https://www8.cao.go.jp/chosei/shukujitsu/syukujitsu.csv>) (as a test fixture only; not included in the package).
418
457
 
419
- ## ライセンス
458
+ UK bank holidays are verified with the GOV.UK bank holidays data (<https://www.gov.uk/bank-holidays.json>, and for earlier years the history of the official GOV.UK repositories [alphagov/calendars](https://github.com/alphagov/calendars) and [alphagov/frontend](https://github.com/alphagov/frontend)), and the Welsh names of the Wales holidays with the Welsh version of GOV.UK (the verification data is a test fixture only; not included in the package).
420
459
 
421
- MIT
422
-
423
- ---
460
+ Contains public sector information licensed under the [Open Government Licence v3.0](https://www.nationalarchives.gov.uk/doc/open-government-licence/version/3/).
424
461
 
425
- ## English
426
-
427
- A business-day calendar that combines public holidays with company or store closures.
462
+ ## License
428
463
 
429
- - **Japanese holidays verified day by day** against the Cabinet Office CSV, from 1955 through the latest year officially announced (substitute holidays, citizens' holidays, imperial ceremonies, and the Olympic moves included)
430
- - **Day-by-day schedules**: `createSchedule` returns any value per day (e.g. store hours) from weekly values, a holiday rule (`"weekday"`, `{ sameAs: 0 }`, or `{ value }`), annual dates and specific dates
431
- - **Recurring rules**: weekly, nth-weekday-of-month (`{ weekday: 3, nth: 3 }`) and day-of-month (`{ day: -1 }`) rules, with per-rule holiday handling (`onHolidayRule`: `"under-holiday"`, `"over-holiday"`, or `"after-holiday"` to move the rule to the next non-holiday, e.g. "closed on Mondays; if Monday is a holiday, closed on the next weekday")
432
- - **Option diagnostics**: `diagnoseCalendar` / `diagnoseSchedule` from `bizdays-toolkit/diagnose` return a list of problems without throwing: rules that can never apply (`shadowed`, `never-matches`), rules whose removal changes nothing (`redundant`), typos in option keys with a suggestion (`unknown-option`), dates outside the holiday provider's years (`out-of-range`), and calendars with no business day. Use `code` to branch; `message` is for humans
433
- - **Month-based due dates**: `adjust` with `following` / `preceding` / `modified-following` / `modified-preceding`, the nth / first / last business day of a month, `dueDate(month, { day, convention })`, and the calendar helpers `closingMonth` and `monthOf`
434
- - **Business-day arithmetic**: closed weekdays, annual closures (including ranges across the new year), specific closed/open dates, holiday policy, `addBusinessDays`, cut-off times, and more
435
- - **Time-zone safe**: instants (`Date`, ISO strings with offsets, Temporal) are converted to a civil date in the calendar's time zone before evaluation
436
- - **Lightweight, zero dependencies**: about 7.4 KB min+gzip (about 8.5 KB with Japanese holidays), ESM, bundled types, `sideEffects: false`
437
- - **Extensible** through the `HolidayProvider` interface (only Japan is bundled for now). Holidays can carry names in other languages via the optional `names` field (keys are canonical BCP 47 language tags); the bundled Japanese data includes only the officially sourced Japanese and English names
438
-
439
- ```ts
440
- import { createCalendar } from "bizdays-toolkit";
441
- import { jp, isHoliday } from "bizdays-toolkit/jp";
442
-
443
- isHoliday("2026-11-23"); // true
444
-
445
- const office = createCalendar({ holidays: jp, closedWeekdays: [0, 6] });
446
- office.addBusinessDays("2026-09-18", 1); // "2026-09-24"
447
- office.businessDayOnOrAfter(new Date(), { cutoff: "14:00" });
448
- ```
449
-
450
- Versioning follows SemVer. Updates that reflect law amendments or official announcements are released as **minor** versions; pin an exact version if you need frozen results. Before 1.0.0, minor versions may contain breaking changes. See [RELEASING.md](./RELEASING.md) (Japanese) for details.
464
+ MIT
package/dist/index.d.ts CHANGED
@@ -2,7 +2,7 @@
2
2
  * 祝日と、会社・店舗の休業日を組み合わせた営業日カレンダー(createCalendar)と、
3
3
  * 曜日・祝日・特定日で変わる値(営業時間など)のスケジュール(createSchedule)。
4
4
  *
5
- * 国ごとの祝日はサブパスから読み込む(例: "bizdays-toolkit/jp")。
5
+ * 国ごとの祝日はサブパスから読み込む(例: "bizdays-toolkit/jp"・"bizdays-toolkit/uk")。
6
6
  *
7
7
  * 公開する API には TSDoc の @public(安定)または @beta(マイナー版で変わりうる)を付ける。
8
8
  * 公開する名前と型は etc/api.md に記録し、pnpm check で意図しない変化を検出する。
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@
2
2
  * 祝日と、会社・店舗の休業日を組み合わせた営業日カレンダー(createCalendar)と、
3
3
  * 曜日・祝日・特定日で変わる値(営業時間など)のスケジュール(createSchedule)。
4
4
  *
5
- * 国ごとの祝日はサブパスから読み込む(例: "bizdays-toolkit/jp")。
5
+ * 国ごとの祝日はサブパスから読み込む(例: "bizdays-toolkit/jp"・"bizdays-toolkit/uk")。
6
6
  *
7
7
  * 公開する API には TSDoc の @public(安定)または @beta(マイナー版で変わりうる)を付ける。
8
8
  * 公開する名前と型は etc/api.md に記録し、pnpm check で意図しない変化を検出する。
@@ -0,0 +1,49 @@
1
+ /**
2
+ * 英国の bank holidays(England・Wales・Scotland・Northern Ireland)。
3
+ *
4
+ * 地域ごとに提供元が分かれている。GOV.UK では England と Wales が 1 つの区分(England and Wales)だが、
5
+ * このライブラリでは別の提供元にしている(日付は現在同じ。将来分かれても利用者のコードを変えずに済むように)。
6
+ *
7
+ * @example
8
+ * import { createCalendar } from "bizdays-toolkit";
9
+ * import { scotland } from "bizdays-toolkit/uk";
10
+ * const calendar = createCalendar({ holidays: scotland, closedWeekdays: [0, 6] });
11
+ */
12
+ import type { Holiday, HolidayProvider } from "../core/types.ts";
13
+ import { type UkHolidayKind } from "./rules.ts";
14
+ export type { KnownUkHolidayKind, UkHolidayKind } from "./rules.ts";
15
+ /**
16
+ * 英国の祝日。
17
+ * @public
18
+ */
19
+ export type UkHoliday = Holiday<UkHolidayKind>;
20
+ /**
21
+ * England の bank holidays の提供元(GOV.UK の England and Wales)。
22
+ * @public
23
+ */
24
+ export declare const england: HolidayProvider<UkHolidayKind>;
25
+ /**
26
+ * Wales の bank holidays の提供元(GOV.UK の England and Wales)。名称にウェールズ語(names.cy)が付く。
27
+ * @public
28
+ */
29
+ export declare const wales: HolidayProvider<UkHolidayKind>;
30
+ /**
31
+ * Scotland の bank holidays の提供元。
32
+ * @public
33
+ */
34
+ export declare const scotland: HolidayProvider<UkHolidayKind>;
35
+ /**
36
+ * Northern Ireland の bank holidays の提供元。
37
+ * @public
38
+ */
39
+ export declare const northernIreland: HolidayProvider<UkHolidayKind>;
40
+ /**
41
+ * 反映済みの GOV.UK のデータの年月。
42
+ * @public
43
+ */
44
+ export declare const dataVersion: string;
45
+ /**
46
+ * GOV.UK が日付を公表している最後の年(それより後は規則による予測で confirmed: false)。
47
+ * @public
48
+ */
49
+ export declare const confirmedThrough: number;
@@ -0,0 +1,83 @@
1
+ /**
2
+ * 英国の bank holidays(England・Wales・Scotland・Northern Ireland)。
3
+ *
4
+ * 地域ごとに提供元が分かれている。GOV.UK では England と Wales が 1 つの区分(England and Wales)だが、
5
+ * このライブラリでは別の提供元にしている(日付は現在同じ。将来分かれても利用者のコードを変えずに済むように)。
6
+ *
7
+ * @example
8
+ * import { createCalendar } from "bizdays-toolkit";
9
+ * import { scotland } from "bizdays-toolkit/uk";
10
+ * const calendar = createCalendar({ holidays: scotland, closedWeekdays: [0, 6] });
11
+ */
12
+ import { CONFIRMED_THROUGH, DATA_VERSION, SUPPORTED_YEARS, computeUkHolidays, } from "./rules.js";
13
+ /** Wales の祝日のウェールズ語の名称(GOV.UK のウェールズ語版の表記) */
14
+ const WELSH_NAMES = {
15
+ "New Year’s Day": "Dydd Calan",
16
+ "Good Friday": "Gwener y Groglith",
17
+ "Easter Monday": "Llun y Pasg",
18
+ "Early May bank holiday": "Gŵyl banc dechrau Mai",
19
+ "Early May bank holiday (VE day)": "Gŵyl banc dechrau Mai (diwrnod VE)",
20
+ "Spring bank holiday": "Gŵyl banc y gwanwyn",
21
+ "Summer bank holiday": "Gŵyl banc yr haf",
22
+ "Christmas Day": "Dydd Nadolig",
23
+ "Boxing Day": "Dydd San Steffan",
24
+ "Queen’s Diamond Jubilee": "Jiwbilî Ddiemwnt y Frenhines",
25
+ "Platinum Jubilee bank holiday": "Gŵyl banc Jiwbilî Blatinwm",
26
+ "Bank Holiday for the State Funeral of Queen Elizabeth II": "Gŵyl y Banc ar gyfer Angladd Gwladol y Frenhines Elizabeth II",
27
+ "Bank holiday for the coronation of King Charles III": "Gŵyl y banc ar gyfer coroni Brenin Siarl III",
28
+ };
29
+ function withWelsh(list) {
30
+ return list.map((h) => {
31
+ const cy = WELSH_NAMES[h.name];
32
+ return Object.freeze(cy === undefined ? h : { ...h, names: Object.freeze({ cy }) });
33
+ });
34
+ }
35
+ function provider(id, division, transform) {
36
+ const cache = new Map();
37
+ return Object.freeze({
38
+ id,
39
+ timeZone: "Europe/London",
40
+ supportedYears: SUPPORTED_YEARS,
41
+ dataVersion: DATA_VERSION,
42
+ confirmedThrough: CONFIRMED_THROUGH,
43
+ holidaysInYear(year) {
44
+ let list = cache.get(year);
45
+ if (!list) {
46
+ const computed = computeUkHolidays(division, year);
47
+ list = Object.freeze(transform ? transform(computed) : computed);
48
+ cache.set(year, list);
49
+ }
50
+ return list;
51
+ },
52
+ });
53
+ }
54
+ /**
55
+ * England の bank holidays の提供元(GOV.UK の England and Wales)。
56
+ * @public
57
+ */
58
+ export const england = provider("GB-ENG", "england-and-wales");
59
+ /**
60
+ * Wales の bank holidays の提供元(GOV.UK の England and Wales)。名称にウェールズ語(names.cy)が付く。
61
+ * @public
62
+ */
63
+ export const wales = provider("GB-WLS", "england-and-wales", withWelsh);
64
+ /**
65
+ * Scotland の bank holidays の提供元。
66
+ * @public
67
+ */
68
+ export const scotland = provider("GB-SCT", "scotland");
69
+ /**
70
+ * Northern Ireland の bank holidays の提供元。
71
+ * @public
72
+ */
73
+ export const northernIreland = provider("GB-NIR", "northern-ireland");
74
+ /**
75
+ * 反映済みの GOV.UK のデータの年月。
76
+ * @public
77
+ */
78
+ export const dataVersion = DATA_VERSION;
79
+ /**
80
+ * GOV.UK が日付を公表している最後の年(それより後は規則による予測で confirmed: false)。
81
+ * @public
82
+ */
83
+ export const confirmedThrough = CONFIRMED_THROUGH;
@@ -0,0 +1,46 @@
1
+ /**
2
+ * 英国の bank holidays(GOV.UK の区分: England and Wales・Scotland・Northern Ireland)の計算。
3
+ *
4
+ * - 毎年の祝日: 1971 年の Banking and Financial Dealings Act の附則と、毎年の布告で定まる日を規則で計算する
5
+ * (England and Wales・Northern Ireland の Good Friday と Christmas Day は慣習法上の休日だが、GOV.UK の一覧と同じく含める)
6
+ * - 振替: 土曜日・日曜日に当たる固定日の祝日は、その後で最も近い、祝日でない平日に振り替える(日付順に割り当てる)
7
+ * - 臨時の祝日・移動: 布告ごとの日付をテーブルで持つ
8
+ *
9
+ * 対応年のすべての日付と名称が GOV.UK のデータと一致することを、テスト用フィクスチャ
10
+ * (test/fixtures/govuk-bank-holidays.json)で検証している。CONFIRMED_THROUGH より後の年は、
11
+ * 規則による予測(confirmed: false)。
12
+ */
13
+ import { type DateString } from "../core/date.ts";
14
+ import type { Holiday } from "../core/types.ts";
15
+ /**
16
+ * 英国の祝日の種別(既知の値)。
17
+ * @public
18
+ */
19
+ export type KnownUkHolidayKind =
20
+ /** 毎年の祝日(布告で日付が移動した年を含む) */
21
+ "regular"
22
+ /** 土曜日・日曜日に当たった祝日の振替 */
23
+ | "substitute"
24
+ /** 布告による臨時の祝日(記念式典・国葬など) */
25
+ | "special";
26
+ /**
27
+ * 英国の祝日の種別。将来の版で値が増えることがあるので、分岐では「その他」も扱うこと。
28
+ * すべての値を網羅したことを型で確かめたい場合は KnownUkHolidayKind を使う。
29
+ * @public
30
+ */
31
+ export type UkHolidayKind = KnownUkHolidayKind | (string & {});
32
+ /** @internal GOV.UK の区分 */
33
+ export type UkDivision = "england-and-wales" | "scotland" | "northern-ireland";
34
+ /** @internal 対応年 */
35
+ export declare const SUPPORTED_YEARS: Readonly<{
36
+ from: 2012;
37
+ to: 2099;
38
+ }>;
39
+ /** @internal GOV.UK が日付を公表している最後の年 */
40
+ export declare const CONFIRMED_THROUGH = 2028;
41
+ /** @internal 反映済みの GOV.UK のデータの年月(2026 年の Scotland の World Cup bank holiday の追加) */
42
+ export declare const DATA_VERSION = "2026-02";
43
+ /** @internal 復活祭の日曜日(グレゴリオ暦。Anonymous Gregorian algorithm) */
44
+ export declare function easterSunday(year: number): DateString;
45
+ /** @internal その年・区分の祝日(日付順) */
46
+ export declare function computeUkHolidays(division: UkDivision, year: number): Holiday<UkHolidayKind>[];
@@ -0,0 +1,165 @@
1
+ /**
2
+ * 英国の bank holidays(GOV.UK の区分: England and Wales・Scotland・Northern Ireland)の計算。
3
+ *
4
+ * - 毎年の祝日: 1971 年の Banking and Financial Dealings Act の附則と、毎年の布告で定まる日を規則で計算する
5
+ * (England and Wales・Northern Ireland の Good Friday と Christmas Day は慣習法上の休日だが、GOV.UK の一覧と同じく含める)
6
+ * - 振替: 土曜日・日曜日に当たる固定日の祝日は、その後で最も近い、祝日でない平日に振り替える(日付順に割り当てる)
7
+ * - 臨時の祝日・移動: 布告ごとの日付をテーブルで持つ
8
+ *
9
+ * 対応年のすべての日付と名称が GOV.UK のデータと一致することを、テスト用フィクスチャ
10
+ * (test/fixtures/govuk-bank-holidays.json)で検証している。CONFIRMED_THROUGH より後の年は、
11
+ * 規則による予測(confirmed: false)。
12
+ */
13
+ import { addDays, daysInMonth, formatDate, nthWeekdayOfMonth, weekdayOf, } from "../core/date.js";
14
+ import { rangeError } from "../core/errors.js";
15
+ /** @internal 対応年 */
16
+ export const SUPPORTED_YEARS = Object.freeze({ from: 2012, to: 2099 });
17
+ /** @internal GOV.UK が日付を公表している最後の年 */
18
+ export const CONFIRMED_THROUGH = 2028;
19
+ /** @internal 反映済みの GOV.UK のデータの年月(2026 年の Scotland の World Cup bank holiday の追加) */
20
+ export const DATA_VERSION = "2026-02";
21
+ const NEW_YEAR = "New Year’s Day";
22
+ const SECOND_JANUARY = "2nd January";
23
+ const ST_PATRICK = "St Patrick’s Day";
24
+ const GOOD_FRIDAY = "Good Friday";
25
+ const EASTER_MONDAY = "Easter Monday";
26
+ const EARLY_MAY = "Early May bank holiday";
27
+ const SPRING = "Spring bank holiday";
28
+ const BOYNE = "Battle of the Boyne (Orangemen’s Day)";
29
+ const SUMMER = "Summer bank holiday";
30
+ const ST_ANDREW = "St Andrew’s Day";
31
+ const CHRISTMAS = "Christmas Day";
32
+ const BOXING = "Boxing Day";
33
+ const ALL = ["england-and-wales", "scotland", "northern-ireland"];
34
+ /** 布告で毎年の祝日の日付を移した年(名称が変わる場合は name) */
35
+ const MOVED = [
36
+ { year: 2012, from: SPRING, date: "2012-06-04" },
37
+ { year: 2020, from: EARLY_MAY, date: "2020-05-08", name: "Early May bank holiday (VE day)" },
38
+ { year: 2022, from: SPRING, date: "2022-06-02" },
39
+ ];
40
+ /** 布告による臨時の祝日 */
41
+ const SPECIAL_DAYS = [
42
+ { date: "2012-06-05", name: "Queen’s Diamond Jubilee", divisions: ALL },
43
+ { date: "2022-06-03", name: "Platinum Jubilee bank holiday", divisions: ALL },
44
+ {
45
+ date: "2022-09-19",
46
+ name: "Bank Holiday for the State Funeral of Queen Elizabeth II",
47
+ divisions: ALL,
48
+ },
49
+ {
50
+ date: "2023-05-08",
51
+ name: "Bank holiday for the coronation of King Charles III",
52
+ divisions: ALL,
53
+ },
54
+ { date: "2026-06-15", name: "World Cup bank holiday", divisions: ["scotland"] },
55
+ ];
56
+ /**
57
+ * 振替の日付は規則どおりで、どちらの祝日の振替かの割り当てだけが規則と異なる年。
58
+ * 2023 年の Scotland(1 月 1 日が日曜日)は、GOV.UK では 1/2 が New Year’s Day の振替、1/3 が 2nd January の振替。
59
+ * 同じ曜日の並びの 2012・2017 年は、附則どおり 1/2 が 2nd January、1/3 が New Year’s Day の振替
60
+ */
61
+ const RENAMED = [
62
+ { division: "scotland", date: "2023-01-02", name: NEW_YEAR, kind: "substitute" },
63
+ { division: "scotland", date: "2023-01-03", name: SECOND_JANUARY, kind: "substitute" },
64
+ ];
65
+ /** @internal 復活祭の日曜日(グレゴリオ暦。Anonymous Gregorian algorithm) */
66
+ export function easterSunday(year) {
67
+ const a = year % 19;
68
+ const b = Math.floor(year / 100);
69
+ const c = year % 100;
70
+ const d = Math.floor(b / 4);
71
+ const e = b % 4;
72
+ const f = Math.floor((b + 8) / 25);
73
+ const g = Math.floor((b - f + 1) / 3);
74
+ const h = (19 * a + b - d - g + 15) % 30;
75
+ const i = Math.floor(c / 4);
76
+ const k = c % 4;
77
+ const l = (32 + 2 * e + 2 * i - h - k) % 7;
78
+ const m = Math.floor((a + 11 * h + 22 * l) / 451);
79
+ const month = Math.floor((h + l - 7 * m + 114) / 31);
80
+ const day = ((h + l - 7 * m + 114) % 31) + 1;
81
+ return formatDate(year, month, day);
82
+ }
83
+ function lastMonday(year, month) {
84
+ const last = formatDate(year, month, daysInMonth(year, month));
85
+ return addDays(last, -((weekdayOf(last) - 1 + 7) % 7));
86
+ }
87
+ function isWeekend(date) {
88
+ const w = weekdayOf(date);
89
+ return w === 0 || w === 6;
90
+ }
91
+ /** @internal その年・区分の祝日(日付順) */
92
+ export function computeUkHolidays(division, year) {
93
+ if (!Number.isInteger(year) || year < SUPPORTED_YEARS.from || year > SUPPORTED_YEARS.to) {
94
+ throw rangeError("out-of-range", `UK bank holidays are supported for ${SUPPORTED_YEARS.from}-${SUPPORTED_YEARS.to}; requested ${year}`);
95
+ }
96
+ const scotland = division === "scotland";
97
+ const ni = division === "northern-ireland";
98
+ const fixed = [{ month: 1, day: 1, name: NEW_YEAR }];
99
+ if (scotland)
100
+ fixed.push({ month: 1, day: 2, name: SECOND_JANUARY });
101
+ if (ni)
102
+ fixed.push({ month: 3, day: 17, name: ST_PATRICK });
103
+ if (ni)
104
+ fixed.push({ month: 7, day: 12, name: BOYNE });
105
+ if (scotland)
106
+ fixed.push({ month: 11, day: 30, name: ST_ANDREW });
107
+ fixed.push({ month: 12, day: 25, name: CHRISTMAS }, { month: 12, day: 26, name: BOXING });
108
+ const easter = easterSunday(year);
109
+ const movable = [
110
+ { date: addDays(easter, -2), name: GOOD_FRIDAY, kind: "regular" },
111
+ { date: nthWeekdayOfMonth(year, 5, 1, 1), name: EARLY_MAY, kind: "regular" },
112
+ { date: lastMonday(year, 5), name: SPRING, kind: "regular" },
113
+ {
114
+ date: scotland ? nthWeekdayOfMonth(year, 8, 1, 1) : lastMonday(year, 8),
115
+ name: SUMMER,
116
+ kind: "regular",
117
+ },
118
+ ];
119
+ if (!scotland)
120
+ movable.push({ date: addDays(easter, 1), name: EASTER_MONDAY, kind: "regular" });
121
+ const entries = [];
122
+ for (const e of movable) {
123
+ const moved = MOVED.find((m) => m.year === year && m.from === e.name);
124
+ entries.push(moved ? { date: moved.date, name: moved.name ?? e.name, kind: e.kind } : e);
125
+ }
126
+ for (const s of SPECIAL_DAYS) {
127
+ if (s.date.startsWith(`${year}-`) && s.divisions.includes(division)) {
128
+ entries.push({ date: s.date, name: s.name, kind: "special" });
129
+ }
130
+ }
131
+ // 平日に当たる固定日の祝日を先に置き、土曜日・日曜日に当たるものを日付順に振り替える
132
+ const taken = new Set(entries.map((e) => e.date));
133
+ const fixedDates = fixed.map((f) => ({ date: formatDate(year, f.month, f.day), name: f.name }));
134
+ for (const f of fixedDates) {
135
+ if (!isWeekend(f.date)) {
136
+ entries.push({ date: f.date, name: f.name, kind: "regular" });
137
+ taken.add(f.date);
138
+ }
139
+ }
140
+ for (const f of fixedDates) {
141
+ if (!isWeekend(f.date))
142
+ continue;
143
+ let day = addDays(f.date, 1);
144
+ while (isWeekend(day) || taken.has(day))
145
+ day = addDays(day, 1);
146
+ entries.push({ date: day, name: f.name, kind: "substitute" });
147
+ taken.add(day);
148
+ }
149
+ for (const r of RENAMED) {
150
+ if (r.division !== division || !r.date.startsWith(`${year}-`))
151
+ continue;
152
+ const i = entries.findIndex((e) => e.date === r.date);
153
+ if (i >= 0)
154
+ entries[i] = { date: r.date, name: r.name, kind: r.kind };
155
+ }
156
+ return entries
157
+ .sort((a, b) => (a.date < b.date ? -1 : 1))
158
+ .map((e) => Object.freeze({
159
+ date: e.date,
160
+ name: e.name,
161
+ nameEn: e.name,
162
+ kind: e.kind,
163
+ confirmed: year <= CONFIRMED_THROUGH,
164
+ }));
165
+ }
package/package.json CHANGED
@@ -1,8 +1,9 @@
1
1
  {
2
2
  "name": "bizdays-toolkit",
3
- "version": "0.7.0",
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. / 祝日と営業日のカレンダー(タイムゾーン対応・日本の祝日は内閣府データで全日照合)",
3
+ "version": "0.8.1",
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
+ "bank-holidays",
6
7
  "bizdays",
7
8
  "business-day",
8
9
  "business-days",
@@ -10,6 +11,7 @@
10
11
  "holiday",
11
12
  "holidays",
12
13
  "japan",
14
+ "uk",
13
15
  "workday",
14
16
  "営業日",
15
17
  "祝日"
@@ -40,6 +42,10 @@
40
42
  "types": "./dist/jp/index.d.ts",
41
43
  "default": "./dist/jp/index.js"
42
44
  },
45
+ "./uk": {
46
+ "types": "./dist/uk/index.d.ts",
47
+ "default": "./dist/uk/index.js"
48
+ },
43
49
  "./diagnose": {
44
50
  "types": "./dist/diagnose/index.d.ts",
45
51
  "default": "./dist/diagnose/index.js"
@@ -50,13 +56,15 @@
50
56
  "build": "rm -rf dist && tsc -p tsconfig.build.json",
51
57
  "typecheck": "tsc --noEmit",
52
58
  "lint": "oxlint --deny-warnings src scripts test",
53
- "test": "node --test \"test/**/*.test.ts\"",
59
+ "test": "node --import ./scripts/strict-warnings.ts --test \"test/**/*.test.ts\"",
54
60
  "test:tz": "node scripts/test-tz.ts",
61
+ "test:compat": "node --test test/compat/esm.test.mjs test/compat/cjs.test.cjs",
55
62
  "bench": "node scripts/bench.ts",
56
63
  "fmt": "oxfmt",
57
64
  "fmt:check": "oxfmt --check",
58
- "check": "pnpm typecheck && pnpm lint && pnpm test && pnpm test:tz && pnpm build && pnpm fmt:check && node scripts/check-package.ts && node scripts/check-size.ts && node scripts/api-report.ts",
65
+ "check": "node scripts/check-toolchain.ts && pnpm typecheck && pnpm lint && pnpm test && pnpm test:tz && pnpm build && pnpm test:compat && node scripts/check-readme-examples.ts && pnpm fmt:check && node scripts/check-package.ts && node scripts/check-size.ts && node scripts/api-report.ts",
59
66
  "verify:cao": "node scripts/verify-cao.ts",
67
+ "verify:govuk": "node scripts/verify-govuk.ts",
60
68
  "prepublishOnly": "pnpm check",
61
69
  "release-notes": "node scripts/release-notes.ts",
62
70
  "api:update": "pnpm build && node scripts/api-report.ts --update"