bizdays-toolkit 0.2.0 → 0.3.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 +31 -1
- package/README.md +21 -2
- package/dist/core/calendar.d.ts +63 -10
- package/dist/core/calendar.js +77 -46
- package/dist/core/date.d.ts +24 -6
- package/dist/core/date.js +24 -10
- package/dist/core/errors.d.ts +78 -0
- package/dist/core/errors.js +61 -0
- package/dist/core/input.d.ts +25 -10
- package/dist/core/input.js +41 -21
- package/dist/core/schedule.d.ts +110 -16
- package/dist/core/schedule.js +296 -73
- package/dist/core/types.d.ts +11 -3
- package/dist/index.d.ts +7 -3
- package/dist/index.js +5 -1
- package/dist/jp/index.d.ts +41 -12
- package/dist/jp/index.js +36 -10
- package/dist/jp/rules.d.ts +17 -8
- package/dist/jp/rules.js +8 -7
- package/package.json +5 -3
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,35 @@
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [0.3.0] - 2026-10-15
|
|
8
|
+
|
|
9
|
+
1.0.0 に向けて、将来の機能追加で利用者のコードが壊れにくい作りにしました。機能の追加はありません。
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- 例外に `code` を付けた(`invalid-date`・`invalid-range`・`no-business-day` など)。例外は従来どおり `RangeError`・`TypeError`・`Error` のインスタンス。`isBizdaysError()` で判定できる
|
|
14
|
+
- `KnownDayReason`・`KnownScheduleReason`・`KnownJapaneseHolidayKind`・`KnownErrorCode`: 既知の値だけの閉じた型(網羅を型で確かめたい場合に使う)
|
|
15
|
+
- 祝日の提供元(`HolidayProvider`)の形式を作る時点で、返したデータを年ごとに初めて使うときに検証する(誤りは `invalid-provider`・`invalid-provider-data`)
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
|
|
19
|
+
- `DayReason`・`ScheduleReason`・`JapaneseHolidayKind` を「開いた型」にした(将来の版で値が増えても、利用者のコードがコンパイルエラーにならない)。値を列挙して分岐しているコードは「その他」の分岐が必要になる
|
|
20
|
+
- 設定の誤りのメッセージに、設定の中の位置(例: `closedDates[1].date`)を入れた
|
|
21
|
+
- 設定は作る時点で複製するようにした(作ったあとに元の配列を書き換えても、結果が変わらない)
|
|
22
|
+
- 提供元が返した祝日は、契約の項目だけを持つ変更できないオブジェクトにして返す
|
|
23
|
+
- 別の実行環境(iframe・`vm`)で作られた `Date` も受け付けるようにした
|
|
24
|
+
|
|
25
|
+
### Removed
|
|
26
|
+
|
|
27
|
+
- `vernalEquinoxDay`・`autumnalEquinoxDay`(`bizdays-toolkit/jp`): 祝日の計算の内部の手段のため非公開にした
|
|
28
|
+
- `nthWeekdayOfMonth`: 内部の関数のため非公開にした
|
|
29
|
+
|
|
30
|
+
### 開発
|
|
31
|
+
|
|
32
|
+
- 品質の確認を追加: タイムゾーン(`TZ`)を変えたテスト、性質のテスト、型のテスト、サイズの上限(`size-limits.json`)、公開 API のスナップショット(`etc/api.md`)
|
|
33
|
+
- 互換性の約束と安定度の表示(`@public`・`@beta`)を RELEASING.md に、設計上の決定を `docs/decisions.md` に記載
|
|
34
|
+
- `SECURITY.md` を追加(脆弱性の非公開の報告先)
|
|
35
|
+
|
|
7
36
|
## [0.2.0] - 2026-10-14
|
|
8
37
|
|
|
9
38
|
### Added
|
|
@@ -50,7 +79,8 @@
|
|
|
50
79
|
|
|
51
80
|
- 日本: 2026 年 2 月の官報(2027 年の春分・秋分の日)まで反映(`dataVersion: "2026-02"`、`confirmedThrough: 2027`)
|
|
52
81
|
|
|
53
|
-
[Unreleased]: https://github.com/inohan77/bizdays-toolkit/compare/v0.
|
|
82
|
+
[Unreleased]: https://github.com/inohan77/bizdays-toolkit/compare/v0.3.0...HEAD
|
|
83
|
+
[0.3.0]: https://github.com/inohan77/bizdays-toolkit/compare/v0.2.0...v0.3.0
|
|
54
84
|
[0.2.0]: https://github.com/inohan77/bizdays-toolkit/compare/v0.1.2...v0.2.0
|
|
55
85
|
[0.1.2]: https://github.com/inohan77/bizdays-toolkit/compare/v0.1.1...v0.1.2
|
|
56
86
|
[0.1.1]: https://github.com/inohan77/bizdays-toolkit/compare/v0.1.0...v0.1.1
|
package/README.md
CHANGED
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
- **日ごとの値(営業時間など)**: 「火曜定休、祝日は日曜の営業時間、元日は休業、大晦日は短縮営業」のように、曜日・祝日・特定日で変わる値を同じ規則で引けます
|
|
21
21
|
- **営業日の計算**: 定休日の曜日・毎年の休業日(年末年始のような年をまたぐ期間も可)・臨時休業日・臨時営業日と祝日を組み合わせ、「N 営業日後」「締め時刻を過ぎたら翌営業日」などを計算します
|
|
22
22
|
- **タイムゾーンを間違えない**: `Date` などの瞬間は、カレンダーのタイムゾーン(日本は `Asia/Tokyo`)で暦日に変換してから判定します。UTC で動くサーバーでも、日本時間 0〜9 時が前日扱いになりません
|
|
23
|
-
- **軽量・依存ゼロ**: minify + gzip で約
|
|
23
|
+
- **軽量・依存ゼロ**: minify + gzip で約 5.5KB(日本の祝日込みで約 7KB)。ESM・型定義同梱・`sideEffects: false` で、Node.js・ブラウザー・Edge ランタイムで動作します
|
|
24
24
|
- **他の国・地域に拡張できる**: 祝日の提供元はインターフェース(`HolidayProvider`)で差し替えられます。現在は日本のみ同梱しています
|
|
25
25
|
|
|
26
26
|
[English](#english)
|
|
@@ -169,6 +169,24 @@ const calendar = createCalendar({ holidays: myCountry, closedWeekdays: [0, 6] })
|
|
|
169
169
|
|
|
170
170
|
祝日の提供元の対応年(日本は 1955〜2099 年)を外れた日付を判定すると `RangeError` になります。祝日がない日として扱いたい場合は `outOfRange: "ignore"` を指定してください。
|
|
171
171
|
|
|
172
|
+
### エラー
|
|
173
|
+
|
|
174
|
+
設定や入力の誤りは、従来どおり `RangeError`・`TypeError` などの例外になり、`code` を持ちます。メッセージの文言は変わることがあるので、判定には `code` を使ってください。
|
|
175
|
+
|
|
176
|
+
```ts
|
|
177
|
+
import { createCalendar, isBizdaysError } from "bizdays-toolkit";
|
|
178
|
+
|
|
179
|
+
try {
|
|
180
|
+
createCalendar({ closedDates: ["2026-12-32"] });
|
|
181
|
+
} catch (error) {
|
|
182
|
+
if (isBizdaysError(error) && error.code === "invalid-date") {
|
|
183
|
+
// 日付の誤り(メッセージには設定の中の位置 "closedDates[0].date" が入る)
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
主なコード: `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
|
+
|
|
172
190
|
## バージョン番号とデータの更新
|
|
173
191
|
|
|
174
192
|
[Semantic Versioning](https://semver.org/lang/ja/) に従います。詳しくは [RELEASING.md](./RELEASING.md) を参照してください。
|
|
@@ -177,6 +195,7 @@ const calendar = createCalendar({ holidays: myCountry, closedWeekdays: [0, 6] })
|
|
|
177
195
|
- 祝日の判定結果を固定したい場合は、版を完全一致で指定してください(例: `"bizdays-toolkit": "0.1.0"`)
|
|
178
196
|
- 反映済みのデータは `dataVersion`(日本は反映済みの官報の年月)と `confirmedThrough` で確認できます
|
|
179
197
|
- 1.0.0 までは、マイナー版で破壊的変更を行うことがあります
|
|
198
|
+
- 判定の理由(`reason`)・祝日の種別(`kind`)・エラーコードは、将来の版で値が増えることがあります。値を列挙して分岐するときは「その他」も扱ってください([RELEASING.md](./RELEASING.md) の「互換性の約束」)
|
|
180
199
|
|
|
181
200
|
## 出典
|
|
182
201
|
|
|
@@ -196,7 +215,7 @@ A business-day calendar that combines public holidays with company or store clos
|
|
|
196
215
|
- **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
|
|
197
216
|
- **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
|
|
198
217
|
- **Time-zone safe**: instants (`Date`, ISO strings with offsets, Temporal) are converted to a civil date in the calendar's time zone before evaluation
|
|
199
|
-
- **Lightweight, zero dependencies**: about
|
|
218
|
+
- **Lightweight, zero dependencies**: about 5.5 KB min+gzip (about 7 KB with Japanese holidays), ESM, bundled types, `sideEffects: false`
|
|
200
219
|
- **Extensible** through the `HolidayProvider` interface (only Japan is bundled for now)
|
|
201
220
|
|
|
202
221
|
```ts
|
package/dist/core/calendar.d.ts
CHANGED
|
@@ -12,33 +12,53 @@
|
|
|
12
12
|
* 内部では、営業日かどうか(真偽値)を返すスケジュール(createSchedule)として作っている。
|
|
13
13
|
*/
|
|
14
14
|
import { type DateString, type Weekday } from "./date.ts";
|
|
15
|
+
import { IssueCollector } from "./errors.ts";
|
|
15
16
|
import { type DateInput } from "./input.ts";
|
|
16
|
-
import { type HolidayLookup, type HolidaySourceOptions } from "./schedule.ts";
|
|
17
|
+
import { type CompiledSchedule, type HolidayLookup, type HolidaySourceOptions } from "./schedule.ts";
|
|
17
18
|
import type { Holiday } from "./types.ts";
|
|
18
|
-
/**
|
|
19
|
+
/**
|
|
20
|
+
* 祝日の扱い。
|
|
21
|
+
* @public
|
|
22
|
+
*/
|
|
19
23
|
export type HolidayPolicy = "closed" | "open" | "ignore";
|
|
20
|
-
/**
|
|
24
|
+
/**
|
|
25
|
+
* 毎年の月日 "MM-DD"(名前付きも可)。
|
|
26
|
+
* @public
|
|
27
|
+
*/
|
|
21
28
|
export type AnnualDate = string | {
|
|
22
29
|
readonly date: string;
|
|
23
30
|
readonly name?: string;
|
|
24
31
|
};
|
|
25
|
-
/**
|
|
32
|
+
/**
|
|
33
|
+
* 毎年の月日の期間(from > to なら年をまたぐ。例: 12-31 〜 01-03)。
|
|
34
|
+
* @public
|
|
35
|
+
*/
|
|
26
36
|
export interface AnnualRange {
|
|
27
37
|
readonly from: string;
|
|
28
38
|
readonly to: string;
|
|
29
39
|
readonly name?: string;
|
|
30
40
|
}
|
|
31
|
-
/**
|
|
41
|
+
/**
|
|
42
|
+
* 個別の日付 "YYYY-MM-DD"(名前付きも可)。
|
|
43
|
+
* @public
|
|
44
|
+
*/
|
|
32
45
|
export type SpecificDate = DateString | {
|
|
33
46
|
readonly date: DateString;
|
|
34
47
|
readonly name?: string;
|
|
35
48
|
};
|
|
36
|
-
/**
|
|
49
|
+
/**
|
|
50
|
+
* 個別の日付の期間(両端を含む)。
|
|
51
|
+
* @public
|
|
52
|
+
*/
|
|
37
53
|
export interface DateRange {
|
|
38
54
|
readonly from: DateString;
|
|
39
55
|
readonly to: DateString;
|
|
40
56
|
readonly name?: string;
|
|
41
57
|
}
|
|
58
|
+
/**
|
|
59
|
+
* 営業日カレンダーの設定。
|
|
60
|
+
* @public
|
|
61
|
+
*/
|
|
42
62
|
export interface CalendarOptions<Kind extends string = string> extends HolidaySourceOptions<Kind> {
|
|
43
63
|
/** 定休日の曜日(0 = 日曜日) */
|
|
44
64
|
readonly closedWeekdays?: readonly Weekday[];
|
|
@@ -53,9 +73,21 @@ export interface CalendarOptions<Kind extends string = string> extends HolidaySo
|
|
|
53
73
|
/** 営業日を探すときに調べる最大日数(既定: 3660)。超えたら Error */
|
|
54
74
|
readonly maxSearchDays?: number;
|
|
55
75
|
}
|
|
56
|
-
/**
|
|
57
|
-
|
|
58
|
-
|
|
76
|
+
/**
|
|
77
|
+
* 営業・休業を決めた規則(既知の値)。
|
|
78
|
+
* @public
|
|
79
|
+
*/
|
|
80
|
+
export type KnownDayReason = "open-date" | "closed-date" | "closed-annual" | "holiday" | "closed-weekday" | "regular";
|
|
81
|
+
/**
|
|
82
|
+
* 営業・休業を決めた規則。将来の版で値が増えることがあるので、分岐では「その他」も扱うこと。
|
|
83
|
+
* すべての値を網羅したことを型で確かめたい場合は KnownDayReason を使う。
|
|
84
|
+
* @public
|
|
85
|
+
*/
|
|
86
|
+
export type DayReason = KnownDayReason | (string & {});
|
|
87
|
+
/**
|
|
88
|
+
* 1 日分の判定結果。
|
|
89
|
+
* @public
|
|
90
|
+
*/
|
|
59
91
|
export interface DayInfo<Kind extends string = string> {
|
|
60
92
|
readonly date: DateString;
|
|
61
93
|
readonly weekday: Weekday;
|
|
@@ -67,6 +99,10 @@ export interface DayInfo<Kind extends string = string> {
|
|
|
67
99
|
/** その日が祝日なら祝日(holidayPolicy に関係なく付く) */
|
|
68
100
|
readonly holiday?: Holiday<Kind>;
|
|
69
101
|
}
|
|
102
|
+
/**
|
|
103
|
+
* 営業日を探すときのオプション。
|
|
104
|
+
* @public
|
|
105
|
+
*/
|
|
70
106
|
export interface BusinessDayOptions {
|
|
71
107
|
/**
|
|
72
108
|
* 締め時刻 "HH:mm"(カレンダーのタイムゾーン)。
|
|
@@ -74,6 +110,10 @@ export interface BusinessDayOptions {
|
|
|
74
110
|
*/
|
|
75
111
|
readonly cutoff?: string;
|
|
76
112
|
}
|
|
113
|
+
/**
|
|
114
|
+
* 営業日カレンダー。
|
|
115
|
+
* @public
|
|
116
|
+
*/
|
|
77
117
|
export interface Calendar<Kind extends string = string> extends HolidayLookup<Kind> {
|
|
78
118
|
/** 1 日分の判定結果(営業日か・理由・祝日) */
|
|
79
119
|
dayInfo(input: DateInput): DayInfo<Kind>;
|
|
@@ -97,5 +137,18 @@ export interface Calendar<Kind extends string = string> extends HolidayLookup<Ki
|
|
|
97
137
|
/** 期間内の営業日(両端を含む) */
|
|
98
138
|
listBusinessDays(from: DateInput, to: DateInput): readonly DateString[];
|
|
99
139
|
}
|
|
100
|
-
|
|
140
|
+
interface CompiledCalendar<Kind extends string> {
|
|
141
|
+
readonly schedule: CompiledSchedule<boolean, Kind>;
|
|
142
|
+
readonly maxSearchDays: number;
|
|
143
|
+
}
|
|
144
|
+
/** @internal 営業日カレンダーの設定を検証して取り込む(誤りは c に集める) */
|
|
145
|
+
export declare function compileCalendarOptions<Kind extends string>(c: IssueCollector, options: unknown): CompiledCalendar<Kind> | undefined;
|
|
146
|
+
/**
|
|
147
|
+
* 祝日と休業日の規則から営業日カレンダーを作る。
|
|
148
|
+
*
|
|
149
|
+
* 設定は作る時点で取り込まれるので、あとから元の配列を書き換えても結果は変わらない。
|
|
150
|
+
* 設定の誤りは code 付きの RangeError になる。
|
|
151
|
+
* @public
|
|
152
|
+
*/
|
|
101
153
|
export declare function createCalendar<Kind extends string = string>(options?: CalendarOptions<Kind>): Calendar<Kind>;
|
|
154
|
+
export {};
|
package/dist/core/calendar.js
CHANGED
|
@@ -12,59 +12,86 @@
|
|
|
12
12
|
* 内部では、営業日かどうか(真偽値)を返すスケジュール(createSchedule)として作っている。
|
|
13
13
|
*/
|
|
14
14
|
import { addDays, fromDayNumber, toDayNumber } from "./date.js";
|
|
15
|
+
import { checkUnknownKeys, IssueCollector, plainError, rangeError } from "./errors.js";
|
|
15
16
|
import { parseTimeOfDay, toCivil } from "./input.js";
|
|
16
|
-
import {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
17
|
+
import { buildSchedule, compileAnnualRules, compileDateRules, compileHolidaySource, isWeekday, } from "./schedule.js";
|
|
18
|
+
const CALENDAR_OPTION_KEYS = [
|
|
19
|
+
"holidays",
|
|
20
|
+
"timeZone",
|
|
21
|
+
"outOfRange",
|
|
22
|
+
"closedWeekdays",
|
|
23
|
+
"closedAnnual",
|
|
24
|
+
"closedDates",
|
|
25
|
+
"openDates",
|
|
26
|
+
"holidayPolicy",
|
|
27
|
+
"maxSearchDays",
|
|
28
|
+
];
|
|
29
|
+
/** @internal 営業日カレンダーの設定を検証して取り込む(誤りは c に集める) */
|
|
30
|
+
export function compileCalendarOptions(c, options) {
|
|
31
|
+
if (typeof options !== "object" || options === null || Array.isArray(options)) {
|
|
32
|
+
c.error("invalid-option", "", "options must be an object");
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|
|
35
|
+
const o = options;
|
|
36
|
+
checkUnknownKeys(c, "", o, CALENDAR_OPTION_KEYS);
|
|
37
|
+
const source = compileHolidaySource(c, o);
|
|
28
38
|
const closedWeekdays = new Set();
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
39
|
+
const rawWeekdays = o["closedWeekdays"];
|
|
40
|
+
if (rawWeekdays !== undefined) {
|
|
41
|
+
if (!Array.isArray(rawWeekdays)) {
|
|
42
|
+
c.error("invalid-option", "closedWeekdays", "must be an array");
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
rawWeekdays.forEach((w, i) => {
|
|
46
|
+
if (isWeekday(w))
|
|
47
|
+
closedWeekdays.add(w);
|
|
48
|
+
else
|
|
49
|
+
c.error("invalid-weekday", `closedWeekdays[${i}]`, "must be an integer from 0 (Sunday) to 6 (Saturday)");
|
|
50
|
+
});
|
|
51
|
+
}
|
|
33
52
|
}
|
|
34
|
-
const holidayPolicy =
|
|
35
|
-
if (
|
|
36
|
-
|
|
53
|
+
const holidayPolicy = o["holidayPolicy"] ?? "closed";
|
|
54
|
+
if (holidayPolicy !== "closed" && holidayPolicy !== "open" && holidayPolicy !== "ignore") {
|
|
55
|
+
c.error("invalid-option", "holidayPolicy", 'must be "closed", "open" or "ignore"');
|
|
37
56
|
}
|
|
38
|
-
const maxSearchDays =
|
|
57
|
+
const maxSearchDays = o["maxSearchDays"] ?? 3660;
|
|
39
58
|
if (!Number.isInteger(maxSearchDays) || maxSearchDays < 1) {
|
|
40
|
-
|
|
59
|
+
c.error("invalid-option", "maxSearchDays", "must be a positive integer");
|
|
41
60
|
}
|
|
42
|
-
const
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
61
|
+
const annual = compileAnnualRules(c, "closedAnnual", o["closedAnnual"], () => false, true);
|
|
62
|
+
const open = compileDateRules(c, "openDates", o["openDates"], () => true, true);
|
|
63
|
+
const closed = compileDateRules(c, "closedDates", o["closedDates"], () => false, true);
|
|
64
|
+
return {
|
|
65
|
+
schedule: {
|
|
66
|
+
source,
|
|
67
|
+
weekly: Object.freeze([0, 1, 2, 3, 4, 5, 6].map((w) => !closedWeekdays.has(w))),
|
|
68
|
+
holiday: holidayPolicy === "ignore" ? "weekday" : { value: holidayPolicy === "open" },
|
|
69
|
+
annual,
|
|
70
|
+
// 臨時営業日を先に並べて、臨時休業日より優先する
|
|
71
|
+
dates: [...open, ...closed],
|
|
72
|
+
},
|
|
73
|
+
maxSearchDays: Number.isInteger(maxSearchDays) ? maxSearchDays : 3660,
|
|
47
74
|
};
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
dates: [...toDateValues(options.openDates, true), ...toDateValues(options.closedDates, false)],
|
|
63
|
-
});
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* 祝日と休業日の規則から営業日カレンダーを作る。
|
|
78
|
+
*
|
|
79
|
+
* 設定は作る時点で取り込まれるので、あとから元の配列を書き換えても結果は変わらない。
|
|
80
|
+
* 設定の誤りは code 付きの RangeError になる。
|
|
81
|
+
* @public
|
|
82
|
+
*/
|
|
83
|
+
export function createCalendar(options = {}) {
|
|
84
|
+
const c = new IssueCollector();
|
|
85
|
+
const compiled = compileCalendarOptions(c, options);
|
|
86
|
+
c.throwFirstError();
|
|
87
|
+
const { maxSearchDays } = compiled;
|
|
88
|
+
const schedule = buildSchedule(compiled.schedule);
|
|
64
89
|
function info(date) {
|
|
65
90
|
const day = schedule.dayInfo(date);
|
|
66
91
|
let reason;
|
|
67
|
-
|
|
92
|
+
// 内部では閉じた型(KnownScheduleReason)で分岐し、値が増えたときの対応漏れを型で検出する
|
|
93
|
+
const known = day.reason;
|
|
94
|
+
switch (known) {
|
|
68
95
|
case "date":
|
|
69
96
|
reason = day.value ? "open-date" : "closed-date";
|
|
70
97
|
break;
|
|
@@ -77,6 +104,10 @@ export function createCalendar(options = {}) {
|
|
|
77
104
|
case "weekday":
|
|
78
105
|
reason = day.value ? "regular" : "closed-weekday";
|
|
79
106
|
break;
|
|
107
|
+
default: {
|
|
108
|
+
const unreachable = known;
|
|
109
|
+
throw plainError("invalid-option", `Unexpected schedule reason: ${String(unreachable)}`);
|
|
110
|
+
}
|
|
80
111
|
}
|
|
81
112
|
const result = {
|
|
82
113
|
date: day.date,
|
|
@@ -100,7 +131,7 @@ export function createCalendar(options = {}) {
|
|
|
100
131
|
if (isOpen(date))
|
|
101
132
|
return date;
|
|
102
133
|
}
|
|
103
|
-
throw
|
|
134
|
+
throw plainError("no-business-day", `No business day found within ${maxSearchDays} days from ${start}`);
|
|
104
135
|
}
|
|
105
136
|
function countOpen(from, toExclusive) {
|
|
106
137
|
let count = 0;
|
|
@@ -136,7 +167,7 @@ export function createCalendar(options = {}) {
|
|
|
136
167
|
businessDayOnOrBefore: (input) => search(dateOf(input), -1),
|
|
137
168
|
addBusinessDays(input, n) {
|
|
138
169
|
if (!Number.isInteger(n))
|
|
139
|
-
throw
|
|
170
|
+
throw rangeError("invalid-argument", `n must be an integer: ${n}`);
|
|
140
171
|
let date = dateOf(input);
|
|
141
172
|
if (n === 0)
|
|
142
173
|
return search(date, 1);
|
|
@@ -155,7 +186,7 @@ export function createCalendar(options = {}) {
|
|
|
155
186
|
const a = dateOf(from);
|
|
156
187
|
const b = dateOf(to);
|
|
157
188
|
if (a > b)
|
|
158
|
-
throw
|
|
189
|
+
throw rangeError("invalid-range", `"from" must not be after "to" (${a} > ${b})`);
|
|
159
190
|
const result = [];
|
|
160
191
|
for (let n = toDayNumber(a); n <= toDayNumber(b); n++) {
|
|
161
192
|
const date = fromDayNumber(n);
|
package/dist/core/date.d.ts
CHANGED
|
@@ -4,9 +4,15 @@
|
|
|
4
4
|
* 日付は常に "YYYY-MM-DD" 文字列で扱い、内部では 1970-01-01 からの日数(day number)に
|
|
5
5
|
* 変換して計算する。Date オブジェクトのローカル時刻は使わない。
|
|
6
6
|
*/
|
|
7
|
-
/**
|
|
7
|
+
/**
|
|
8
|
+
* 暦日を表す "YYYY-MM-DD" 文字列。
|
|
9
|
+
* @public
|
|
10
|
+
*/
|
|
8
11
|
export type DateString = string;
|
|
9
|
-
/**
|
|
12
|
+
/**
|
|
13
|
+
* 曜日(0 = 日曜日 … 6 = 土曜日。Date#getDay と同じ)。
|
|
14
|
+
* @public
|
|
15
|
+
*/
|
|
10
16
|
export type Weekday = 0 | 1 | 2 | 3 | 4 | 5 | 6;
|
|
11
17
|
/** 年月日に分解する。形式または暦として不正なら RangeError */
|
|
12
18
|
export declare function parseDate(value: string): {
|
|
@@ -14,7 +20,10 @@ export declare function parseDate(value: string): {
|
|
|
14
20
|
month: number;
|
|
15
21
|
day: number;
|
|
16
22
|
};
|
|
17
|
-
/**
|
|
23
|
+
/**
|
|
24
|
+
* "YYYY-MM-DD" として正しい暦日か。
|
|
25
|
+
* @public
|
|
26
|
+
*/
|
|
18
27
|
export declare function isValidDate(value: unknown): value is DateString;
|
|
19
28
|
/** 年月日から "YYYY-MM-DD" を作る(範囲外なら RangeError) */
|
|
20
29
|
export declare function formatDate(year: number, month: number, day: number): DateString;
|
|
@@ -26,11 +35,20 @@ export declare function daysInMonth(year: number, month: number): number;
|
|
|
26
35
|
export declare function toDayNumber(date: DateString): number;
|
|
27
36
|
/** 1970-01-01 からの日数を "YYYY-MM-DD" に戻す */
|
|
28
37
|
export declare function fromDayNumber(dayNumber: number): DateString;
|
|
29
|
-
/**
|
|
38
|
+
/**
|
|
39
|
+
* n 日後(負なら前)の日付。
|
|
40
|
+
* @public
|
|
41
|
+
*/
|
|
30
42
|
export declare function addDays(date: DateString, days: number): DateString;
|
|
31
|
-
/**
|
|
43
|
+
/**
|
|
44
|
+
* from から to までの日数(to が後なら正)。
|
|
45
|
+
* @public
|
|
46
|
+
*/
|
|
32
47
|
export declare function diffDays(from: DateString, to: DateString): number;
|
|
33
|
-
/**
|
|
48
|
+
/**
|
|
49
|
+
* 曜日(0 = 日曜日)。
|
|
50
|
+
* @public
|
|
51
|
+
*/
|
|
34
52
|
export declare function weekdayOf(date: DateString): Weekday;
|
|
35
53
|
/** その月の第 n 何曜日(n は 1 始まり) */
|
|
36
54
|
export declare function nthWeekdayOfMonth(year: number, month: number, weekday: Weekday, n: number): DateString;
|
package/dist/core/date.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* 日付は常に "YYYY-MM-DD" 文字列で扱い、内部では 1970-01-01 からの日数(day number)に
|
|
5
5
|
* 変換して計算する。Date オブジェクトのローカル時刻は使わない。
|
|
6
6
|
*/
|
|
7
|
+
import { rangeError } from "./errors.js";
|
|
7
8
|
const DATE_RE = /^(\d{4})-(\d{2})-(\d{2})$/;
|
|
8
9
|
const MS_PER_DAY = 86_400_000;
|
|
9
10
|
const MONTH_DAYS = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
|
|
@@ -11,17 +12,20 @@ const MONTH_DAYS = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
|
|
|
11
12
|
export function parseDate(value) {
|
|
12
13
|
const m = DATE_RE.exec(value);
|
|
13
14
|
if (!m) {
|
|
14
|
-
throw
|
|
15
|
+
throw rangeError("invalid-date", `Invalid date string: ${JSON.stringify(value)} (expected YYYY-MM-DD)`);
|
|
15
16
|
}
|
|
16
17
|
const year = Number(m[1]);
|
|
17
18
|
const month = Number(m[2]);
|
|
18
19
|
const day = Number(m[3]);
|
|
19
20
|
if (year < 1000 || month < 1 || month > 12 || day < 1 || day > daysInMonth(year, month)) {
|
|
20
|
-
throw
|
|
21
|
+
throw rangeError("invalid-date", `Invalid date: ${value}`);
|
|
21
22
|
}
|
|
22
23
|
return { year, month, day };
|
|
23
24
|
}
|
|
24
|
-
/**
|
|
25
|
+
/**
|
|
26
|
+
* "YYYY-MM-DD" として正しい暦日か。
|
|
27
|
+
* @public
|
|
28
|
+
*/
|
|
25
29
|
export function isValidDate(value) {
|
|
26
30
|
if (typeof value !== "string")
|
|
27
31
|
return false;
|
|
@@ -36,7 +40,7 @@ export function isValidDate(value) {
|
|
|
36
40
|
/** 年月日から "YYYY-MM-DD" を作る(範囲外なら RangeError) */
|
|
37
41
|
export function formatDate(year, month, day) {
|
|
38
42
|
if (!Number.isInteger(year) || year < 1000 || year > 9999) {
|
|
39
|
-
throw
|
|
43
|
+
throw rangeError("out-of-range", `Year out of range (1000-9999): ${year}`);
|
|
40
44
|
}
|
|
41
45
|
const s = `${year}-${String(month).padStart(2, "0")}-${String(day).padStart(2, "0")}`;
|
|
42
46
|
parseDate(s);
|
|
@@ -52,7 +56,7 @@ export function daysInMonth(year, month) {
|
|
|
52
56
|
return isLeapYear(year) ? 29 : 28;
|
|
53
57
|
const days = MONTH_DAYS[month - 1];
|
|
54
58
|
if (days === undefined)
|
|
55
|
-
throw
|
|
59
|
+
throw rangeError("invalid-date", `Invalid month: ${month}`);
|
|
56
60
|
return days;
|
|
57
61
|
}
|
|
58
62
|
/** 1970-01-01 からの日数 */
|
|
@@ -65,17 +69,27 @@ export function fromDayNumber(dayNumber) {
|
|
|
65
69
|
const d = new Date(dayNumber * MS_PER_DAY);
|
|
66
70
|
return formatDate(d.getUTCFullYear(), d.getUTCMonth() + 1, d.getUTCDate());
|
|
67
71
|
}
|
|
68
|
-
/**
|
|
72
|
+
/**
|
|
73
|
+
* n 日後(負なら前)の日付。
|
|
74
|
+
* @public
|
|
75
|
+
*/
|
|
69
76
|
export function addDays(date, days) {
|
|
70
|
-
if (!Number.isInteger(days))
|
|
71
|
-
throw
|
|
77
|
+
if (!Number.isInteger(days)) {
|
|
78
|
+
throw rangeError("invalid-argument", `days must be an integer: ${days}`);
|
|
79
|
+
}
|
|
72
80
|
return fromDayNumber(toDayNumber(date) + days);
|
|
73
81
|
}
|
|
74
|
-
/**
|
|
82
|
+
/**
|
|
83
|
+
* from から to までの日数(to が後なら正)。
|
|
84
|
+
* @public
|
|
85
|
+
*/
|
|
75
86
|
export function diffDays(from, to) {
|
|
76
87
|
return toDayNumber(to) - toDayNumber(from);
|
|
77
88
|
}
|
|
78
|
-
/**
|
|
89
|
+
/**
|
|
90
|
+
* 曜日(0 = 日曜日)。
|
|
91
|
+
* @public
|
|
92
|
+
*/
|
|
79
93
|
export function weekdayOf(date) {
|
|
80
94
|
// 1970-01-01 は木曜日(4)
|
|
81
95
|
return ((((toDayNumber(date) + 4) % 7) + 7) % 7);
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* コード付きの例外と、入力の問題を集める仕組み。
|
|
3
|
+
*
|
|
4
|
+
* 例外は従来どおり RangeError・TypeError・Error のインスタンスで、code プロパティを持つ。
|
|
5
|
+
* 例外のメッセージの文言は API に含まない(変わることがある)。判定には code を使う。
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* 既知のエラーコード(増えることがある)。
|
|
9
|
+
* @public
|
|
10
|
+
*/
|
|
11
|
+
export type KnownErrorCode =
|
|
12
|
+
/** 日付 "YYYY-MM-DD" の形式・値の誤り */
|
|
13
|
+
"invalid-date"
|
|
14
|
+
/** 毎年の月日 "MM-DD" の形式・値の誤り */
|
|
15
|
+
| "invalid-month-day"
|
|
16
|
+
/** 時刻・日時の形式・値の誤り */
|
|
17
|
+
| "invalid-time"
|
|
18
|
+
/** 受け付けない種類の入力 */
|
|
19
|
+
| "invalid-input"
|
|
20
|
+
/** IANA タイムゾーン名の誤り */
|
|
21
|
+
| "invalid-time-zone"
|
|
22
|
+
/** 期間の開始が終了より後 */
|
|
23
|
+
| "invalid-range"
|
|
24
|
+
/** 曜日の番号(0〜6)の誤り */
|
|
25
|
+
| "invalid-weekday"
|
|
26
|
+
/** 引数の値の誤り(整数でない日数など) */
|
|
27
|
+
| "invalid-argument"
|
|
28
|
+
/** 設定の値の誤り */
|
|
29
|
+
| "invalid-option"
|
|
30
|
+
/** 祝日の提供元どうしのタイムゾーンが異なり、timeZone の指定が必要 */
|
|
31
|
+
| "time-zone-conflict"
|
|
32
|
+
/** 対応している年の範囲の外 */
|
|
33
|
+
| "out-of-range"
|
|
34
|
+
/** 祝日の提供元の形式の誤り */
|
|
35
|
+
| "invalid-provider"
|
|
36
|
+
/** 祝日の提供元が返したデータの誤り */
|
|
37
|
+
| "invalid-provider-data"
|
|
38
|
+
/** 指定した日数の範囲に営業日がない */
|
|
39
|
+
| "no-business-day";
|
|
40
|
+
/**
|
|
41
|
+
* エラーコード。将来の版で値が増えることがある。
|
|
42
|
+
* @public
|
|
43
|
+
*/
|
|
44
|
+
export type ErrorCode = KnownErrorCode | (string & {});
|
|
45
|
+
/**
|
|
46
|
+
* このライブラリが投げた例外(code 付き)か。
|
|
47
|
+
* @public
|
|
48
|
+
*/
|
|
49
|
+
export declare function isBizdaysError(error: unknown): error is Error & {
|
|
50
|
+
readonly code: ErrorCode;
|
|
51
|
+
};
|
|
52
|
+
/** @internal */
|
|
53
|
+
export declare function rangeError(code: KnownErrorCode, message: string): RangeError;
|
|
54
|
+
/** @internal */
|
|
55
|
+
export declare function typeError(code: KnownErrorCode, message: string): TypeError;
|
|
56
|
+
/** @internal */
|
|
57
|
+
export declare function plainError(code: KnownErrorCode, message: string): Error;
|
|
58
|
+
/** @internal 問題の重さ */
|
|
59
|
+
export type IssueSeverity = "error" | "warning" | "info";
|
|
60
|
+
/** @internal 設定の問題 1 件 */
|
|
61
|
+
export interface Issue {
|
|
62
|
+
readonly severity: IssueSeverity;
|
|
63
|
+
readonly code: string;
|
|
64
|
+
/** 設定の中の位置(例: "dates[1].from") */
|
|
65
|
+
readonly path: string;
|
|
66
|
+
readonly message: string;
|
|
67
|
+
}
|
|
68
|
+
/** @internal 問題を集める */
|
|
69
|
+
export declare class IssueCollector {
|
|
70
|
+
readonly issues: Issue[];
|
|
71
|
+
error(code: KnownErrorCode, path: string, message: string): void;
|
|
72
|
+
warning(code: string, path: string, message: string): void;
|
|
73
|
+
get hasErrors(): boolean;
|
|
74
|
+
/** 最初の誤りを例外にして投げる(誤りがなければ何もしない) */
|
|
75
|
+
throwFirstError(): void;
|
|
76
|
+
}
|
|
77
|
+
/** @internal 設定のオブジェクトに、知らないキーがあれば警告として記録する */
|
|
78
|
+
export declare function checkUnknownKeys(collector: IssueCollector, path: string, value: object, known: readonly string[]): void;
|