bizdays-toolkit 0.1.2 → 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.
@@ -0,0 +1,426 @@
1
+ /**
2
+ * 日ごとに値を返すスケジュール(営業時間・受付枠など、曜日・祝日・特定日で変わるもの)。
3
+ *
4
+ * 1 日の値は次の優先順位で決まる(上が優先):
5
+ * 1. dates … 個別の日付・期間(配列で先に書いたものが優先)
6
+ * 2. annual … 毎年の月日・月日の期間(年をまたぐ期間も可。配列で先に書いたものが優先)
7
+ * 3. 祝日 … holiday の規則("weekday": 曜日どおり / { sameAs: 曜日 }: その曜日の値 / { value }: 指定の値)
8
+ * 4. weekly … 曜日ごとの値
9
+ *
10
+ * 営業日カレンダー(createCalendar)は、このスケジュールの真偽値版として作られている。
11
+ */
12
+ import { isValidDate, parseDate, toDayNumber, weekdayOf, } from "./date.js";
13
+ import { checkUnknownKeys, IssueCollector, rangeError } from "./errors.js";
14
+ import { instantToCivil, toCivil, toDateTimeParts, validateTimeZone, } from "./input.js";
15
+ // ------------------------------------------------------------
16
+ // 入力チェック(誤りを集める。作成時は最初の誤りを例外にする)
17
+ // ------------------------------------------------------------
18
+ const MONTH_DAY_RE = /^(\d{2})-(\d{2})$/;
19
+ const MAX_DAYS_IN_MONTH = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
20
+ const isObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
21
+ /** @internal "MM-DD" として正しいか(2 月 29 日は可) */
22
+ export function isValidMonthDay(value) {
23
+ if (typeof value !== "string")
24
+ return false;
25
+ const m = MONTH_DAY_RE.exec(value);
26
+ if (!m)
27
+ return false;
28
+ const month = Number(m[1]);
29
+ const day = Number(m[2]);
30
+ return month >= 1 && month <= 12 && day >= 1 && day <= MAX_DAYS_IN_MONTH[month - 1];
31
+ }
32
+ /** @internal 曜日の番号として正しいか */
33
+ export const isWeekday = (value) => Number.isInteger(value) && value >= 0 && value <= 6;
34
+ function checkName(c, path, name) {
35
+ if (name === undefined)
36
+ return undefined;
37
+ if (typeof name !== "string") {
38
+ c.error("invalid-option", `${path}.name`, "name must be a string");
39
+ return undefined;
40
+ }
41
+ return name;
42
+ }
43
+ function checkDate(c, path, value) {
44
+ if (!isValidDate(value)) {
45
+ c.error("invalid-date", path, `Invalid date ${JSON.stringify(value)} (expected YYYY-MM-DD)`);
46
+ return undefined;
47
+ }
48
+ return toDayNumber(value);
49
+ }
50
+ function checkMonthDay(c, path, value) {
51
+ if (!isValidMonthDay(value)) {
52
+ c.error("invalid-month-day", path, `Invalid month-day ${JSON.stringify(value)} (expected MM-DD)`);
53
+ return undefined;
54
+ }
55
+ return value;
56
+ }
57
+ /**
58
+ * @internal 個別の日付の規則を検証して取り込む。
59
+ * entry は "YYYY-MM-DD"・{ date }・{ from, to } のいずれか。値は valueOf で決める
60
+ */
61
+ export function compileDateRules(c, path, entries, valueOf, allowString) {
62
+ if (entries === undefined)
63
+ return [];
64
+ if (!Array.isArray(entries)) {
65
+ c.error("invalid-option", path, "must be an array");
66
+ return [];
67
+ }
68
+ const rules = [];
69
+ entries.forEach((raw, i) => {
70
+ const p = `${path}[${i}]`;
71
+ const entry = allowString && typeof raw === "string" ? { date: raw } : raw;
72
+ if (!isObject(entry)) {
73
+ c.error("invalid-option", p, allowString ? "must be a date string or an object" : "must be an object");
74
+ return;
75
+ }
76
+ const value = valueOf(entry, p);
77
+ const name = checkName(c, p, entry["name"]);
78
+ if ("date" in entry) {
79
+ checkUnknownKeys(c, p, entry, ["date", "name", "value"]);
80
+ const n = checkDate(c, `${p}.date`, entry["date"]);
81
+ if (n !== undefined && value !== undefined)
82
+ rules.push({ from: n, to: n, value, name });
83
+ return;
84
+ }
85
+ if (!("from" in entry) || !("to" in entry)) {
86
+ c.error("invalid-option", p, 'must have "date" or both "from" and "to"');
87
+ return;
88
+ }
89
+ checkUnknownKeys(c, p, entry, ["from", "to", "name", "value"]);
90
+ const from = checkDate(c, `${p}.from`, entry["from"]);
91
+ const to = checkDate(c, `${p}.to`, entry["to"]);
92
+ if (from === undefined || to === undefined)
93
+ return;
94
+ if (from > to) {
95
+ c.error("invalid-range", p, `"from" must not be after "to" (${String(entry["from"])} > ${String(entry["to"])})`);
96
+ return;
97
+ }
98
+ if (value !== undefined)
99
+ rules.push({ from, to, value, name });
100
+ });
101
+ return rules;
102
+ }
103
+ /** @internal 毎年の月日の規則を検証して取り込む(entry は "MM-DD"・{ date }・{ from, to }) */
104
+ export function compileAnnualRules(c, path, entries, valueOf, allowString) {
105
+ if (entries === undefined)
106
+ return [];
107
+ if (!Array.isArray(entries)) {
108
+ c.error("invalid-option", path, "must be an array");
109
+ return [];
110
+ }
111
+ const rules = [];
112
+ entries.forEach((raw, i) => {
113
+ const p = `${path}[${i}]`;
114
+ const entry = allowString && typeof raw === "string" ? { date: raw } : raw;
115
+ if (!isObject(entry)) {
116
+ c.error("invalid-option", p, allowString ? "must be a month-day string or an object" : "must be an object");
117
+ return;
118
+ }
119
+ const value = valueOf(entry, p);
120
+ const name = checkName(c, p, entry["name"]);
121
+ if ("date" in entry) {
122
+ checkUnknownKeys(c, p, entry, ["date", "name", "value"]);
123
+ const md = checkMonthDay(c, `${p}.date`, entry["date"]);
124
+ if (md !== undefined && value !== undefined)
125
+ rules.push({ from: md, to: md, value, name });
126
+ return;
127
+ }
128
+ if (!("from" in entry) || !("to" in entry)) {
129
+ c.error("invalid-option", p, 'must have "date" or both "from" and "to"');
130
+ return;
131
+ }
132
+ checkUnknownKeys(c, p, entry, ["from", "to", "name", "value"]);
133
+ const from = checkMonthDay(c, `${p}.from`, entry["from"]);
134
+ const to = checkMonthDay(c, `${p}.to`, entry["to"]);
135
+ if (from !== undefined && to !== undefined && value !== undefined) {
136
+ rules.push({ from, to, value, name });
137
+ }
138
+ });
139
+ return rules;
140
+ }
141
+ /** 規則の value を取り出す(value が必須の規則用) */
142
+ function requiredValue(c) {
143
+ return (entry, path) => {
144
+ if (!("value" in entry)) {
145
+ c.error("invalid-option", path, 'must have "value"');
146
+ return undefined;
147
+ }
148
+ return entry["value"];
149
+ };
150
+ }
151
+ function validateProvider(c, path, p) {
152
+ const bad = (message) => {
153
+ c.error("invalid-provider", path, message);
154
+ return false;
155
+ };
156
+ if (!isObject(p))
157
+ return bad("must be a holiday provider object");
158
+ if (typeof p["id"] !== "string" || p["id"] === "")
159
+ return bad('"id" must be a non-empty string');
160
+ if (typeof p["timeZone"] !== "string")
161
+ return bad('"timeZone" must be a string');
162
+ try {
163
+ validateTimeZone(p["timeZone"]);
164
+ }
165
+ catch {
166
+ return bad(`"timeZone" is not a valid IANA time zone: ${JSON.stringify(p["timeZone"])}`);
167
+ }
168
+ const years = p["supportedYears"];
169
+ if (!isObject(years) ||
170
+ !Number.isInteger(years["from"]) ||
171
+ !Number.isInteger(years["to"]) ||
172
+ years["from"] < 1000 ||
173
+ years["to"] > 9999 ||
174
+ years["from"] > years["to"]) {
175
+ return bad('"supportedYears" must be { from, to } with integer years in 1000-9999 and from <= to');
176
+ }
177
+ if (typeof p["dataVersion"] !== "string")
178
+ return bad('"dataVersion" must be a string');
179
+ if (p["confirmedThrough"] !== undefined && !Number.isInteger(p["confirmedThrough"])) {
180
+ return bad('"confirmedThrough" must be an integer year');
181
+ }
182
+ if (typeof p["holidaysInYear"] !== "function")
183
+ return bad('"holidaysInYear" must be a function');
184
+ return true;
185
+ }
186
+ /** @internal 祝日の提供元とタイムゾーンの設定を検証する */
187
+ export function compileHolidaySource(c, options) {
188
+ const raw = options["holidays"];
189
+ const list = raw === undefined ? [] : Array.isArray(raw) ? raw : [raw];
190
+ const providers = [];
191
+ list.forEach((p, i) => {
192
+ const path = Array.isArray(raw) ? `holidays[${i}]` : "holidays";
193
+ if (validateProvider(c, path, p))
194
+ providers.push(p);
195
+ });
196
+ let timeZone = "UTC";
197
+ if (options["timeZone"] !== undefined) {
198
+ try {
199
+ timeZone = validateTimeZone(options["timeZone"]);
200
+ }
201
+ catch {
202
+ c.error("invalid-time-zone", "timeZone", `Invalid time zone: ${JSON.stringify(options["timeZone"])}`);
203
+ }
204
+ }
205
+ else {
206
+ const zones = [...new Set(providers.map((p) => validateTimeZone(p.timeZone)))];
207
+ if (zones.length > 1) {
208
+ c.error("time-zone-conflict", "timeZone", `Holiday providers use different time zones (${zones.join(", ")}); specify timeZone`);
209
+ }
210
+ timeZone = zones[0] ?? "UTC";
211
+ }
212
+ const outOfRange = options["outOfRange"] ?? "throw";
213
+ if (outOfRange !== "throw" && outOfRange !== "ignore") {
214
+ c.error("invalid-option", "outOfRange", 'must be "throw" or "ignore"');
215
+ }
216
+ return {
217
+ providers: Object.freeze(providers),
218
+ timeZone,
219
+ outOfRange: outOfRange === "ignore" ? "ignore" : "throw",
220
+ };
221
+ }
222
+ // ------------------------------------------------------------
223
+ // 祝日を調べる機能
224
+ // ------------------------------------------------------------
225
+ function providerDataError(provider, year, message) {
226
+ return rangeError("invalid-provider-data", `Holiday provider ${provider.id} returned invalid data for ${year}: ${message}`);
227
+ }
228
+ /** 提供元が返したデータを検証し、契約の項目だけを持つ変更できないオブジェクトにする */
229
+ function loadProviderYear(provider, year) {
230
+ const list = provider.holidaysInYear(year);
231
+ if (!Array.isArray(list))
232
+ throw providerDataError(provider, year, "holidaysInYear must return an array");
233
+ const seen = new Set();
234
+ return list.map((h, i) => {
235
+ if (!isObject(h))
236
+ throw providerDataError(provider, year, `item ${i} is not an object`);
237
+ const { date, name, nameEn, kind, confirmed } = h;
238
+ if (!isValidDate(date) || parseDate(date).year !== year) {
239
+ throw providerDataError(provider, year, `item ${i} has an invalid date ${JSON.stringify(date)}`);
240
+ }
241
+ if (seen.has(date))
242
+ throw providerDataError(provider, year, `duplicate date ${date}`);
243
+ seen.add(date);
244
+ if (typeof name !== "string" || typeof nameEn !== "string" || typeof kind !== "string") {
245
+ throw providerDataError(provider, year, `item ${i} (${date}) must have string name, nameEn and kind`);
246
+ }
247
+ if (typeof confirmed !== "boolean") {
248
+ throw providerDataError(provider, year, `item ${i} (${date}) must have boolean confirmed`);
249
+ }
250
+ return Object.freeze({ date, name, nameEn, kind: kind, confirmed });
251
+ });
252
+ }
253
+ /** @internal 祝日を調べる機能を作る(設定は検証済みであること) */
254
+ export function createHolidayLookup(source) {
255
+ const { providers, timeZone, outOfRange } = source;
256
+ const yearCache = new Map();
257
+ function holidayMap(year) {
258
+ let map = yearCache.get(year);
259
+ if (!map) {
260
+ map = new Map();
261
+ for (const provider of providers) {
262
+ const { from, to } = provider.supportedYears;
263
+ if (year < from || year > to) {
264
+ if (outOfRange === "ignore")
265
+ continue;
266
+ throw rangeError("out-of-range", `Holidays for ${provider.id} are supported for ${from}-${to}; requested ${year}`);
267
+ }
268
+ for (const h of loadProviderYear(provider, year)) {
269
+ // 同じ日に複数の提供元の祝日がある場合は、先に渡した提供元を優先する
270
+ if (!map.has(h.date))
271
+ map.set(h.date, h);
272
+ }
273
+ }
274
+ yearCache.set(year, map);
275
+ }
276
+ return map;
277
+ }
278
+ function holidayOn(date) {
279
+ if (providers.length === 0)
280
+ return undefined;
281
+ return holidayMap(parseDate(date).year).get(date);
282
+ }
283
+ const dateOf = (input) => toCivil(input, timeZone).date;
284
+ function holidaysInYear(year) {
285
+ if (!Number.isInteger(year))
286
+ throw rangeError("invalid-argument", `Invalid year: ${year}`);
287
+ return Object.freeze([...holidayMap(year).values()].sort((a, b) => a.date < b.date ? -1 : a.date > b.date ? 1 : 0));
288
+ }
289
+ return {
290
+ timeZone,
291
+ providers,
292
+ holidayOn,
293
+ dateOf,
294
+ civilOf: (input) => toDateTimeParts(input, timeZone),
295
+ today: (now = new Date()) => instantToCivil(now.getTime(), timeZone).date,
296
+ isHoliday: (input) => holidayOn(dateOf(input)) !== undefined,
297
+ getHoliday: (input) => holidayOn(dateOf(input)),
298
+ holidaysInYear,
299
+ holidaysBetween(from, to) {
300
+ const a = dateOf(from);
301
+ const b = dateOf(to);
302
+ if (a > b)
303
+ throw rangeError("invalid-range", `"from" must not be after "to" (${a} > ${b})`);
304
+ const result = [];
305
+ for (let y = parseDate(a).year; y <= parseDate(b).year; y++) {
306
+ for (const h of holidaysInYear(y))
307
+ if (h.date >= a && h.date <= b)
308
+ result.push(h);
309
+ }
310
+ return Object.freeze(result);
311
+ },
312
+ };
313
+ }
314
+ const SCHEDULE_OPTION_KEYS = [
315
+ "holidays",
316
+ "timeZone",
317
+ "outOfRange",
318
+ "weekly",
319
+ "holiday",
320
+ "annual",
321
+ "dates",
322
+ ];
323
+ /** @internal スケジュールの設定を検証して取り込む(誤りは c に集める) */
324
+ export function compileScheduleOptions(c, options) {
325
+ if (!isObject(options)) {
326
+ c.error("invalid-option", "", "options must be an object");
327
+ return undefined;
328
+ }
329
+ checkUnknownKeys(c, "", options, SCHEDULE_OPTION_KEYS);
330
+ const source = compileHolidaySource(c, options);
331
+ const weeklyRaw = options["weekly"];
332
+ let weekly = [];
333
+ if (!Array.isArray(weeklyRaw) || weeklyRaw.length !== 7) {
334
+ c.error("invalid-option", "weekly", "must have 7 values ([Sunday, Monday, ..., Saturday])");
335
+ }
336
+ else {
337
+ weekly = Object.freeze([...weeklyRaw]);
338
+ }
339
+ let holiday = "weekday";
340
+ const holidayRaw = options["holiday"];
341
+ if (holidayRaw === undefined || holidayRaw === "weekday") {
342
+ holiday = "weekday";
343
+ }
344
+ else if (isObject(holidayRaw) && "sameAs" in holidayRaw) {
345
+ if (isWeekday(holidayRaw["sameAs"]))
346
+ holiday = Object.freeze({ sameAs: holidayRaw["sameAs"] });
347
+ else
348
+ c.error("invalid-weekday", "holiday.sameAs", "must be an integer from 0 (Sunday) to 6 (Saturday)");
349
+ }
350
+ else if (isObject(holidayRaw) && "value" in holidayRaw) {
351
+ holiday = Object.freeze({ value: holidayRaw["value"] });
352
+ }
353
+ else {
354
+ c.error("invalid-option", "holiday", 'must be "weekday", { sameAs: weekday } or { value }');
355
+ }
356
+ const annual = compileAnnualRules(c, "annual", options["annual"], requiredValue(c), false);
357
+ const dates = compileDateRules(c, "dates", options["dates"], requiredValue(c), false);
358
+ return { source, weekly, holiday, annual, dates };
359
+ }
360
+ function matchAnnual(rule, monthDay) {
361
+ return rule.from <= rule.to
362
+ ? monthDay >= rule.from && monthDay <= rule.to
363
+ : monthDay >= rule.from || monthDay <= rule.to;
364
+ }
365
+ /** @internal 検証済みの規則からスケジュールを作る */
366
+ export function buildSchedule(compiled) {
367
+ const { weekly, holiday: holidayRule, annual, dates } = compiled;
368
+ const lookup = createHolidayLookup(compiled.source);
369
+ function info(date) {
370
+ const dayNumber = toDayNumber(date);
371
+ const weekday = weekdayOf(date);
372
+ const holiday = lookup.holidayOn(date);
373
+ const make = (value, reason, name) => {
374
+ const result = {
375
+ date,
376
+ weekday,
377
+ value,
378
+ reason,
379
+ };
380
+ if (name !== undefined)
381
+ result.name = name;
382
+ if (holiday !== undefined)
383
+ result.holiday = holiday;
384
+ return Object.freeze(result);
385
+ };
386
+ const byDate = dates.find((r) => dayNumber >= r.from && dayNumber <= r.to);
387
+ if (byDate)
388
+ return make(byDate.value, "date", byDate.name);
389
+ const monthDay = date.slice(5);
390
+ const byAnnual = annual.find((r) => matchAnnual(r, monthDay));
391
+ if (byAnnual)
392
+ return make(byAnnual.value, "annual", byAnnual.name);
393
+ if (holiday && holidayRule !== "weekday") {
394
+ const value = "sameAs" in holidayRule ? weekly[holidayRule.sameAs] : holidayRule.value;
395
+ return make(value, "holiday", holiday.name);
396
+ }
397
+ return make(weekly[weekday], "weekday", undefined);
398
+ }
399
+ const schedule = {
400
+ timeZone: lookup.timeZone,
401
+ providers: lookup.providers,
402
+ dateOf: lookup.dateOf,
403
+ civilOf: lookup.civilOf,
404
+ today: lookup.today,
405
+ isHoliday: lookup.isHoliday,
406
+ getHoliday: lookup.getHoliday,
407
+ holidaysInYear: lookup.holidaysInYear,
408
+ holidaysBetween: lookup.holidaysBetween,
409
+ valueOn: (input) => info(lookup.dateOf(input)).value,
410
+ dayInfo: (input) => info(lookup.dateOf(input)),
411
+ };
412
+ return Object.freeze(schedule);
413
+ }
414
+ /**
415
+ * 曜日・祝日・特定日の規則から、日ごとに値を返すスケジュールを作る。
416
+ *
417
+ * 設定は作る時点で複製されるので、あとから元の配列やオブジェクトを書き換えても結果は変わらない
418
+ * (値 T そのものは複製しない)。設定の誤りは code 付きの RangeError になる。
419
+ * @public
420
+ */
421
+ export function createSchedule(options) {
422
+ const c = new IssueCollector();
423
+ const compiled = compileScheduleOptions(c, options);
424
+ c.throwFirstError();
425
+ return buildSchedule(compiled);
426
+ }
@@ -1,5 +1,8 @@
1
1
  import type { DateString } from "./date.ts";
