bizdays-toolkit 0.3.1 → 0.5.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 +42 -1
- package/README.md +156 -7
- package/dist/core/calendar.d.ts +19 -5
- package/dist/core/calendar.js +20 -4
- package/dist/core/diagnose.d.ts +79 -0
- package/dist/core/diagnose.js +386 -0
- package/dist/core/errors.d.ts +14 -3
- package/dist/core/errors.js +23 -4
- package/dist/core/schedule.d.ts +128 -8
- package/dist/core/schedule.js +216 -24
- package/dist/diagnose/index.d.ts +7 -0
- package/dist/diagnose/index.js +7 -0
- package/dist/index.d.ts +1 -1
- package/package.json +6 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,45 @@
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [0.5.0] - 2026-10-16
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- 規則の診断(サブパス `bizdays-toolkit/diagnose`): `diagnoseCalendar(options)`・`diagnoseSchedule(options)`。例外を投げず、`{ severity, code, path, message, relatedPath? }` の配列を返す
|
|
12
|
+
- `error`: 作成で例外になる誤り(`code` は例外と同じ)。作成時は最初の 1 件で止まるが、診断ではすべて返す。祝日の提供元が返すデータは、対応年のすべての年を調べる(`invalid-provider-data`)
|
|
13
|
+
- `warning`: `unknown-option`(知らないキー。近い既知のキーを候補に挙げる)・`shadowed`(同じ種類の先の規則に完全に含まれる。`relatedPath` に先の規則)・`never-matches`(当てはまる日がない繰り返しの規則)・`out-of-range`(個別の日付が祝日の提供元の対応年の外)・`no-business-day`(営業日が 1 日もない設定)
|
|
14
|
+
- `info`: `redundant`(取り除いても結果が変わらない規則)
|
|
15
|
+
- 型: `Diagnostic`・`DiagnosticSeverity`・`DiagnosticCode`・`KnownDiagnosticCode`
|
|
16
|
+
|
|
17
|
+
### Removed
|
|
18
|
+
|
|
19
|
+
- `HolidayRule` 型(0.4.0 で非推奨): `HolidayValueRule` を使う
|
|
20
|
+
|
|
21
|
+
### 開発
|
|
22
|
+
|
|
23
|
+
- 診断の性質のテスト(`test/diagnose-properties.test.ts`): 使われない・取り除いても変わらないと診断した規則を実際に取り除き、結果が変わらないことを乱数の設定で確かめる
|
|
24
|
+
- サイズの上限に `./diagnose` を追加
|
|
25
|
+
- `docs/decisions.md` に 15 番目(規則の診断の設計)を追加
|
|
26
|
+
|
|
27
|
+
## [0.4.0] - 2026-10-15
|
|
28
|
+
|
|
29
|
+
### Added
|
|
30
|
+
|
|
31
|
+
- 繰り返しの規則: 毎週(`{ weekday }`)・毎月の第 n 曜日(`{ weekday, nth }`。`nth` は 1〜5 と -1(最終))・毎月の日(`{ day }`。`day` は 1〜31 と -1(末日))。どれにも月の絞り込み(`months`)を付けられる。営業日カレンダーの `closedRecurring`・`openRecurring`、スケジュールの `recurring`
|
|
32
|
+
- 祝日に当たったときの扱いを規則ごとに選ぶ `onHolidayRule`: `"under-holiday"`(祝日の扱いに従う。既定)・`"over-holiday"`(祝日でも当てはめる)・`"after-holiday"`(祝日でない翌日に振り替える。「月曜休館(祝日の場合は翌平日)」など)
|
|
33
|
+
- `dayInfo()` の `shiftedFrom`: 振替で決まった日の、振替の元の日付
|
|
34
|
+
- 判定の理由に `"recurring"`(スケジュール)・`"open-recurring"`・`"closed-recurring"`(営業日カレンダー)を追加
|
|
35
|
+
- 型: `RecurringRule`・`RecurringValue`・`RecurringPattern`・`WeekdayPattern`・`DayOfMonthPattern`・`OnHolidayRule`・`Nth`・`DayOfMonth`・`Month`・`HolidayValueRule`
|
|
36
|
+
|
|
37
|
+
### Deprecated
|
|
38
|
+
|
|
39
|
+
- `HolidayRule` 型: `HolidayValueRule` に改名した(繰り返しの規則の `onHolidayRule` と紛れないため)。旧名は同じ型の別名として残し、次以降のマイナー版で削除する
|
|
40
|
+
|
|
41
|
+
### 開発
|
|
42
|
+
|
|
43
|
+
- 性能の計測(`pnpm bench`、依存なし)を追加
|
|
44
|
+
- README に繰り返しの規則の節と、よくある設定の例(国民の休日を営業日として扱う、共通の設定から店舗ごとのカレンダーを作る)を追加。`docs/decisions.md` に 12〜14 番目(判定の順番を固定する理由、「第 n 何曜日」の定義、振替先の定義)を追加
|
|
45
|
+
|
|
7
46
|
## [0.3.1] - 2026-10-15
|
|
8
47
|
|
|
9
48
|
ライブラリのコード・公開する API・祝日データに変更はありません。開発の検査を強めた版です。
|
|
@@ -90,7 +129,9 @@
|
|
|
90
129
|
|
|
91
130
|
- 日本: 2026 年 2 月の官報(2027 年の春分・秋分の日)まで反映(`dataVersion: "2026-02"`、`confirmedThrough: 2027`)
|
|
92
131
|
|
|
93
|
-
[Unreleased]: https://github.com/inohan77/bizdays-toolkit/compare/v0.
|
|
132
|
+
[Unreleased]: https://github.com/inohan77/bizdays-toolkit/compare/v0.5.0...HEAD
|
|
133
|
+
[0.5.0]: https://github.com/inohan77/bizdays-toolkit/compare/v0.4.0...v0.5.0
|
|
134
|
+
[0.4.0]: https://github.com/inohan77/bizdays-toolkit/compare/v0.3.1...v0.4.0
|
|
94
135
|
[0.3.1]: https://github.com/inohan77/bizdays-toolkit/compare/v0.3.0...v0.3.1
|
|
95
136
|
[0.3.0]: https://github.com/inohan77/bizdays-toolkit/compare/v0.2.0...v0.3.0
|
|
96
137
|
[0.2.0]: https://github.com/inohan77/bizdays-toolkit/compare/v0.1.2...v0.2.0
|
package/README.md
CHANGED
|
@@ -17,10 +17,12 @@
|
|
|
17
17
|
祝日と、会社・店舗の休業日を組み合わせた営業日カレンダーです。
|
|
18
18
|
|
|
19
19
|
- **日本の祝日を内閣府のデータで全日照合**: 1955 年〜官報公表済みの年まで、内閣府「国民の祝日」CSV のすべての日付と一致することをテストで確かめています(振替休日・国民の休日・皇室行事による休日・五輪特措法による移動を含む)
|
|
20
|
+
- **繰り返しの規則**: 「第 3 水曜定休」「毎月末日は棚卸」「月曜休館(祝日の場合は翌平日)」のような規則を、祝日に当たったときの扱い(祝日に従う・祝日でも当てはめる・翌日に振り替える)とあわせて指定できます
|
|
20
21
|
- **日ごとの値(営業時間など)**: 「火曜定休、祝日は日曜の営業時間、元日は休業、大晦日は短縮営業」のように、曜日・祝日・特定日で変わる値を同じ規則で引けます
|
|
22
|
+
- **設定の診断**: `bizdays-toolkit/diagnose` の `diagnoseCalendar`・`diagnoseSchedule` が、決して使われない規則・取り除いても結果が変わらない規則・キーの打ち間違い・営業日が 1 日もない設定などを、例外を投げずに一覧で返します。テストに 1 行足せば、設定の変更で規則が死んでいないかを確かめられます
|
|
21
23
|
- **営業日の計算**: 定休日の曜日・毎年の休業日(年末年始のような年をまたぐ期間も可)・臨時休業日・臨時営業日と祝日を組み合わせ、「N 営業日後」「締め時刻を過ぎたら翌営業日」などを計算します
|
|
22
24
|
- **タイムゾーンを間違えない**: `Date` などの瞬間は、カレンダーのタイムゾーン(日本は `Asia/Tokyo`)で暦日に変換してから判定します。UTC で動くサーバーでも、日本時間 0〜9 時が前日扱いになりません
|
|
23
|
-
- **軽量・依存ゼロ**: minify + gzip で約
|
|
25
|
+
- **軽量・依存ゼロ**: minify + gzip で約 6.5KB(日本の祝日込みで約 7.5KB)。ESM・型定義同梱・`sideEffects: false` で、Node.js・ブラウザー・Edge ランタイムで動作します
|
|
24
26
|
- **他の国・地域に拡張できる**: 祝日の提供元はインターフェース(`HolidayProvider`)で差し替えられます。現在は日本のみ同梱しています
|
|
25
27
|
|
|
26
28
|
[English](#english)
|
|
@@ -84,9 +86,96 @@ shipping.dayInfo("2027-01-01");
|
|
|
84
86
|
1. `openDates`(臨時営業日)
|
|
85
87
|
2. `closedDates`(臨時休業日)
|
|
86
88
|
3. `closedAnnual`(毎年の休業日)
|
|
87
|
-
4.
|
|
88
|
-
5. `
|
|
89
|
-
6.
|
|
89
|
+
4. `openRecurring`・`closedRecurring` のうち `onHolidayRule: "over-holiday"` の規則(祝日でも当てはめる)
|
|
90
|
+
5. 祝日(`holidayPolicy` に従う。`"open"` は定休日の曜日より優先して営業日にする)
|
|
91
|
+
6. `openRecurring`・`closedRecurring`(既定の `"under-holiday"` と、`"after-holiday"` の振替先)
|
|
92
|
+
7. `closedWeekdays`(定休日の曜日)
|
|
93
|
+
8. それ以外は営業日
|
|
94
|
+
|
|
95
|
+
この順番は固定です。繰り返しの規則と祝日の関係だけは、規則ごとに `onHolidayRule` で選べます(次の節)。
|
|
96
|
+
|
|
97
|
+
### 繰り返しの規則(毎週・毎月の第 n 曜日・毎月の日)
|
|
98
|
+
|
|
99
|
+
「第 3 水曜定休」「第 2・第 4 土曜休業」「毎月末日は棚卸」「月曜休館(祝日の場合は翌平日)」のような繰り返しの休業日・営業日は、`closedRecurring`・`openRecurring` で指定します。
|
|
100
|
+
|
|
101
|
+
```ts
|
|
102
|
+
const museum = createCalendar({
|
|
103
|
+
holidays: jp,
|
|
104
|
+
holidayPolicy: "open", // 祝日は開館
|
|
105
|
+
closedRecurring: [
|
|
106
|
+
{ weekday: 1, name: "月曜休館", onHolidayRule: "after-holiday" }, // 祝日の場合は翌平日に休館
|
|
107
|
+
{ weekday: 3, nth: 3, months: [2, 8], name: "2 月・8 月の第 3 水曜は臨時休館" },
|
|
108
|
+
{ day: -1, name: "月末棚卸", onHolidayRule: "over-holiday" }, // 祝日でも休館
|
|
109
|
+
],
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
museum.isBusinessDay("2026-10-12"); // true(スポーツの日の月曜は開館)
|
|
113
|
+
museum.dayInfo("2026-10-13");
|
|
114
|
+
// { date: "2026-10-13", weekday: 2, businessDay: false, reason: "closed-recurring",
|
|
115
|
+
// name: "月曜休館", shiftedFrom: "2026-10-12" }
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
| 書き方 | 意味 |
|
|
119
|
+
| ------------------------ | ------------------------------------------------------------------------------- |
|
|
120
|
+
| `{ weekday: 1 }` | 毎週月曜日 |
|
|
121
|
+
| `{ weekday: 3, nth: 3 }` | 第 3 水曜日。`nth` は 1〜5 と -1(最終)。`[2, 4]` のように配列で複数指定できる |
|
|
122
|
+
| `{ day: 20 }` | 毎月 20 日。`day` は 1〜31 と -1(末日)。配列で複数指定できる |
|
|
123
|
+
| `months: [8, 12]` | どの書き方にも付けられる、月の絞り込み |
|
|
124
|
+
|
|
125
|
+
- **第 n 何曜日**は「その月で n 回目のその曜日」です(1〜7 日が第 1)。カレンダーの第 n 週の行にある曜日ではありません
|
|
126
|
+
- **その月にない日**(`nth: 5` がない月、`day: 31` がない月)は当てはまりません。末日は `day: -1` で指定します
|
|
127
|
+
- 繰り返しの規則どうしは配列で先に書いたものが優先で、`openRecurring` は `closedRecurring` より優先です
|
|
128
|
+
|
|
129
|
+
**祝日に当たったときの扱い**(`onHolidayRule`)は、規則ごとに選べます。
|
|
130
|
+
|
|
131
|
+
| 値 | 意味 | 店舗の告知での書き方 |
|
|
132
|
+
| ------------------------- | -------------------------------------------------------- | ------------------------------------- |
|
|
133
|
+
| `"under-holiday"`(既定) | 祝日の下。祝日の扱い(`holidayPolicy`)に従う | 「第 3 水曜定休(祝日の場合は営業)」 |
|
|
134
|
+
| `"over-holiday"` | 祝日の上。祝日でも当てはめる | 「第 3 水曜定休(祝日も休み)」 |
|
|
135
|
+
| `"after-holiday"` | 祝日の後。祝日には当てはめず、祝日でない翌日に振り替える | 「月曜休館(祝日の場合は翌平日)」 |
|
|
136
|
+
|
|
137
|
+
- `"under-holiday"` の規則は、`holidayPolicy: "ignore"`(祝日を判定に使わない)なら祝日にも当てはまります
|
|
138
|
+
- `"after-holiday"` の振替先は「その日以降で最初の祝日でない日」です。祝日が続く場合(GW など)は連休が明けた日になります。振替先の日に臨時営業日・臨時休業日・毎年の休業日があれば、そちらが優先です。振替で決まった日の `dayInfo()` には、振替の元の日付 `shiftedFrom` が付きます
|
|
139
|
+
- 振替の判断に使う「祝日」は、祝日の提供元(`jp` など)の祝日だけです(毎年の休業日などは含みません)
|
|
140
|
+
|
|
141
|
+
スケジュールでは、同じ書き方の規則に `value` を付けて `recurring` に指定します。
|
|
142
|
+
|
|
143
|
+
```ts
|
|
144
|
+
const hours = createSchedule<Hours>({
|
|
145
|
+
holidays: jp,
|
|
146
|
+
weekly: [sunday, weekday, weekday, weekday, weekday, weekday, weekday],
|
|
147
|
+
recurring: [
|
|
148
|
+
{ weekday: 1, value: null, name: "月曜休業", onHolidayRule: "after-holiday" },
|
|
149
|
+
{ day: -1, value: { open: "10:00", close: "17:00" }, name: "月末は短縮営業" },
|
|
150
|
+
],
|
|
151
|
+
});
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### よくある設定の例
|
|
155
|
+
|
|
156
|
+
**国民の休日(祝日に挟まれた日)を営業日として扱う**:祝日の提供元を包んで、種別で除外します。
|
|
157
|
+
|
|
158
|
+
```ts
|
|
159
|
+
import { jp } from "bizdays-toolkit/jp";
|
|
160
|
+
|
|
161
|
+
const jpWithoutCitizensHoliday = {
|
|
162
|
+
...jp,
|
|
163
|
+
holidaysInYear: (year: number) => jp.holidaysInYear(year).filter((h) => h.kind !== "citizens"),
|
|
164
|
+
};
|
|
165
|
+
const calendar = createCalendar({ holidays: jpWithoutCitizensHoliday, closedWeekdays: [0, 6] });
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
**共通の設定から店舗ごとのカレンダーを作る**:設定は普通のオブジェクトなので、展開して足します。
|
|
169
|
+
|
|
170
|
+
```ts
|
|
171
|
+
const base = {
|
|
172
|
+
holidays: jp,
|
|
173
|
+
closedWeekdays: [0],
|
|
174
|
+
closedAnnual: [{ from: "12-31", to: "01-03" }],
|
|
175
|
+
} as const;
|
|
176
|
+
const shibuya = createCalendar({ ...base, openDates: ["2026-12-27"] });
|
|
177
|
+
const yokohama = createCalendar({ ...base, closedRecurring: [{ weekday: 3, nth: 3 }] });
|
|
178
|
+
```
|
|
90
179
|
|
|
91
180
|
### 日ごとの値(営業時間など)
|
|
92
181
|
|
|
@@ -113,7 +202,7 @@ storeHours.dayInfo("2027-01-01");
|
|
|
113
202
|
// { date: "2027-01-01", weekday: 5, value: null, reason: "annual", name: "元日休業", holiday: {…} }
|
|
114
203
|
```
|
|
115
204
|
|
|
116
|
-
値は `dates`(個別の日付・期間)→ `annual`(毎年の月日・期間)→ 祝日 → `weekly`(曜日)の順に決まります。同じ種類の規則が重なる場合は、配列で先に書いたものが優先です。
|
|
205
|
+
値は `dates`(個別の日付・期間)→ `annual`(毎年の月日・期間)→ `recurring`(`"over-holiday"`)→ 祝日 → `recurring`(既定・振替先)→ `weekly`(曜日)の順に決まります。同じ種類の規則が重なる場合は、配列で先に書いたものが優先です。
|
|
117
206
|
|
|
118
207
|
「今、営業時間内か」のような時刻の範囲の判定は、業種ごとの決まり(深夜をまたぐ営業、休憩時間など)があるためライブラリでは行いません。`civilOf` でタイムゾーンでの暦日と時刻を取り出せば、アプリ側で簡単に書けます。
|
|
119
208
|
|
|
@@ -187,6 +276,64 @@ try {
|
|
|
187
276
|
|
|
188
277
|
主なコード: `invalid-date`・`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`。将来の版でコードが増えることがあります。
|
|
189
278
|
|
|
279
|
+
### 設定の診断
|
|
280
|
+
|
|
281
|
+
`bizdays-toolkit/diagnose` の `diagnoseCalendar`・`diagnoseSchedule` は、設定を調べて問題を一覧で返します。例外は投げません(問題がなければ空の配列)。
|
|
282
|
+
|
|
283
|
+
```ts
|
|
284
|
+
import { diagnoseCalendar } from "bizdays-toolkit/diagnose";
|
|
285
|
+
import { jp } from "bizdays-toolkit/jp";
|
|
286
|
+
|
|
287
|
+
diagnoseCalendar({
|
|
288
|
+
holidays: jp,
|
|
289
|
+
closedWeekdays: [0],
|
|
290
|
+
closedRecurring: [
|
|
291
|
+
{ weekday: 3, nth: 3, name: "第 3 水曜定休" },
|
|
292
|
+
{ weekday: 3, nth: 3, months: [8], name: "8 月の第 3 水曜" }, // 先の規則に含まれる
|
|
293
|
+
{ weekday: 0, name: "日曜休み" }, // 定休日の曜日と同じ
|
|
294
|
+
],
|
|
295
|
+
closedDates: ["2100-01-05"], // 日本の祝日の対応年(1955〜2099 年)の外
|
|
296
|
+
closeAnnual: ["12-31"], // 打ち間違い
|
|
297
|
+
});
|
|
298
|
+
// [
|
|
299
|
+
// { severity: "warning", code: "unknown-option", path: "closeAnnual", message: '… (did you mean "closedAnnual"?)' },
|
|
300
|
+
// { severity: "warning", code: "out-of-range", path: "closedDates[0]", relatedPath: "holidays", message: "…" },
|
|
301
|
+
// { severity: "warning", code: "shadowed", path: "closedRecurring[1]", relatedPath: "closedRecurring[0]", message: "…" },
|
|
302
|
+
// { severity: "info", code: "redundant", path: "closedRecurring[2]", message: "…" },
|
|
303
|
+
// ]
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
| 重さ(`severity`) | `code` | 内容 |
|
|
307
|
+
| ------------------ | ----------------------- | -------------------------------------------------------------------------------------------------------------------------- |
|
|
308
|
+
| `error` | 例外と同じコード | `createCalendar`・`createSchedule` で例外になる誤り。作成時は最初の 1 件で止まりますが、診断ではすべて返します |
|
|
309
|
+
| `error` | `invalid-provider-data` | 祝日の提供元が返したデータの誤り。作成時は使った年だけを調べますが、診断では対応年のすべての年を調べます |
|
|
310
|
+
| `warning` | `unknown-option` | 知らないキー(作成時は無視されます)。近い既知のキーがあれば `message` に候補を挙げます |
|
|
311
|
+
| `warning` | `shadowed` | 同じ種類の先の規則(`relatedPath`)に完全に含まれ、決して使われない規則 |
|
|
312
|
+
| `warning` | `never-matches` | 当てはまる日がない繰り返しの規則(`{ day: 30, months: [2] }` など) |
|
|
313
|
+
| `warning` | `out-of-range` | 個別の日付が祝日の提供元(`relatedPath`)の対応年の外 |
|
|
314
|
+
| `warning` | `no-business-day` | 営業日が 1 日もない設定(営業日カレンダーのみ。営業日を探すと例外になります) |
|
|
315
|
+
| `info` | `redundant` | 取り除いても結果が変わらない規則(先の規則の組み合わせに覆われる、定休日の曜日と同じ、スケジュールで曜日の値と同じ値など) |
|
|
316
|
+
|
|
317
|
+
- 判定には `code` を使ってください。`message` は英語で、文言は変わることがあります。`code` は将来の版で増えることがあります
|
|
318
|
+
- `error` があるときは、規則どうしの診断(`shadowed`・`redundant` など)は行いません
|
|
319
|
+
- `shadowed` は同じ種類の規則どうし(個別の日付どうし、毎年の月日どうし、繰り返しの規則どうし)だけを調べます。判定の順番は固定なので、種類をまたいで使われない規則は `redundant` として見つかります。臨時営業日(`openDates`)と臨時休業日(`closedDates`)は同じ種類で、先の臨時営業日に含まれる臨時休業日は `shadowed` です
|
|
320
|
+
- 繰り返しの規則の `shadowed` は、祝日の扱い(`onHolidayRule`)も見ます。たとえば `"under-holiday"` の規則は、同じ日に当たる `"over-holiday"` の規則を覆いません(祝日には後の規則が当たるため)
|
|
321
|
+
- `redundant` は、規則を取り除いた結果と 1 日ずつ比べます。個別の日付はその期間を、毎年の月日と繰り返しの規則は個別の日付を除いた設定で、祝日の提供元の対応年(日本は 1955〜2099 年)を、提供元がなければ 2001〜2028 年(曜日と月日の組み合わせが一巡する 28 年)を調べます。スケジュールの値は、配列と普通のオブジェクトなら中身で、それ以外(`Date` など)は同じものかで比べます
|
|
322
|
+
- 日本の祝日を使う設定では、全日を調べるため数百ミリ秒かかります。リクエストごとではなく、テストや起動時の確認で使ってください。開発・テスト用の道具なので、アプリの本体のバンドルに入らないようサブパスに分けています
|
|
323
|
+
|
|
324
|
+
テストで設定を守る例:
|
|
325
|
+
|
|
326
|
+
```ts
|
|
327
|
+
import assert from "node:assert/strict";
|
|
328
|
+
import { test } from "node:test";
|
|
329
|
+
import { diagnoseCalendar } from "bizdays-toolkit/diagnose";
|
|
330
|
+
import { shippingOptions } from "../src/calendar.ts";
|
|
331
|
+
|
|
332
|
+
test("出荷カレンダーの設定に問題がない", () => {
|
|
333
|
+
assert.deepEqual(diagnoseCalendar(shippingOptions), []);
|
|
334
|
+
});
|
|
335
|
+
```
|
|
336
|
+
|
|
190
337
|
## バージョン番号とデータの更新
|
|
191
338
|
|
|
192
339
|
[Semantic Versioning](https://semver.org/lang/ja/) に従います。詳しくは [RELEASING.md](./RELEASING.md) を参照してください。
|
|
@@ -195,7 +342,7 @@ try {
|
|
|
195
342
|
- 祝日の判定結果を固定したい場合は、版を完全一致で指定してください(例: `"bizdays-toolkit": "0.1.0"`)
|
|
196
343
|
- 反映済みのデータは `dataVersion`(日本は反映済みの官報の年月)と `confirmedThrough` で確認できます
|
|
197
344
|
- 1.0.0 までは、マイナー版で破壊的変更を行うことがあります
|
|
198
|
-
- 判定の理由(`reason`)・祝日の種別(`kind
|
|
345
|
+
- 判定の理由(`reason`)・祝日の種別(`kind`)・エラーコード・診断のコードは、将来の版で値が増えることがあります。値を列挙して分岐するときは「その他」も扱ってください([RELEASING.md](./RELEASING.md) の「互換性の約束」)
|
|
199
346
|
|
|
200
347
|
## 出典
|
|
201
348
|
|
|
@@ -213,9 +360,11 @@ A business-day calendar that combines public holidays with company or store clos
|
|
|
213
360
|
|
|
214
361
|
- **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)
|
|
215
362
|
- **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
|
|
363
|
+
- **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")
|
|
364
|
+
- **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
|
|
216
365
|
- **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
|
|
217
366
|
- **Time-zone safe**: instants (`Date`, ISO strings with offsets, Temporal) are converted to a civil date in the calendar's time zone before evaluation
|
|
218
|
-
- **Lightweight, zero dependencies**: about
|
|
367
|
+
- **Lightweight, zero dependencies**: about 6.5 KB min+gzip (about 7.5 KB with Japanese holidays), ESM, bundled types, `sideEffects: false`
|
|
219
368
|
- **Extensible** through the `HolidayProvider` interface (only Japan is bundled for now)
|
|
220
369
|
|
|
221
370
|
```ts
|
package/dist/core/calendar.d.ts
CHANGED
|
@@ -5,16 +5,21 @@
|
|
|
5
5
|
* 1. openDates … 臨時営業日(個別の日付・期間)
|
|
6
6
|
* 2. closedDates … 臨時休業日(個別の日付・期間)
|
|
7
7
|
* 3. closedAnnual … 毎年の休業日(月日・月日の期間。年をまたぐ期間も可)
|
|
8
|
-
* 4.
|
|
9
|
-
* 5.
|
|
10
|
-
* 6.
|
|
8
|
+
* 4. openRecurring・closedRecurring(onHolidayRule: "over-holiday")… 祝日でも当てはめる繰り返しの規則
|
|
9
|
+
* 5. 祝日 … holidayPolicy に従う(closed: 休業 / open: 営業 / ignore: 判定に使わない)
|
|
10
|
+
* 6. openRecurring・closedRecurring("under-holiday"・"after-holiday" の振替先)… 繰り返しの規則
|
|
11
|
+
* 7. closedWeekdays … 定休日の曜日
|
|
12
|
+
* 8. それ以外は営業日
|
|
13
|
+
*
|
|
14
|
+
* 繰り返しの規則は openRecurring が closedRecurring より優先。"after-holiday" の規則は、
|
|
15
|
+
* 祝日には当てはめず、その日以降で最初の祝日でない日に当てはめる(振替)
|
|
11
16
|
*
|
|
12
17
|
* 内部では、営業日かどうか(真偽値)を返すスケジュール(createSchedule)として作っている。
|
|
13
18
|
*/
|
|
14
19
|
import { type DateString, type Weekday } from "./date.ts";
|
|
15
20
|
import { IssueCollector } from "./errors.ts";
|
|
16
21
|
import { type DateInput } from "./input.ts";
|
|
17
|
-
import { type CompiledSchedule, type HolidayLookup, type HolidaySourceOptions } from "./schedule.ts";
|
|
22
|
+
import { type CompiledSchedule, type RecurringRule, type HolidayLookup, type HolidaySourceOptions } from "./schedule.ts";
|
|
18
23
|
import type { Holiday } from "./types.ts";
|
|
19
24
|
/**
|
|
20
25
|
* 祝日の扱い。
|
|
@@ -68,6 +73,13 @@ export interface CalendarOptions<Kind extends string = string> extends HolidaySo
|
|
|
68
73
|
readonly closedDates?: readonly (SpecificDate | DateRange)[];
|
|
69
74
|
/** 臨時営業日(ほかのすべての規則より優先) */
|
|
70
75
|
readonly openDates?: readonly (SpecificDate | DateRange)[];
|
|
76
|
+
/**
|
|
77
|
+
* 繰り返しの休業日(毎週・毎月の第 n 曜日・毎月の日)。
|
|
78
|
+
* 祝日に当たったときの扱いは規則ごとに onHolidayRule で指定する(既定: "under-holiday")
|
|
79
|
+
*/
|
|
80
|
+
readonly closedRecurring?: readonly RecurringRule[];
|
|
81
|
+
/** 繰り返しの営業日。closedRecurring と同じ日に当たる場合はこちらが優先 */
|
|
82
|
+
readonly openRecurring?: readonly RecurringRule[];
|
|
71
83
|
/** 祝日の扱い(既定: "closed") */
|
|
72
84
|
readonly holidayPolicy?: HolidayPolicy;
|
|
73
85
|
/** 営業日を探すときに調べる最大日数(既定: 3660)。超えたら Error */
|
|
@@ -77,7 +89,7 @@ export interface CalendarOptions<Kind extends string = string> extends HolidaySo
|
|
|
77
89
|
* 営業・休業を決めた規則(既知の値)。
|
|
78
90
|
* @public
|
|
79
91
|
*/
|
|
80
|
-
export type KnownDayReason = "open-date" | "closed-date" | "closed-annual" | "holiday" | "closed-weekday" | "regular";
|
|
92
|
+
export type KnownDayReason = "open-date" | "closed-date" | "closed-annual" | "open-recurring" | "closed-recurring" | "holiday" | "closed-weekday" | "regular";
|
|
81
93
|
/**
|
|
82
94
|
* 営業・休業を決めた規則。将来の版で値が増えることがあるので、分岐では「その他」も扱うこと。
|
|
83
95
|
* すべての値を網羅したことを型で確かめたい場合は KnownDayReason を使う。
|
|
@@ -98,6 +110,8 @@ export interface DayInfo<Kind extends string = string> {
|
|
|
98
110
|
readonly name?: string;
|
|
99
111
|
/** その日が祝日なら祝日(holidayPolicy に関係なく付く) */
|
|
100
112
|
readonly holiday?: Holiday<Kind>;
|
|
113
|
+
/** 祝日から振り替えられた繰り返しの規則で決まった場合、振替の元の日付(onHolidayRule: "after-holiday") */
|
|
114
|
+
readonly shiftedFrom?: DateString;
|
|
101
115
|
}
|
|
102
116
|
/**
|
|
103
117
|
* 営業日を探すときのオプション。
|
package/dist/core/calendar.js
CHANGED
|
@@ -5,16 +5,21 @@
|
|
|
5
5
|
* 1. openDates … 臨時営業日(個別の日付・期間)
|
|
6
6
|
* 2. closedDates … 臨時休業日(個別の日付・期間)
|
|
7
7
|
* 3. closedAnnual … 毎年の休業日(月日・月日の期間。年をまたぐ期間も可)
|
|
8
|
-
* 4.
|
|
9
|
-
* 5.
|
|
10
|
-
* 6.
|
|
8
|
+
* 4. openRecurring・closedRecurring(onHolidayRule: "over-holiday")… 祝日でも当てはめる繰り返しの規則
|
|
9
|
+
* 5. 祝日 … holidayPolicy に従う(closed: 休業 / open: 営業 / ignore: 判定に使わない)
|
|
10
|
+
* 6. openRecurring・closedRecurring("under-holiday"・"after-holiday" の振替先)… 繰り返しの規則
|
|
11
|
+
* 7. closedWeekdays … 定休日の曜日
|
|
12
|
+
* 8. それ以外は営業日
|
|
13
|
+
*
|
|
14
|
+
* 繰り返しの規則は openRecurring が closedRecurring より優先。"after-holiday" の規則は、
|
|
15
|
+
* 祝日には当てはめず、その日以降で最初の祝日でない日に当てはめる(振替)
|
|
11
16
|
*
|
|
12
17
|
* 内部では、営業日かどうか(真偽値)を返すスケジュール(createSchedule)として作っている。
|
|
13
18
|
*/
|
|
14
19
|
import { addDays, fromDayNumber, toDayNumber } from "./date.js";
|
|
15
20
|
import { checkUnknownKeys, IssueCollector, plainError, rangeError } from "./errors.js";
|
|
16
21
|
import { parseTimeOfDay, toCivil } from "./input.js";
|
|
17
|
-
import { buildSchedule, compileAnnualRules, compileDateRules, compileHolidaySource, isWeekday, } from "./schedule.js";
|
|
22
|
+
import { buildSchedule, compileAnnualRules, compileDateRules, compileHolidaySource, compileRecurringRules, isWeekday, RECURRING_RULE_KEYS, } from "./schedule.js";
|
|
18
23
|
const CALENDAR_OPTION_KEYS = [
|
|
19
24
|
"holidays",
|
|
20
25
|
"timeZone",
|
|
@@ -23,6 +28,8 @@ const CALENDAR_OPTION_KEYS = [
|
|
|
23
28
|
"closedAnnual",
|
|
24
29
|
"closedDates",
|
|
25
30
|
"openDates",
|
|
31
|
+
"closedRecurring",
|
|
32
|
+
"openRecurring",
|
|
26
33
|
"holidayPolicy",
|
|
27
34
|
"maxSearchDays",
|
|
28
35
|
];
|
|
@@ -61,6 +68,8 @@ export function compileCalendarOptions(c, options) {
|
|
|
61
68
|
const annual = compileAnnualRules(c, "closedAnnual", o["closedAnnual"], () => false, true);
|
|
62
69
|
const open = compileDateRules(c, "openDates", o["openDates"], () => true, true);
|
|
63
70
|
const closed = compileDateRules(c, "closedDates", o["closedDates"], () => false, true);
|
|
71
|
+
const openRecurring = compileRecurringRules(c, "openRecurring", o["openRecurring"], () => true, RECURRING_RULE_KEYS);
|
|
72
|
+
const closedRecurring = compileRecurringRules(c, "closedRecurring", o["closedRecurring"], () => false, RECURRING_RULE_KEYS);
|
|
64
73
|
return {
|
|
65
74
|
schedule: {
|
|
66
75
|
source,
|
|
@@ -69,6 +78,8 @@ export function compileCalendarOptions(c, options) {
|
|
|
69
78
|
annual,
|
|
70
79
|
// 臨時営業日を先に並べて、臨時休業日より優先する
|
|
71
80
|
dates: [...open, ...closed],
|
|
81
|
+
// 繰り返しの営業日を先に並べて、繰り返しの休業日より優先する
|
|
82
|
+
recurring: [...openRecurring, ...closedRecurring],
|
|
72
83
|
},
|
|
73
84
|
maxSearchDays: Number.isInteger(maxSearchDays) ? maxSearchDays : 3660,
|
|
74
85
|
};
|
|
@@ -98,6 +109,9 @@ export function createCalendar(options = {}) {
|
|
|
98
109
|
case "annual":
|
|
99
110
|
reason = "closed-annual";
|
|
100
111
|
break;
|
|
112
|
+
case "recurring":
|
|
113
|
+
reason = day.value ? "open-recurring" : "closed-recurring";
|
|
114
|
+
break;
|
|
101
115
|
case "holiday":
|
|
102
116
|
reason = "holiday";
|
|
103
117
|
break;
|
|
@@ -120,6 +134,8 @@ export function createCalendar(options = {}) {
|
|
|
120
134
|
result.name = day.name;
|
|
121
135
|
if (day.holiday !== undefined)
|
|
122
136
|
result.holiday = day.holiday;
|
|
137
|
+
if (day.shiftedFrom !== undefined)
|
|
138
|
+
result.shiftedFrom = day.shiftedFrom;
|
|
123
139
|
return Object.freeze(result);
|
|
124
140
|
}
|
|
125
141
|
const isOpen = (date) => schedule.valueOn(date);
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 規則の診断(diagnoseCalendar・diagnoseSchedule)。
|
|
3
|
+
*
|
|
4
|
+
* 設定を作るときと同じ検証(IssueCollector)で誤りと知らないキーを集め、誤りがなければ
|
|
5
|
+
* 規則どうしの関係を調べる。例外は投げず、見つけた問題を配列で返す。
|
|
6
|
+
*
|
|
7
|
+
* - error … createCalendar・createSchedule で例外になる誤り(code は例外と同じ)
|
|
8
|
+
* - warning … 作れるが、意図と違う可能性が高いもの
|
|
9
|
+
* unknown-option … 知らないキー(作成時は無視される)。近い既知のキーがあれば message に候補を挙げる
|
|
10
|
+
* shadowed … 同じ種類の先の規則に完全に覆われ、決して使われない(relatedPath に先の規則)
|
|
11
|
+
* never-matches … 当てはまる日がない繰り返しの規則(2 月 30 日など)
|
|
12
|
+
* out-of-range … 個別の日付が祝日の提供元の対応年の外にある(relatedPath に提供元)
|
|
13
|
+
* no-business-day … 営業日が 1 日もない設定(営業日カレンダーのみ)
|
|
14
|
+
* - info … redundant: 取り除いても結果が変わらない規則
|
|
15
|
+
*
|
|
16
|
+
* 判定の順番(個別の日付 → 毎年の月日 → 繰り返し(over)→ 祝日 → 繰り返し(under・振替先)→ 曜日)は
|
|
17
|
+
* 固定なので、shadowed は同じ種類の規則どうしに限って調べる。種類をまたいで使われない規則は
|
|
18
|
+
* redundant(取り除いても結果が変わらない)として見つかる。
|
|
19
|
+
*/
|
|
20
|
+
import { type CalendarOptions } from "./calendar.ts";
|
|
21
|
+
import { type KnownErrorCode } from "./errors.ts";
|
|
22
|
+
import { type ScheduleOptions } from "./schedule.ts";
|
|
23
|
+
/**
|
|
24
|
+
* 診断の重さ。
|
|
25
|
+
* - "error": 作成すると例外になる誤り
|
|
26
|
+
* - "warning": 作れるが、意図と違う可能性が高い
|
|
27
|
+
* - "info": 結果には影響しないが、整理できる
|
|
28
|
+
*
|
|
29
|
+
* 出力の列挙だが、重さの段階は増やさないため閉じた型にしている(docs/decisions.md の 15)。
|
|
30
|
+
* @public
|
|
31
|
+
*/
|
|
32
|
+
export type DiagnosticSeverity = "error" | "warning" | "info";
|
|
33
|
+
/**
|
|
34
|
+
* 既知の診断コード(増えることがある)。error の code は例外の code(KnownErrorCode)と同じ。
|
|
35
|
+
* @public
|
|
36
|
+
*/
|
|
37
|
+
export type KnownDiagnosticCode = KnownErrorCode
|
|
38
|
+
/** 知らないキー(作成時は無視される) */
|
|
39
|
+
| "unknown-option"
|
|
40
|
+
/** 同じ種類の先の規則に完全に覆われ、決して使われない */
|
|
41
|
+
| "shadowed"
|
|
42
|
+
/** 当てはまる日がない繰り返しの規則 */
|
|
43
|
+
| "never-matches"
|
|
44
|
+
/** 取り除いても結果が変わらない */
|
|
45
|
+
| "redundant";
|
|
46
|
+
/**
|
|
47
|
+
* 診断コード。将来の版で値が増えることがあるので、分岐では「その他」も扱うこと。
|
|
48
|
+
* @public
|
|
49
|
+
*/
|
|
50
|
+
export type DiagnosticCode = KnownDiagnosticCode | (string & {});
|
|
51
|
+
/**
|
|
52
|
+
* 診断の結果 1 件。message は英語で、文言は API に含まない(判定には code を使う)。
|
|
53
|
+
* @public
|
|
54
|
+
*/
|
|
55
|
+
export interface Diagnostic {
|
|
56
|
+
readonly severity: DiagnosticSeverity;
|
|
57
|
+
readonly code: DiagnosticCode;
|
|
58
|
+
/** 設定の中の位置(例: "closedRecurring[1]"・"closedDates[0].date")。設定全体なら "" */
|
|
59
|
+
readonly path: string;
|
|
60
|
+
readonly message: string;
|
|
61
|
+
/** 関係する別の位置(shadowed では覆っている先の規則、out-of-range では祝日の提供元) */
|
|
62
|
+
readonly relatedPath?: string;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* 営業日カレンダーの設定を診断する。例外は投げず、見つけた問題を返す(問題がなければ空の配列)。
|
|
66
|
+
*
|
|
67
|
+
* error があるときは、規則どうしの診断(shadowed・redundant など)は行わない。
|
|
68
|
+
* JavaScript から渡された型の合わない設定も受け付ける。
|
|
69
|
+
* @public
|
|
70
|
+
*/
|
|
71
|
+
export declare function diagnoseCalendar(options?: CalendarOptions | object): readonly Diagnostic[];
|
|
72
|
+
/**
|
|
73
|
+
* スケジュールの設定を診断する。例外は投げず、見つけた問題を返す(問題がなければ空の配列)。
|
|
74
|
+
*
|
|
75
|
+
* 値が同じかは、配列と普通のオブジェクトは中身で、それ以外は Object.is で比べる。
|
|
76
|
+
* error があるときは、規則どうしの診断(shadowed・redundant など)は行わない。
|
|
77
|
+
* @public
|
|
78
|
+
*/
|
|
79
|
+
export declare function diagnoseSchedule(options: ScheduleOptions<unknown> | object): readonly Diagnostic[];
|