2
- /** 祝日・休日 1 日分 */
2
+ /**
3
+ * 祝日・休日 1 日分。
4
+ * @public
5
+ */
3
6
  export interface Holiday<Kind extends string = string> {
4
7
  /** 暦日 "YYYY-MM-DD" */
5
8
  readonly date: DateString;
@@ -18,7 +21,12 @@ export interface Holiday<Kind extends string = string> {
18
21
  /**
19
22
  * 祝日の提供元(国・地域ごとのルール)。
20
23
  *
21
- * 他の国・地域の祝日はこのインターフェースを実装して createCalendar に渡す。
24
+ * 他の国・地域の祝日はこのインターフェースを実装して createCalendar・createSchedule に渡す。
25
+ * holidaysInYear が返すデータは、年ごとに初めて使うときに検証され、誤りがあれば
26
+ * code: "invalid-provider-data" の例外になる。
27
+ *
28
+ * 互換性の約束: このインターフェースに項目を足すときは、メジャー版以外では省略可能な項目に限る。
29
+ * @public
22
30
  */
23
31
  export interface HolidayProvider<Kind extends string = string> {
24
32
  /** ISO 3166-1 alpha-2(国)または ISO 3166-2(地域)のコード。例: "JP", "US-CA" */
@@ -34,6 +42,6 @@ export interface HolidayProvider<Kind extends string = string> {
34
42
  readonly dataVersion: string;
35
43
  /** この年までは公式に確定している(それより後は confirmed: false を含みうる) */
36
44
  readonly confirmedThrough?: number;
37
- /** その年の祝日・休日を日付順に返す */
45
+ /** その年の祝日・休日(同じ日付は 1 件まで。順序は問わない) */
38
46
  holidaysInYear(year: number): readonly Holiday<Kind>[];
39
47
  }
package/dist/index.d.ts CHANGED
@@ -1,9 +1,15 @@
1
1
  /**
2
- * 祝日と、会社・店舗の休業日を組み合わせた営業日カレンダー。
2
+ * 祝日と、会社・店舗の休業日を組み合わせた営業日カレンダー(createCalendar)と、
3
+ * 曜日・祝日・特定日で変わる値(営業時間など)のスケジュール(createSchedule)。
3
4
  *
4
5
  * 国ごとの祝日はサブパスから読み込む(例: "bizdays-toolkit/jp")。
6
+ *
7
+ * 公開する API には TSDoc の @public(安定)または @beta(マイナー版で変わりうる)を付ける。
8
+ * 公開する名前と型は etc/api.md に記録し、pnpm check で意図しない変化を検出する。
5
9
  */
6
- export { createCalendar, type AnnualDate, type AnnualRange, type BusinessDayOptions, type Calendar, type CalendarOptions, type DateRange, type DayInfo, type DayReason, type HolidayPolicy, type SpecificDate, } from "./core/calendar.ts";
7
- export { addDays, diffDays, isValidDate, nthWeekdayOfMonth, weekdayOf, type DateString, type Weekday, } from "./core/date.ts";
8
- export { toDateString, type DateInput, type TemporalDateLike, type TemporalInstantLike, } from "./core/input.ts";
10
+ export { createCalendar, type AnnualDate, type AnnualRange, type BusinessDayOptions, type Calendar, type CalendarOptions, type DateRange, type DayInfo, type DayReason, type HolidayPolicy, type KnownDayReason, type SpecificDate, } from "./core/calendar.ts";
11
+ export { createSchedule, type AnnualRangeValue, type AnnualValue, type CivilDateTimeParts, type DateRangeValue, type DateValue, type HolidayLookup, type HolidayRule, type HolidaySourceOptions, type KnownScheduleReason, type Schedule, type ScheduleDay, type ScheduleOptions, type ScheduleReason, } from "./core/schedule.ts";
12
+ export { addDays, diffDays, isValidDate, weekdayOf, type DateString, type Weekday, } from "./core/date.ts";
13
+ export { isBizdaysError, type ErrorCode, type KnownErrorCode } from "./core/errors.ts";
14
+ export { toDateString, toDateTimeParts, type DateInput, type TemporalDateLike, type TemporalInstantLike, } from "./core/input.ts";
9
15
  export type { Holiday, HolidayProvider } from "./core/types.ts";
package/dist/index.js CHANGED
@@ -1,8 +1,14 @@
1
1
  /**
2
- * 祝日と、会社・店舗の休業日を組み合わせた営業日カレンダー。
2
+ * 祝日と、会社・店舗の休業日を組み合わせた営業日カレンダー(createCalendar)と、
3
+ * 曜日・祝日・特定日で変わる値(営業時間など)のスケジュール(createSchedule)。
3
4
  *
4
5
  * 国ごとの祝日はサブパスから読み込む(例: "bizdays-toolkit/jp")。
6
+ *
7
+ * 公開する API には TSDoc の @public(安定)または @beta(マイナー版で変わりうる)を付ける。
8
+ * 公開する名前と型は etc/api.md に記録し、pnpm check で意図しない変化を検出する。
5
9
  */
6
10
  export { createCalendar, } from "./core/calendar.js";
7
- export { addDays, diffDays, isValidDate, nthWeekdayOfMonth, weekdayOf, } from "./core/date.js";
8
- export { toDateString, } from "./core/input.js";
11
+ export { createSchedule, } from "./core/schedule.js";
12
+ export { addDays, diffDays, isValidDate, weekdayOf, } from "./core/date.js";
13
+ export { isBizdaysError } from "./core/errors.js";
14
+ export { toDateString, toDateTimeParts, } from "./core/input.js";
@@ -14,23 +14,57 @@ import type { DateString } from "../core/date.ts";
14
14
  import type { DateInput } from "../core/input.ts";
15
15
  import type { Holiday, HolidayProvider } from "../core/types.ts";
16
16
  import { type JapaneseHolidayKind } from "./rules.ts";
17
- export type { JapaneseHolidayKind } from "./rules.ts";
18
- export { autumnalEquinoxDay, vernalEquinoxDay } from "./rules.ts";
19
- /** 日本の祝日 */
17
+ export type { JapaneseHolidayKind, KnownJapaneseHolidayKind } from "./rules.ts";
18
+ /**
19
+ * 日本の祝日。
20
+ * @public
21
+ */
20
22
  export type JapaneseHoliday = Holiday<JapaneseHolidayKind>;
21
- /** 日本の祝日の提供元(createCalendar の holidays に渡す) */
23
+ /**
24
+ * 日本の祝日の提供元(createCalendar・createSchedule の holidays に渡す)。
25
+ * @public
26
+ */
22
27
  export declare const jp: HolidayProvider<JapaneseHolidayKind>;
23
- /** 祝日か(Date などの瞬間は日本時間で判定する) */
28
+ /**
29
+ * 祝日か(Date などの瞬間は日本時間で判定する)。
30
+ * @public
31
+ */
24
32
  export declare function isHoliday(input: DateInput): boolean;
25
- /** 祝日なら祝日を返す */
33
+ /**
34
+ * 祝日なら祝日を返す。
35
+ * @public
36
+ */
26
37
  export declare function getHoliday(input: DateInput): JapaneseHoliday | undefined;
27
- /** その年の祝日 */
38
+ /**
39
+ * その年の祝日。
40
+ * @public
41
+ */
28
42
  export declare function holidaysInYear(year: number): readonly JapaneseHoliday[];
29
- /** 期間内の祝日(両端を含む) */
43
+ /**
44
+ * 期間内の祝日(両端を含む)。
45
+ * @public
46
+ */
30
47
  export declare function holidaysBetween(from: DateInput, to: DateInput): readonly JapaneseHoliday[];
31
- /** 日本時間での暦日 */
48
+ /**
49
+ * 日本時間での暦日。
50
+ * @public
51
+ */
32
52
  export declare function toJapanDate(input: DateInput): DateString;
33
- /** 反映済みの官報の年月 */
53
+ /**
54
+ * 日本時間での暦日と時刻("HH:mm"。暦日だけの入力では null)。
55
+ * @public
56
+ */
57
+ export declare function toJapanDateTime(input: DateInput): {
58
+ readonly date: DateString;
59
+ readonly time: string | null;
60
+ };
61
+ /**
62
+ * 反映済みの官報の年月。
63
+ * @public
64
+ */
34
65
  export declare const dataVersion: string;
35
- /** 春分・秋分の日が官報で確定している最後の年 */
66
+ /**
67
+ * 春分・秋分の日が官報で確定している最後の年。
68
+ * @public
69
+ */
36
70
  export declare const confirmedThrough: number;
package/dist/jp/index.js CHANGED
@@ -12,9 +12,11 @@
12
12
  */
13
13
  import { createCalendar } from "../core/calendar.js";
14
14
  import { CONFIRMED_THROUGH, DATA_VERSION, SUPPORTED_YEARS, computeJapaneseHolidays, } from "./rules.js";
15
- export { autumnalEquinoxDay, vernalEquinoxDay } from "./rules.js";
16
15
  const cache = new Map();
17
- /** 日本の祝日の提供元(createCalendar の holidays に渡す) */
16
+ /**
17
+ * 日本の祝日の提供元(createCalendar・createSchedule の holidays に渡す)。
18
+ * @public
19
+ */
18
20
  export const jp = Object.freeze({
19
21
  id: "JP",
20
22
  timeZone: "Asia/Tokyo",
@@ -32,27 +34,55 @@ export const jp = Object.freeze({
32
34
  });
33
35
  let defaultCalendar;
34
36
  const calendar = () => (defaultCalendar ??= createCalendar({ holidays: jp }));
35
- /** 祝日か(Date などの瞬間は日本時間で判定する) */
37
+ /**
38
+ * 祝日か(Date などの瞬間は日本時間で判定する)。
39
+ * @public
40
+ */
36
41
  export function isHoliday(input) {
37
42
  return calendar().isHoliday(input);
38
43
  }
39
- /** 祝日なら祝日を返す */
44
+ /**
45
+ * 祝日なら祝日を返す。
46
+ * @public
47
+ */
40
48
  export function getHoliday(input) {
41
49
  return calendar().getHoliday(input);
42
50
  }
43
- /** その年の祝日 */
51
+ /**
52
+ * その年の祝日。
53
+ * @public
54
+ */
44
55
  export function holidaysInYear(year) {
45
56
  return calendar().holidaysInYear(year);
46
57
  }
47
- /** 期間内の祝日(両端を含む) */
58
+ /**
59
+ * 期間内の祝日(両端を含む)。
60
+ * @public
61
+ */
48
62
  export function holidaysBetween(from, to) {
49
63
  return calendar().holidaysBetween(from, to);
50
64
  }
51
- /** 日本時間での暦日 */
65
+ /**
66
+ * 日本時間での暦日。
67
+ * @public
68
+ */
52
69
  export function toJapanDate(input) {
53
70
  return calendar().dateOf(input);
54
71
  }
55
- /** 反映済みの官報の年月 */
72
+ /**
73
+ * 日本時間での暦日と時刻("HH:mm"。暦日だけの入力では null)。
74
+ * @public
75
+ */
76
+ export function toJapanDateTime(input) {
77
+ return calendar().civilOf(input);
78
+ }
79
+ /**
80
+ * 反映済みの官報の年月。
81
+ * @public
82
+ */
56
83
  export const dataVersion = DATA_VERSION;
57
- /** 春分・秋分の日が官報で確定している最後の年 */
84
+ /**
85
+ * 春分・秋分の日が官報で確定している最後の年。
86
+ * @public
87
+ */
58
88
  export const confirmedThrough = CONFIRMED_THROUGH;