@typescript-calendar-lib/core 0.1.1 → 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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 nazozokc
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,233 @@
1
+ # @typescript-calendar-lib/core
2
+
3
+ Shared, framework-agnostic calendar utilities: date math, locale data, and grid building. Zero runtime dependencies.
4
+
5
+ > **Tip:** You normally don't need `core` directly — the `cli`, `react`, and `tui` packages re-export the types and utilities you'll need. Reach for `core` when building your own renderer.
6
+
7
+ ## Installation
8
+
9
+ ```sh
10
+ npm install @typescript-calendar-lib/core
11
+ # or
12
+ bun add @typescript-calendar-lib/core
13
+ ```
14
+
15
+ ## Types
16
+
17
+ ### `Locale`
18
+
19
+ ```ts
20
+ type Locale = "en" | "ja" | "es" | "de" | "fr" | "ko" | "zh";
21
+ ```
22
+
23
+ English, Japanese, Spanish, German, French, Korean, and Chinese (Simplified).
24
+
25
+ ### `WeekStart`
26
+
27
+ ```ts
28
+ type WeekStart = "sunday" | "monday";
29
+ ```
30
+
31
+ ### `HighlightStyle`
32
+
33
+ ```ts
34
+ type HighlightStyle = "bracket" | "reverse";
35
+ ```
36
+
37
+ `"bracket"` wraps the day in brackets (`[8]`), `"reverse"` uses reverse-video styling.
38
+
39
+ ### `CalendarOptions`
40
+
41
+ ```ts
42
+ interface CalendarOptions {
43
+ year: number;
44
+ month: number; // 1-indexed (1–12)
45
+ locale?: Locale; // default: "en"
46
+ weekStart?: WeekStart; // default: "sunday"
47
+ highlight?: Date; // date to highlight (e.g. today)
48
+ highlightStyle?: HighlightStyle; // default: "bracket"
49
+ range?: { from: Date; to: Date };
50
+ color?: boolean; // default: false
51
+ }
52
+ ```
53
+
54
+ ### `CalendarYearOptions`
55
+
56
+ Same as `CalendarOptions` minus `month` — renders a full year:
57
+
58
+ ```ts
59
+ interface CalendarYearOptions {
60
+ year: number;
61
+ locale?: Locale;
62
+ weekStart?: WeekStart;
63
+ highlight?: Date;
64
+ highlightStyle?: HighlightStyle;
65
+ range?: { from: Date; to: Date };
66
+ color?: boolean;
67
+ }
68
+ ```
69
+
70
+ ### `CalendarRangeOptions`
71
+
72
+ Same as `CalendarOptions` minus `year`/`month`, rendered for every month between `from` and `to` (inclusive):
73
+
74
+ ```ts
75
+ interface CalendarRangeOptions {
76
+ from: Date;
77
+ to: Date;
78
+ locale?: Locale;
79
+ weekStart?: WeekStart;
80
+ highlight?: Date;
81
+ highlightStyle?: HighlightStyle;
82
+ range?: { from: Date; to: Date };
83
+ color?: boolean;
84
+ }
85
+ ```
86
+
87
+ ### `RenderMonthOptions`
88
+
89
+ Same as `CalendarOptions` minus `year`/`month`. Used by the low-level `renderMonth` helper.
90
+
91
+ ## Constants
92
+
93
+ ### `LOCALES`
94
+
95
+ ```ts
96
+ const LOCALES: Record<Locale, LocaleData>;
97
+ ```
98
+
99
+ Contains month names, weekday headers, and short weekday headers for each locale.
100
+
101
+ ### `MIN_YEAR` / `MAX_YEAR`
102
+
103
+ The supported year range (`1` / `9999`). Values outside this range are rejected by validation.
104
+
105
+ ## Functions
106
+
107
+ ### `getMonthName(locale, month): string`
108
+
109
+ Returns the localized month name for a 1-indexed month:
110
+
111
+ ```ts
112
+ getMonthName("en", 9); // "September"
113
+ getMonthName("ja", 9); // "9月"
114
+ ```
115
+
116
+ ### `getWeekdayHeaders(locale, weekStart): readonly string[]`
117
+
118
+ Returns the weekday header array respecting `weekStart`:
119
+
120
+ ```ts
121
+ getWeekdayHeaders("en", "sunday"); // ["Sun", "Mon", ..., "Sat"]
122
+ getWeekdayHeaders("en", "monday"); // ["Mon", "Tue", ..., "Sun"]
123
+ ```
124
+
125
+ ### `buildMonthGrid(year, month, weekStart): (number | null)[][]`
126
+
127
+ Builds a 6×7 grid of day numbers (`1`–`31`) with `null` for empty cells. The grid is always 6 rows tall for stable layouts:
128
+
129
+ ```ts
130
+ buildMonthGrid(2026, 9, "sunday");
131
+ // [
132
+ // [null, null, 1, 2, 3, 4, 5],
133
+ // [6, 7, 8, 9, 10, 11, 12],
134
+ // ...
135
+ // ]
136
+ ```
137
+
138
+ ### `firstDayOfMonth(year, month): Date`
139
+
140
+ Returns a `Date` for the 1st of the month:
141
+
142
+ ```ts
143
+ firstDayOfMonth(2026, 9); // Tue Sep 01 2026
144
+ ```
145
+
146
+ ### `lastDayOfMonth(year, month): Date`
147
+
148
+ Returns a `Date` for the last day of the month:
149
+
150
+ ```ts
151
+ lastDayOfMonth(2026, 2); // Sat Feb 28 2026 (no leap year)
152
+ lastDayOfMonth(2028, 2); // Tue Feb 29 2028 (leap year)
153
+ ```
154
+
155
+ ### `isDateInRange(date, range?): boolean`
156
+
157
+ Returns `true` if `date` falls within `range` (inclusive). Returns `false` when `range` is `undefined`.
158
+
159
+ Throws `RangeError` when `range.from` is after `range.to`, or when any date is invalid.
160
+
161
+ ### `isSameDay(a, b): boolean`
162
+
163
+ Compares dates by year, month, and day only (ignores time component). Throws `RangeError` when either date is invalid.
164
+
165
+ ### `getMonthRange(from, to): { year: number; month: number }[]`
166
+
167
+ Returns the list of all months from `from` to `to` (inclusive):
168
+
169
+ ```ts
170
+ getMonthRange(new Date(2026, 5, 1), new Date(2026, 8, 30));
171
+ // [{ year: 2026, month: 6 }, { year: 2026, month: 7 },
172
+ // { year: 2026, month: 8 }, { year: 2026, month: 9 }]
173
+ ```
174
+
175
+ Throws `RangeError` when `from` is after `to`, or when either date is invalid.
176
+
177
+ ### `createDate(year, monthIndex, day): Date`
178
+
179
+ Creates a local `Date` at `00:00:00` without the `new Date(year, ...)` 1900-interpretation for years `0`–`99`. `monthIndex` is 0-based like `Date`.
180
+
181
+ ### `assertValidDate(date): void`
182
+
183
+ Throws `RangeError` when `date` is not a `Date` instance or is an `Invalid Date`.
184
+
185
+ ## Input validation
186
+
187
+ All functions validate their inputs and throw `RangeError` on invalid values instead of silently producing wrong results:
188
+
189
+ | Input | Accepted range | Example of invalid input |
190
+ | :--- | :--- | :--- |
191
+ | `year` | integer `1`–`9999` | `0`, `-1`, `10000`, `2026.5`, `NaN` |
192
+ | `month` | integer `1`–`12` | `0`, `13`, `2.5`, `NaN` |
193
+ | `weekStart` | `"sunday"` \| `"monday"` | `"tuesday"` |
194
+ | `locale` | `"en"` \| `"ja"` \| `"es"` \| `"de"` \| `"fr"` \| `"ko"` \| `"zh"` | `"xx"` |
195
+ | `range` | `from <= to`, valid `Date`s | reversed or invalid dates |
196
+
197
+ > **Note:** `new Date(year, ...)` interprets years `0`–`99` as `1900 + year`. The library avoids this by using `setFullYear` internally (`createDate`), so `firstDayOfMonth(50, 9)` correctly returns the year `50` — but you still cannot pass year `0` or `10000`.
198
+
199
+ ## Exports
200
+
201
+ ```ts
202
+ // Types
203
+ import type {
204
+ CalendarOptions,
205
+ CalendarRangeOptions,
206
+ CalendarYearOptions,
207
+ HighlightStyle,
208
+ Locale,
209
+ RenderMonthOptions,
210
+ WeekStart,
211
+ } from "@typescript-calendar-lib/core";
212
+
213
+ // Values
214
+ import {
215
+ LOCALES,
216
+ MAX_YEAR,
217
+ MIN_YEAR,
218
+ assertValidDate,
219
+ buildMonthGrid,
220
+ createDate,
221
+ firstDayOfMonth,
222
+ getMonthName,
223
+ getMonthRange,
224
+ getWeekdayHeaders,
225
+ isDateInRange,
226
+ isSameDay,
227
+ lastDayOfMonth,
228
+ } from "@typescript-calendar-lib/core";
229
+ ```
230
+
231
+ ## License
232
+
233
+ MIT
@@ -0,0 +1,79 @@
1
+ //#region src/types.d.ts
2
+ type Locale = "en" | "ja" | "es" | "de" | "fr" | "ko" | "zh";
3
+ type WeekStart = "sunday" | "monday";
4
+ type HighlightStyle = "bracket" | "reverse";
5
+ interface BaseCalendarOptions {
6
+ locale?: Locale;
7
+ weekStart?: WeekStart;
8
+ highlight?: Date;
9
+ highlightStyle?: HighlightStyle;
10
+ range?: {
11
+ from: Date;
12
+ to: Date;
13
+ };
14
+ color?: boolean;
15
+ }
16
+ interface CalendarOptions extends BaseCalendarOptions {
17
+ year: number;
18
+ month: number;
19
+ }
20
+ interface CalendarYearOptions extends BaseCalendarOptions {
21
+ year: number;
22
+ }
23
+ interface CalendarRangeOptions extends BaseCalendarOptions {
24
+ from: Date;
25
+ to: Date;
26
+ }
27
+ interface RenderMonthOptions extends BaseCalendarOptions {}
28
+ //#endregion
29
+ //#region src/locale.d.ts
30
+ interface LocaleData {
31
+ months: readonly string[];
32
+ weekdays: readonly string[];
33
+ weekdaysShort: readonly string[];
34
+ weekdaysMonday: readonly string[];
35
+ weekdaysMondayShort: readonly string[];
36
+ }
37
+ export declare const LOCALES: Record<Locale, LocaleData>;
38
+ export declare function getWeekdayHeaders(locale: Locale, weekStart: WeekStart): readonly string[];
39
+ export declare function getMonthName(locale: Locale, month: number): string;
40
+ //#endregion
41
+ //#region src/utils.d.ts
42
+ /** 月初日(1日)を返す */
43
+ export declare function firstDayOfMonth(year: number, month: number): Date;
44
+ /** 月末日を返す */
45
+ export declare function lastDayOfMonth(year: number, month: number): Date;
46
+ /**
47
+ * 月の日付グリッドを生成する(6行×7列)
48
+ * 各セルは日数(1-31)または null(空欄)
49
+ */
50
+ export declare function buildMonthGrid(year: number, month: number, weekStart: WeekStart): (number | null)[][];
51
+ /** 日付が範囲内に含まれるか */
52
+ export declare function isDateInRange(date: Date, range?: {
53
+ from: Date;
54
+ to: Date;
55
+ }): boolean;
56
+ /** 日付が今日と一致するか(日付のみ比較) */
57
+ export declare function isSameDay(a: Date, b: Date): boolean;
58
+ /** 日付が範囲の開始年から終了年までの月リストを返す */
59
+ export declare function getMonthRange(from: Date, to: Date): {
60
+ year: number;
61
+ month: number;
62
+ }[];
63
+ //#endregion
64
+ //#region src/validation.d.ts
65
+ /** サポートする年の最小値(JS Date は 0-99 を 1900+年 と解釈するため 1 から) */
66
+ export declare const MIN_YEAR = 1;
67
+ /** サポートする年の最大値 */
68
+ export declare const MAX_YEAR = 9999;
69
+ /** Date が有効(Invalid Date でない)ことを検証する */
70
+ export declare function assertValidDate(date: Date): void;
71
+ /**
72
+ * ローカルの指定日 00:00:00 を生成する。
73
+ * `new Date(year, ...)` は year 0-99 を 1900+year と解釈するため、
74
+ * setFullYear を使って year 0-99 も正しく扱えるようにする。
75
+ */
76
+ export declare function createDate(year: number, monthIndex: number, day: number): Date;
77
+ //#endregion
78
+ export type { CalendarOptions, CalendarRangeOptions, CalendarYearOptions, HighlightStyle, Locale, RenderMonthOptions, WeekStart };
79
+ //# sourceMappingURL=index.d.ts.map
package/dist/index.js ADDED
@@ -0,0 +1,318 @@
1
+ //#region src/validation.ts
2
+ /** サポートする年の最小値(JS Date は 0-99 を 1900+年 と解釈するため 1 から) */
3
+ const MIN_YEAR = 1;
4
+ /** サポートする年の最大値 */
5
+ const MAX_YEAR = 9999;
6
+ /** 値が min-max の整数であることを検証する(不正なら RangeError) */
7
+ function assertIntegerInRange(name, value, min, max) {
8
+ if (!Number.isInteger(value) || value < min || value > max) throw new RangeError(`Invalid ${name}: ${value} (expected an integer between ${min} and ${max})`);
9
+ }
10
+ /** 年が 1-9999 の整数であることを検証する */
11
+ function assertValidYear(year) {
12
+ assertIntegerInRange("year", year, 1, MAX_YEAR);
13
+ }
14
+ /** 月が 1-12 の整数であることを検証する */
15
+ function assertValidMonth(month) {
16
+ assertIntegerInRange("month", month, 1, 12);
17
+ }
18
+ /** 年の組み合わせ(year と month)を検証する */
19
+ function assertValidYearMonth(year, month) {
20
+ assertValidYear(year);
21
+ assertValidMonth(month);
22
+ }
23
+ /** 週の開始曜日を検証する */
24
+ function assertValidWeekStart(weekStart) {
25
+ if (weekStart !== "sunday" && weekStart !== "monday") throw new RangeError(`Invalid weekStart: "${weekStart}" (expected "sunday" or "monday")`);
26
+ }
27
+ /** Date が有効(Invalid Date でない)ことを検証する */
28
+ function assertValidDate(date) {
29
+ if (!(date instanceof Date) || Number.isNaN(date.getTime())) throw new RangeError(`Invalid Date: ${String(date)}`);
30
+ }
31
+ /**
32
+ * ローカルの指定日 00:00:00 を生成する。
33
+ * `new Date(year, ...)` は year 0-99 を 1900+year と解釈するため、
34
+ * setFullYear を使って year 0-99 も正しく扱えるようにする。
35
+ */
36
+ function createDate(year, monthIndex, day) {
37
+ const date = /* @__PURE__ */ new Date(0);
38
+ date.setHours(0, 0, 0, 0);
39
+ date.setFullYear(year, monthIndex, day);
40
+ return date;
41
+ }
42
+ //#endregion
43
+ //#region src/locale.ts
44
+ /** 月曜始まりの配列は日曜始まりの配列を前ローテーションして生成する */
45
+ function makeLocale(months, weekdays, weekdaysShort = weekdays) {
46
+ const rotate = (arr) => [...arr.slice(1), arr[0]];
47
+ return {
48
+ months,
49
+ weekdays,
50
+ weekdaysShort,
51
+ weekdaysMonday: rotate(weekdays),
52
+ weekdaysMondayShort: rotate(weekdaysShort)
53
+ };
54
+ }
55
+ const LOCALES = {
56
+ en: makeLocale([
57
+ "January",
58
+ "February",
59
+ "March",
60
+ "April",
61
+ "May",
62
+ "June",
63
+ "July",
64
+ "August",
65
+ "September",
66
+ "October",
67
+ "November",
68
+ "December"
69
+ ], [
70
+ "Sun",
71
+ "Mon",
72
+ "Tue",
73
+ "Wed",
74
+ "Thu",
75
+ "Fri",
76
+ "Sat"
77
+ ], [
78
+ "Su",
79
+ "Mo",
80
+ "Tu",
81
+ "We",
82
+ "Th",
83
+ "Fr",
84
+ "Sa"
85
+ ]),
86
+ ja: makeLocale([
87
+ "1月",
88
+ "2月",
89
+ "3月",
90
+ "4月",
91
+ "5月",
92
+ "6月",
93
+ "7月",
94
+ "8月",
95
+ "9月",
96
+ "10月",
97
+ "11月",
98
+ "12月"
99
+ ], [
100
+ "日",
101
+ "月",
102
+ "火",
103
+ "水",
104
+ "木",
105
+ "金",
106
+ "土"
107
+ ]),
108
+ es: makeLocale([
109
+ "enero",
110
+ "febrero",
111
+ "marzo",
112
+ "abril",
113
+ "mayo",
114
+ "junio",
115
+ "julio",
116
+ "agosto",
117
+ "septiembre",
118
+ "octubre",
119
+ "noviembre",
120
+ "diciembre"
121
+ ], [
122
+ "dom",
123
+ "lun",
124
+ "mar",
125
+ "mié",
126
+ "jue",
127
+ "vie",
128
+ "sáb"
129
+ ], [
130
+ "do",
131
+ "lu",
132
+ "ma",
133
+ "mi",
134
+ "ju",
135
+ "vi",
136
+ "sá"
137
+ ]),
138
+ de: makeLocale([
139
+ "Januar",
140
+ "Februar",
141
+ "März",
142
+ "April",
143
+ "Mai",
144
+ "Juni",
145
+ "Juli",
146
+ "August",
147
+ "September",
148
+ "Oktober",
149
+ "November",
150
+ "Dezember"
151
+ ], [
152
+ "So",
153
+ "Mo",
154
+ "Di",
155
+ "Mi",
156
+ "Do",
157
+ "Fr",
158
+ "Sa"
159
+ ]),
160
+ fr: makeLocale([
161
+ "janvier",
162
+ "février",
163
+ "mars",
164
+ "avril",
165
+ "mai",
166
+ "juin",
167
+ "juillet",
168
+ "août",
169
+ "septembre",
170
+ "octobre",
171
+ "novembre",
172
+ "décembre"
173
+ ], [
174
+ "dim.",
175
+ "lun.",
176
+ "mar.",
177
+ "mer.",
178
+ "jeu.",
179
+ "ven.",
180
+ "sam."
181
+ ], [
182
+ "di",
183
+ "lu",
184
+ "ma",
185
+ "me",
186
+ "je",
187
+ "ve",
188
+ "sa"
189
+ ]),
190
+ ko: makeLocale([
191
+ "1월",
192
+ "2월",
193
+ "3월",
194
+ "4월",
195
+ "5월",
196
+ "6월",
197
+ "7월",
198
+ "8월",
199
+ "9월",
200
+ "10월",
201
+ "11월",
202
+ "12월"
203
+ ], [
204
+ "일",
205
+ "월",
206
+ "화",
207
+ "수",
208
+ "목",
209
+ "금",
210
+ "토"
211
+ ]),
212
+ zh: makeLocale([
213
+ "一月",
214
+ "二月",
215
+ "三月",
216
+ "四月",
217
+ "五月",
218
+ "六月",
219
+ "七月",
220
+ "八月",
221
+ "九月",
222
+ "十月",
223
+ "十一月",
224
+ "十二月"
225
+ ], [
226
+ "日",
227
+ "一",
228
+ "二",
229
+ "三",
230
+ "四",
231
+ "五",
232
+ "六"
233
+ ])
234
+ };
235
+ /** ロケールデータを取得する(未知のロケールは RangeError) */
236
+ function getLocaleData(locale) {
237
+ const data = LOCALES[locale];
238
+ if (data === void 0) throw new RangeError(`Invalid locale: "${locale}"`);
239
+ return data;
240
+ }
241
+ function getWeekdayHeaders(locale, weekStart) {
242
+ assertValidWeekStart(weekStart);
243
+ const data = getLocaleData(locale);
244
+ return weekStart === "monday" ? data.weekdaysMonday : data.weekdays;
245
+ }
246
+ function getMonthName(locale, month) {
247
+ assertValidMonth(month);
248
+ return getLocaleData(locale).months[month - 1];
249
+ }
250
+ //#endregion
251
+ //#region src/utils.ts
252
+ /** 月初日(1日)を返す */
253
+ function firstDayOfMonth(year, month) {
254
+ assertValidYearMonth(year, month);
255
+ return createDate(year, month - 1, 1);
256
+ }
257
+ /** 月末日を返す */
258
+ function lastDayOfMonth(year, month) {
259
+ assertValidYearMonth(year, month);
260
+ return createDate(year, month, 0);
261
+ }
262
+ /**
263
+ * 月の日付グリッドを生成する(6行×7列)
264
+ * 各セルは日数(1-31)または null(空欄)
265
+ */
266
+ function buildMonthGrid(year, month, weekStart) {
267
+ assertValidYearMonth(year, month);
268
+ assertValidWeekStart(weekStart);
269
+ const daysInMonth = lastDayOfMonth(year, month).getDate();
270
+ let skip = firstDayOfMonth(year, month).getDay();
271
+ if (weekStart === "monday") skip = (skip + 6) % 7;
272
+ const grid = [];
273
+ let day = 1;
274
+ for (let week = 0; week < 6; week++) {
275
+ const row = [];
276
+ for (let col = 0; col < 7; col++) if (day > daysInMonth || week === 0 && col < skip) row.push(null);
277
+ else row.push(day++);
278
+ grid.push(row);
279
+ }
280
+ return grid;
281
+ }
282
+ /** 範囲(from/to)が有効であることを検証する */
283
+ function assertValidRange(from, to) {
284
+ assertValidDate(from);
285
+ assertValidDate(to);
286
+ if (from.getTime() > to.getTime()) throw new RangeError("Invalid range: from must not be after to");
287
+ }
288
+ /** 日付が範囲内に含まれるか */
289
+ function isDateInRange(date, range) {
290
+ if (!range) return false;
291
+ assertValidRange(range.from, range.to);
292
+ assertValidDate(date);
293
+ const time = date.getTime();
294
+ return time >= range.from.getTime() && time <= range.to.getTime();
295
+ }
296
+ /** 日付が今日と一致するか(日付のみ比較) */
297
+ function isSameDay(a, b) {
298
+ assertValidDate(a);
299
+ assertValidDate(b);
300
+ return a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate();
301
+ }
302
+ /** 日付が範囲の開始年から終了年までの月リストを返す */
303
+ function getMonthRange(from, to) {
304
+ assertValidRange(from, to);
305
+ const start = from.getFullYear() * 12 + from.getMonth();
306
+ const end = to.getFullYear() * 12 + to.getMonth();
307
+ return Array.from({ length: end - start + 1 }, (_, i) => {
308
+ const n = start + i;
309
+ return {
310
+ year: Math.floor(n / 12),
311
+ month: n % 12 + 1
312
+ };
313
+ });
314
+ }
315
+ //#endregion
316
+ export { LOCALES, MAX_YEAR, MIN_YEAR, assertValidDate, buildMonthGrid, createDate, firstDayOfMonth, getMonthName, getMonthRange, getWeekdayHeaders, isDateInRange, isSameDay, lastDayOfMonth };
317
+
318
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/validation.ts","../src/locale.ts","../src/utils.ts"],"sourcesContent":["// ─── 入力検証 ─────────────────────────────────────────────\n\n/** サポートする年の最小値(JS Date は 0-99 を 1900+年 と解釈するため 1 から) */\nexport const MIN_YEAR = 1;\n/** サポートする年の最大値 */\nexport const MAX_YEAR = 9999;\n\n/** 値が min-max の整数であることを検証する(不正なら RangeError) */\nfunction assertIntegerInRange(\n name: string,\n value: number,\n min: number,\n max: number,\n): void {\n if (!Number.isInteger(value) || value < min || value > max) {\n throw new RangeError(\n `Invalid ${name}: ${value} (expected an integer between ${min} and ${max})`,\n );\n }\n}\n\n/** 年が 1-9999 の整数であることを検証する */\nexport function assertValidYear(year: number): void {\n assertIntegerInRange(\"year\", year, MIN_YEAR, MAX_YEAR);\n}\n\n/** 月が 1-12 の整数であることを検証する */\nexport function assertValidMonth(month: number): void {\n assertIntegerInRange(\"month\", month, 1, 12);\n}\n\n/** 年の組み合わせ(year と month)を検証する */\nexport function assertValidYearMonth(year: number, month: number): void {\n assertValidYear(year);\n assertValidMonth(month);\n}\n\n/** 週の開始曜日を検証する */\nexport function assertValidWeekStart(weekStart: string): void {\n if (weekStart !== \"sunday\" && weekStart !== \"monday\") {\n throw new RangeError(\n `Invalid weekStart: \"${weekStart}\" (expected \"sunday\" or \"monday\")`,\n );\n }\n}\n\n/** Date が有効(Invalid Date でない)ことを検証する */\nexport function assertValidDate(date: Date): void {\n if (!(date instanceof Date) || Number.isNaN(date.getTime())) {\n throw new RangeError(`Invalid Date: ${String(date)}`);\n }\n}\n\n/**\n * ローカルの指定日 00:00:00 を生成する。\n * `new Date(year, ...)` は year 0-99 を 1900+year と解釈するため、\n * setFullYear を使って year 0-99 も正しく扱えるようにする。\n */\nexport function createDate(\n year: number,\n monthIndex: number,\n day: number,\n): Date {\n const date = new Date(0);\n date.setHours(0, 0, 0, 0);\n date.setFullYear(year, monthIndex, day);\n return date;\n}\n","import type { Locale, WeekStart } from \"./types.ts\";\nimport { assertValidMonth, assertValidWeekStart } from \"./validation.ts\";\n\nexport interface LocaleData {\n months: readonly string[];\n weekdays: readonly string[];\n weekdaysShort: readonly string[];\n weekdaysMonday: readonly string[];\n weekdaysMondayShort: readonly string[];\n}\n\n/** 月曜始まりの配列は日曜始まりの配列を前ローテーションして生成する */\nfunction makeLocale(\n months: readonly string[],\n weekdays: readonly string[],\n weekdaysShort: readonly string[] = weekdays,\n): LocaleData {\n const rotate = (arr: readonly string[]) => [...arr.slice(1), arr[0]!];\n return {\n months,\n weekdays,\n weekdaysShort,\n weekdaysMonday: rotate(weekdays),\n weekdaysMondayShort: rotate(weekdaysShort),\n };\n}\n\nexport const LOCALES: Record<Locale, LocaleData> = {\n en: makeLocale(\n [\n \"January\",\n \"February\",\n \"March\",\n \"April\",\n \"May\",\n \"June\",\n \"July\",\n \"August\",\n \"September\",\n \"October\",\n \"November\",\n \"December\",\n ],\n [\"Sun\", \"Mon\", \"Tue\", \"Wed\", \"Thu\", \"Fri\", \"Sat\"],\n [\"Su\", \"Mo\", \"Tu\", \"We\", \"Th\", \"Fr\", \"Sa\"],\n ),\n ja: makeLocale(\n [\n \"1月\",\n \"2月\",\n \"3月\",\n \"4月\",\n \"5月\",\n \"6月\",\n \"7月\",\n \"8月\",\n \"9月\",\n \"10月\",\n \"11月\",\n \"12月\",\n ],\n [\"日\", \"月\", \"火\", \"水\", \"木\", \"金\", \"土\"],\n ),\n es: makeLocale(\n [\n \"enero\",\n \"febrero\",\n \"marzo\",\n \"abril\",\n \"mayo\",\n \"junio\",\n \"julio\",\n \"agosto\",\n \"septiembre\",\n \"octubre\",\n \"noviembre\",\n \"diciembre\",\n ],\n [\"dom\", \"lun\", \"mar\", \"mié\", \"jue\", \"vie\", \"sáb\"],\n [\"do\", \"lu\", \"ma\", \"mi\", \"ju\", \"vi\", \"sá\"],\n ),\n de: makeLocale(\n [\n \"Januar\",\n \"Februar\",\n \"März\",\n \"April\",\n \"Mai\",\n \"Juni\",\n \"Juli\",\n \"August\",\n \"September\",\n \"Oktober\",\n \"November\",\n \"Dezember\",\n ],\n [\"So\", \"Mo\", \"Di\", \"Mi\", \"Do\", \"Fr\", \"Sa\"],\n ),\n fr: makeLocale(\n [\n \"janvier\",\n \"février\",\n \"mars\",\n \"avril\",\n \"mai\",\n \"juin\",\n \"juillet\",\n \"août\",\n \"septembre\",\n \"octobre\",\n \"novembre\",\n \"décembre\",\n ],\n [\"dim.\", \"lun.\", \"mar.\", \"mer.\", \"jeu.\", \"ven.\", \"sam.\"],\n [\"di\", \"lu\", \"ma\", \"me\", \"je\", \"ve\", \"sa\"],\n ),\n ko: makeLocale(\n [\n \"1월\",\n \"2월\",\n \"3월\",\n \"4월\",\n \"5월\",\n \"6월\",\n \"7월\",\n \"8월\",\n \"9월\",\n \"10월\",\n \"11월\",\n \"12월\",\n ],\n [\"일\", \"월\", \"화\", \"수\", \"목\", \"금\", \"토\"],\n ),\n zh: makeLocale(\n [\n \"一月\",\n \"二月\",\n \"三月\",\n \"四月\",\n \"五月\",\n \"六月\",\n \"七月\",\n \"八月\",\n \"九月\",\n \"十月\",\n \"十一月\",\n \"十二月\",\n ],\n [\"日\", \"一\", \"二\", \"三\", \"四\", \"五\", \"六\"],\n ),\n};\n\n/** ロケールデータを取得する(未知のロケールは RangeError) */\nfunction getLocaleData(locale: Locale): LocaleData {\n const data = LOCALES[locale];\n if (data === undefined) {\n throw new RangeError(`Invalid locale: \"${locale}\"`);\n }\n return data;\n}\n\nexport function getWeekdayHeaders(\n locale: Locale,\n weekStart: WeekStart,\n): readonly string[] {\n assertValidWeekStart(weekStart);\n const data = getLocaleData(locale);\n return weekStart === \"monday\" ? data.weekdaysMonday : data.weekdays;\n}\n\nexport function getMonthName(locale: Locale, month: number): string {\n assertValidMonth(month);\n const data = getLocaleData(locale);\n return data.months[month - 1]!;\n}\n","import type { WeekStart } from \"./types.ts\";\nimport {\n assertValidDate,\n assertValidWeekStart,\n assertValidYearMonth,\n createDate,\n} from \"./validation.ts\";\n\n/** 月初日(1日)を返す */\nexport function firstDayOfMonth(year: number, month: number): Date {\n assertValidYearMonth(year, month);\n return createDate(year, month - 1, 1);\n}\n\n/** 月末日を返す */\nexport function lastDayOfMonth(year: number, month: number): Date {\n assertValidYearMonth(year, month);\n return createDate(year, month, 0);\n}\n\n/**\n * 月の日付グリッドを生成する(6行×7列)\n * 各セルは日数(1-31)または null(空欄)\n */\nexport function buildMonthGrid(\n year: number,\n month: number,\n weekStart: WeekStart,\n): (number | null)[][] {\n assertValidYearMonth(year, month);\n assertValidWeekStart(weekStart);\n\n const daysInMonth = lastDayOfMonth(year, month).getDate();\n let skip = firstDayOfMonth(year, month).getDay();\n if (weekStart === \"monday\") {\n skip = (skip + 6) % 7;\n }\n\n const grid: (number | null)[][] = [];\n let day = 1;\n for (let week = 0; week < 6; week++) {\n const row: (number | null)[] = [];\n for (let col = 0; col < 7; col++) {\n if (day > daysInMonth || (week === 0 && col < skip)) {\n row.push(null);\n } else {\n row.push(day++);\n }\n }\n grid.push(row);\n }\n return grid;\n}\n\n/** 範囲(from/to)が有効であることを検証する */\nfunction assertValidRange(from: Date, to: Date): void {\n assertValidDate(from);\n assertValidDate(to);\n if (from.getTime() > to.getTime()) {\n throw new RangeError(\"Invalid range: from must not be after to\");\n }\n}\n\n/** 日付が範囲内に含まれるか */\nexport function isDateInRange(\n date: Date,\n range?: { from: Date; to: Date },\n): boolean {\n if (!range) return false;\n assertValidRange(range.from, range.to);\n assertValidDate(date);\n const time = date.getTime();\n return time >= range.from.getTime() && time <= range.to.getTime();\n}\n\n/** 日付が今日と一致するか(日付のみ比較) */\nexport function isSameDay(a: Date, b: Date): boolean {\n assertValidDate(a);\n assertValidDate(b);\n return (\n a.getFullYear() === b.getFullYear() &&\n a.getMonth() === b.getMonth() &&\n a.getDate() === b.getDate()\n );\n}\n\n/** 日付が範囲の開始年から終了年までの月リストを返す */\nexport function getMonthRange(\n from: Date,\n to: Date,\n): { year: number; month: number }[] {\n assertValidRange(from, to);\n const start = from.getFullYear() * 12 + from.getMonth();\n const end = to.getFullYear() * 12 + to.getMonth();\n return Array.from({ length: end - start + 1 }, (_, i) => {\n const n = start + i;\n return { year: Math.floor(n / 12), month: (n % 12) + 1 };\n });\n}\n"],"mappings":";;AAGA,MAAa,WAAW;;AAExB,MAAa,WAAW;;AAGxB,SAAS,qBACP,MACA,OACA,KACA,KACM;CACN,IAAI,CAAC,OAAO,UAAU,KAAK,KAAK,QAAQ,OAAO,QAAQ,KACrD,MAAM,IAAI,WACR,WAAW,KAAK,IAAI,MAAM,gCAAgC,IAAI,OAAO,IAAI,EAC3E;AAEJ;;AAGA,SAAgB,gBAAgB,MAAoB;CAClD,qBAAqB,QAAQ,MAAA,GAAgB,QAAQ;AACvD;;AAGA,SAAgB,iBAAiB,OAAqB;CACpD,qBAAqB,SAAS,OAAO,GAAG,EAAE;AAC5C;;AAGA,SAAgB,qBAAqB,MAAc,OAAqB;CACtE,gBAAgB,IAAI;CACpB,iBAAiB,KAAK;AACxB;;AAGA,SAAgB,qBAAqB,WAAyB;CAC5D,IAAI,cAAc,YAAY,cAAc,UAC1C,MAAM,IAAI,WACR,uBAAuB,UAAU,kCACnC;AAEJ;;AAGA,SAAgB,gBAAgB,MAAkB;CAChD,IAAI,EAAE,gBAAgB,SAAS,OAAO,MAAM,KAAK,QAAQ,CAAC,GACxD,MAAM,IAAI,WAAW,iBAAiB,OAAO,IAAI,GAAG;AAExD;;;;;;AAOA,SAAgB,WACd,MACA,YACA,KACM;CACN,MAAM,uBAAO,IAAI,KAAK,CAAC;CACvB,KAAK,SAAS,GAAG,GAAG,GAAG,CAAC;CACxB,KAAK,YAAY,MAAM,YAAY,GAAG;CACtC,OAAO;AACT;;;;ACvDA,SAAS,WACP,QACA,UACA,gBAAmC,UACvB;CACZ,MAAM,UAAU,QAA2B,CAAC,GAAG,IAAI,MAAM,CAAC,GAAG,IAAI,EAAG;CACpE,OAAO;EACL;EACA;EACA;EACA,gBAAgB,OAAO,QAAQ;EAC/B,qBAAqB,OAAO,aAAa;CAC3C;AACF;AAEA,MAAa,UAAsC;CACjD,IAAI,WACF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;CACF,GACA;EAAC;EAAO;EAAO;EAAO;EAAO;EAAO;EAAO;CAAK,GAChD;EAAC;EAAM;EAAM;EAAM;EAAM;EAAM;EAAM;CAAI,CAC3C;CACA,IAAI,WACF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;CACF,GACA;EAAC;EAAK;EAAK;EAAK;EAAK;EAAK;EAAK;CAAG,CACpC;CACA,IAAI,WACF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;CACF,GACA;EAAC;EAAO;EAAO;EAAO;EAAO;EAAO;EAAO;CAAK,GAChD;EAAC;EAAM;EAAM;EAAM;EAAM;EAAM;EAAM;CAAI,CAC3C;CACA,IAAI,WACF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;CACF,GACA;EAAC;EAAM;EAAM;EAAM;EAAM;EAAM;EAAM;CAAI,CAC3C;CACA,IAAI,WACF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;CACF,GACA;EAAC;EAAQ;EAAQ;EAAQ;EAAQ;EAAQ;EAAQ;CAAM,GACvD;EAAC;EAAM;EAAM;EAAM;EAAM;EAAM;EAAM;CAAI,CAC3C;CACA,IAAI,WACF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;CACF,GACA;EAAC;EAAK;EAAK;EAAK;EAAK;EAAK;EAAK;CAAG,CACpC;CACA,IAAI,WACF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;CACF,GACA;EAAC;EAAK;EAAK;EAAK;EAAK;EAAK;EAAK;CAAG,CACpC;AACF;;AAGA,SAAS,cAAc,QAA4B;CACjD,MAAM,OAAO,QAAQ;CACrB,IAAI,SAAS,KAAA,GACX,MAAM,IAAI,WAAW,oBAAoB,OAAO,EAAE;CAEpD,OAAO;AACT;AAEA,SAAgB,kBACd,QACA,WACmB;CACnB,qBAAqB,SAAS;CAC9B,MAAM,OAAO,cAAc,MAAM;CACjC,OAAO,cAAc,WAAW,KAAK,iBAAiB,KAAK;AAC7D;AAEA,SAAgB,aAAa,QAAgB,OAAuB;CAClE,iBAAiB,KAAK;CAEtB,OADa,cAAc,MACjB,CAAC,CAAC,OAAO,QAAQ;AAC7B;;;;ACrKA,SAAgB,gBAAgB,MAAc,OAAqB;CACjE,qBAAqB,MAAM,KAAK;CAChC,OAAO,WAAW,MAAM,QAAQ,GAAG,CAAC;AACtC;;AAGA,SAAgB,eAAe,MAAc,OAAqB;CAChE,qBAAqB,MAAM,KAAK;CAChC,OAAO,WAAW,MAAM,OAAO,CAAC;AAClC;;;;;AAMA,SAAgB,eACd,MACA,OACA,WACqB;CACrB,qBAAqB,MAAM,KAAK;CAChC,qBAAqB,SAAS;CAE9B,MAAM,cAAc,eAAe,MAAM,KAAK,CAAC,CAAC,QAAQ;CACxD,IAAI,OAAO,gBAAgB,MAAM,KAAK,CAAC,CAAC,OAAO;CAC/C,IAAI,cAAc,UAChB,QAAQ,OAAO,KAAK;CAGtB,MAAM,OAA4B,CAAC;CACnC,IAAI,MAAM;CACV,KAAK,IAAI,OAAO,GAAG,OAAO,GAAG,QAAQ;EACnC,MAAM,MAAyB,CAAC;EAChC,KAAK,IAAI,MAAM,GAAG,MAAM,GAAG,OACzB,IAAI,MAAM,eAAgB,SAAS,KAAK,MAAM,MAC5C,IAAI,KAAK,IAAI;OAEb,IAAI,KAAK,KAAK;EAGlB,KAAK,KAAK,GAAG;CACf;CACA,OAAO;AACT;;AAGA,SAAS,iBAAiB,MAAY,IAAgB;CACpD,gBAAgB,IAAI;CACpB,gBAAgB,EAAE;CAClB,IAAI,KAAK,QAAQ,IAAI,GAAG,QAAQ,GAC9B,MAAM,IAAI,WAAW,0CAA0C;AAEnE;;AAGA,SAAgB,cACd,MACA,OACS;CACT,IAAI,CAAC,OAAO,OAAO;CACnB,iBAAiB,MAAM,MAAM,MAAM,EAAE;CACrC,gBAAgB,IAAI;CACpB,MAAM,OAAO,KAAK,QAAQ;CAC1B,OAAO,QAAQ,MAAM,KAAK,QAAQ,KAAK,QAAQ,MAAM,GAAG,QAAQ;AAClE;;AAGA,SAAgB,UAAU,GAAS,GAAkB;CACnD,gBAAgB,CAAC;CACjB,gBAAgB,CAAC;CACjB,OACE,EAAE,YAAY,MAAM,EAAE,YAAY,KAClC,EAAE,SAAS,MAAM,EAAE,SAAS,KAC5B,EAAE,QAAQ,MAAM,EAAE,QAAQ;AAE9B;;AAGA,SAAgB,cACd,MACA,IACmC;CACnC,iBAAiB,MAAM,EAAE;CACzB,MAAM,QAAQ,KAAK,YAAY,IAAI,KAAK,KAAK,SAAS;CACtD,MAAM,MAAM,GAAG,YAAY,IAAI,KAAK,GAAG,SAAS;CAChD,OAAO,MAAM,KAAK,EAAE,QAAQ,MAAM,QAAQ,EAAE,IAAI,GAAG,MAAM;EACvD,MAAM,IAAI,QAAQ;EAClB,OAAO;GAAE,MAAM,KAAK,MAAM,IAAI,EAAE;GAAG,OAAQ,IAAI,KAAM;EAAE;CACzD,CAAC;AACH"}
package/package.json CHANGED
@@ -1,10 +1,24 @@
1
1
  {
2
2
  "name": "@typescript-calendar-lib/core",
3
- "version": "0.1.1",
3
+ "version": "0.3.0",
4
4
  "type": "module",
5
- "main": "src/index.ts",
6
- "types": "src/index.ts",
7
- "peerDependencies": {
8
- "typescript": "^5"
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "https://github.com/nazozokc/typescript-calendar-lib"
8
+ },
9
+ "main": "./dist/index.js",
10
+ "types": "./dist/index.d.ts",
11
+ "exports": {
12
+ ".": {
13
+ "types": "./dist/index.d.ts",
14
+ "development": "./src/index.ts",
15
+ "default": "./dist/index.js"
16
+ }
17
+ },
18
+ "files": [
19
+ "dist"
20
+ ],
21
+ "scripts": {
22
+ "build": "tsdown"
9
23
  }
10
- }
24
+ }
package/src/index.ts DELETED
@@ -1,22 +0,0 @@
1
- export {
2
- getMonthName,
3
- getWeekdayHeaders,
4
- LOCALES,
5
- } from "./locale.ts";
6
- export type {
7
- CalendarOptions,
8
- CalendarRangeOptions,
9
- CalendarYearOptions,
10
- HighlightStyle,
11
- Locale,
12
- RenderMonthOptions,
13
- WeekStart,
14
- } from "./types.ts";
15
- export {
16
- buildMonthGrid,
17
- firstDayOfMonth,
18
- getMonthRange,
19
- isDateInRange,
20
- isSameDay,
21
- lastDayOfMonth,
22
- } from "./utils.ts";
@@ -1,175 +0,0 @@
1
- import { describe, expect, test } from "vitest";
2
- import { getMonthName, getWeekdayHeaders, LOCALES } from "./locale.ts";
3
-
4
- describe("LOCALES", () => {
5
- test("全ロケールが12ヶ月分の月名を持つ", () => {
6
- for (const locale of Object.keys(LOCALES) as (keyof typeof LOCALES)[]) {
7
- expect(LOCALES[locale].months).toHaveLength(12);
8
- }
9
- });
10
-
11
- test("全ロケールが7曜日のヘッダを持つ", () => {
12
- for (const locale of Object.keys(LOCALES) as (keyof typeof LOCALES)[]) {
13
- expect(LOCALES[locale].weekdays).toHaveLength(7);
14
- expect(LOCALES[locale].weekdaysShort).toHaveLength(7);
15
- expect(LOCALES[locale].weekdaysMonday).toHaveLength(7);
16
- expect(LOCALES[locale].weekdaysMondayShort).toHaveLength(7);
17
- }
18
- });
19
-
20
- test("日本語以外のロケールが存在する", () => {
21
- expect(Object.keys(LOCALES)).toEqual(
22
- expect.arrayContaining(["en", "ja", "es", "de", "fr", "ko", "zh"]),
23
- );
24
- });
25
- });
26
-
27
- describe("getWeekdayHeaders", () => {
28
- test("英語・日曜始まりはSunから", () => {
29
- expect(getWeekdayHeaders("en", "sunday")).toEqual([
30
- "Sun",
31
- "Mon",
32
- "Tue",
33
- "Wed",
34
- "Thu",
35
- "Fri",
36
- "Sat",
37
- ]);
38
- });
39
-
40
- test("英語・月曜始まりはMonから", () => {
41
- expect(getWeekdayHeaders("en", "monday")).toEqual([
42
- "Mon",
43
- "Tue",
44
- "Wed",
45
- "Thu",
46
- "Fri",
47
- "Sat",
48
- "Sun",
49
- ]);
50
- });
51
-
52
- test("日本語・日曜始まりは日から", () => {
53
- expect(getWeekdayHeaders("ja", "sunday")).toEqual([
54
- "日",
55
- "月",
56
- "火",
57
- "水",
58
- "木",
59
- "金",
60
- "土",
61
- ]);
62
- });
63
-
64
- test("日本語・月曜始まりは月から", () => {
65
- expect(getWeekdayHeaders("ja", "monday")).toEqual([
66
- "月",
67
- "火",
68
- "水",
69
- "木",
70
- "金",
71
- "土",
72
- "日",
73
- ]);
74
- });
75
-
76
- test("スペイン語・日曜始まり", () => {
77
- expect(getWeekdayHeaders("es", "sunday")).toEqual([
78
- "dom",
79
- "lun",
80
- "mar",
81
- "mié",
82
- "jue",
83
- "vie",
84
- "sáb",
85
- ]);
86
- });
87
-
88
- test("ドイツ語・月曜始まり", () => {
89
- expect(getWeekdayHeaders("de", "monday")).toEqual([
90
- "Mo",
91
- "Di",
92
- "Mi",
93
- "Do",
94
- "Fr",
95
- "Sa",
96
- "So",
97
- ]);
98
- });
99
-
100
- test("フランス語・日曜始まり", () => {
101
- expect(getWeekdayHeaders("fr", "sunday")).toEqual([
102
- "dim.",
103
- "lun.",
104
- "mar.",
105
- "mer.",
106
- "jeu.",
107
- "ven.",
108
- "sam.",
109
- ]);
110
- });
111
-
112
- test("韓国語・月曜始まり", () => {
113
- expect(getWeekdayHeaders("ko", "monday")).toEqual([
114
- "월",
115
- "화",
116
- "수",
117
- "목",
118
- "금",
119
- "토",
120
- "일",
121
- ]);
122
- });
123
-
124
- test("中国語・月曜始まり", () => {
125
- expect(getWeekdayHeaders("zh", "monday")).toEqual([
126
- "一",
127
- "二",
128
- "三",
129
- "四",
130
- "五",
131
- "六",
132
- "日",
133
- ]);
134
- });
135
- });
136
-
137
- describe("getMonthName", () => {
138
- test("英語の月名", () => {
139
- expect(getMonthName("en", 1)).toBe("January");
140
- expect(getMonthName("en", 9)).toBe("September");
141
- expect(getMonthName("en", 12)).toBe("December");
142
- });
143
-
144
- test("日本語の月名", () => {
145
- expect(getMonthName("ja", 1)).toBe("1月");
146
- expect(getMonthName("ja", 12)).toBe("12月");
147
- });
148
-
149
- test("スペイン語の月名", () => {
150
- expect(getMonthName("es", 1)).toBe("enero");
151
- expect(getMonthName("es", 9)).toBe("septiembre");
152
- expect(getMonthName("es", 12)).toBe("diciembre");
153
- });
154
-
155
- test("ドイツ語の月名", () => {
156
- expect(getMonthName("de", 1)).toBe("Januar");
157
- expect(getMonthName("de", 3)).toBe("März");
158
- expect(getMonthName("de", 12)).toBe("Dezember");
159
- });
160
-
161
- test("フランス語の月名", () => {
162
- expect(getMonthName("fr", 1)).toBe("janvier");
163
- expect(getMonthName("fr", 8)).toBe("août");
164
- });
165
-
166
- test("韓国語の月名", () => {
167
- expect(getMonthName("ko", 1)).toBe("1월");
168
- expect(getMonthName("ko", 12)).toBe("12월");
169
- });
170
-
171
- test("中国語の月名", () => {
172
- expect(getMonthName("zh", 1)).toBe("一月");
173
- expect(getMonthName("zh", 12)).toBe("十二月");
174
- });
175
- });
package/src/locale.ts DELETED
@@ -1,164 +0,0 @@
1
- import type { Locale, WeekStart } from "./types.ts";
2
-
3
- export interface LocaleData {
4
- months: readonly string[];
5
- weekdays: readonly string[];
6
- weekdaysShort: readonly string[];
7
- weekdaysMonday: readonly string[];
8
- weekdaysMondayShort: readonly string[];
9
- }
10
-
11
- export const LOCALES: Record<Locale, LocaleData> = {
12
- en: {
13
- months: [
14
- "January",
15
- "February",
16
- "March",
17
- "April",
18
- "May",
19
- "June",
20
- "July",
21
- "August",
22
- "September",
23
- "October",
24
- "November",
25
- "December",
26
- ],
27
- weekdays: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"],
28
- weekdaysShort: ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"],
29
- weekdaysMonday: ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"],
30
- weekdaysMondayShort: ["Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"],
31
- },
32
- ja: {
33
- months: [
34
- "1月",
35
- "2月",
36
- "3月",
37
- "4月",
38
- "5月",
39
- "6月",
40
- "7月",
41
- "8月",
42
- "9月",
43
- "10月",
44
- "11月",
45
- "12月",
46
- ],
47
- weekdays: ["日", "月", "火", "水", "木", "金", "土"],
48
- weekdaysShort: ["日", "月", "火", "水", "木", "金", "土"],
49
- weekdaysMonday: ["月", "火", "水", "木", "金", "土", "日"],
50
- weekdaysMondayShort: ["月", "火", "水", "木", "金", "土", "日"],
51
- },
52
- es: {
53
- months: [
54
- "enero",
55
- "febrero",
56
- "marzo",
57
- "abril",
58
- "mayo",
59
- "junio",
60
- "julio",
61
- "agosto",
62
- "septiembre",
63
- "octubre",
64
- "noviembre",
65
- "diciembre",
66
- ],
67
- weekdays: ["dom", "lun", "mar", "mié", "jue", "vie", "sáb"],
68
- weekdaysShort: ["do", "lu", "ma", "mi", "ju", "vi", "sá"],
69
- weekdaysMonday: ["lun", "mar", "mié", "jue", "vie", "sáb", "dom"],
70
- weekdaysMondayShort: ["lu", "ma", "mi", "ju", "vi", "sá", "do"],
71
- },
72
- de: {
73
- months: [
74
- "Januar",
75
- "Februar",
76
- "März",
77
- "April",
78
- "Mai",
79
- "Juni",
80
- "Juli",
81
- "August",
82
- "September",
83
- "Oktober",
84
- "November",
85
- "Dezember",
86
- ],
87
- weekdays: ["So", "Mo", "Di", "Mi", "Do", "Fr", "Sa"],
88
- weekdaysShort: ["So", "Mo", "Di", "Mi", "Do", "Fr", "Sa"],
89
- weekdaysMonday: ["Mo", "Di", "Mi", "Do", "Fr", "Sa", "So"],
90
- weekdaysMondayShort: ["Mo", "Di", "Mi", "Do", "Fr", "Sa", "So"],
91
- },
92
- fr: {
93
- months: [
94
- "janvier",
95
- "février",
96
- "mars",
97
- "avril",
98
- "mai",
99
- "juin",
100
- "juillet",
101
- "août",
102
- "septembre",
103
- "octobre",
104
- "novembre",
105
- "décembre",
106
- ],
107
- weekdays: ["dim.", "lun.", "mar.", "mer.", "jeu.", "ven.", "sam."],
108
- weekdaysShort: ["di", "lu", "ma", "me", "je", "ve", "sa"],
109
- weekdaysMonday: ["lun.", "mar.", "mer.", "jeu.", "ven.", "sam.", "dim."],
110
- weekdaysMondayShort: ["lu", "ma", "me", "je", "ve", "sa", "di"],
111
- },
112
- ko: {
113
- months: [
114
- "1월",
115
- "2월",
116
- "3월",
117
- "4월",
118
- "5월",
119
- "6월",
120
- "7월",
121
- "8월",
122
- "9월",
123
- "10월",
124
- "11월",
125
- "12월",
126
- ],
127
- weekdays: ["일", "월", "화", "수", "목", "금", "토"],
128
- weekdaysShort: ["일", "월", "화", "수", "목", "금", "토"],
129
- weekdaysMonday: ["월", "화", "수", "목", "금", "토", "일"],
130
- weekdaysMondayShort: ["월", "화", "수", "목", "금", "토", "일"],
131
- },
132
- zh: {
133
- months: [
134
- "一月",
135
- "二月",
136
- "三月",
137
- "四月",
138
- "五月",
139
- "六月",
140
- "七月",
141
- "八月",
142
- "九月",
143
- "十月",
144
- "十一月",
145
- "十二月",
146
- ],
147
- weekdays: ["日", "一", "二", "三", "四", "五", "六"],
148
- weekdaysShort: ["日", "一", "二", "三", "四", "五", "六"],
149
- weekdaysMonday: ["一", "二", "三", "四", "五", "六", "日"],
150
- weekdaysMondayShort: ["一", "二", "三", "四", "五", "六", "日"],
151
- },
152
- };
153
-
154
- export function getWeekdayHeaders(
155
- locale: Locale,
156
- weekStart: WeekStart,
157
- ): readonly string[] {
158
- const data = LOCALES[locale];
159
- return weekStart === "monday" ? data.weekdaysMonday : data.weekdays;
160
- }
161
-
162
- export function getMonthName(locale: Locale, month: number): string {
163
- return LOCALES[locale].months[month - 1]!;
164
- }
package/src/types.ts DELETED
@@ -1,44 +0,0 @@
1
- export type Locale = "en" | "ja" | "es" | "de" | "fr" | "ko" | "zh";
2
- export type WeekStart = "sunday" | "monday";
3
- export type HighlightStyle = "bracket" | "reverse";
4
-
5
- export interface CalendarOptions {
6
- year: number;
7
- month: number;
8
- locale?: Locale;
9
- weekStart?: WeekStart;
10
- highlight?: Date;
11
- highlightStyle?: HighlightStyle;
12
- range?: { from: Date; to: Date };
13
- color?: boolean;
14
- }
15
-
16
- export interface CalendarYearOptions {
17
- year: number;
18
- locale?: Locale;
19
- weekStart?: WeekStart;
20
- highlight?: Date;
21
- highlightStyle?: HighlightStyle;
22
- range?: { from: Date; to: Date };
23
- color?: boolean;
24
- }
25
-
26
- export interface CalendarRangeOptions {
27
- from: Date;
28
- to: Date;
29
- locale?: Locale;
30
- weekStart?: WeekStart;
31
- highlight?: Date;
32
- highlightStyle?: HighlightStyle;
33
- range?: { from: Date; to: Date };
34
- color?: boolean;
35
- }
36
-
37
- export interface RenderMonthOptions {
38
- locale?: Locale;
39
- weekStart?: WeekStart;
40
- highlight?: Date;
41
- highlightStyle?: HighlightStyle;
42
- range?: { from: Date; to: Date };
43
- color?: boolean;
44
- }
package/src/utils.test.ts DELETED
@@ -1,140 +0,0 @@
1
- import { describe, expect, test } from "vitest";
2
- import {
3
- buildMonthGrid,
4
- firstDayOfMonth,
5
- getMonthRange,
6
- isDateInRange,
7
- isSameDay,
8
- lastDayOfMonth,
9
- } from "./utils.ts";
10
-
11
- describe("firstDayOfMonth", () => {
12
- test("1月の月初", () => {
13
- expect(firstDayOfMonth(2026, 1)).toEqual(new Date(2026, 0, 1));
14
- });
15
-
16
- test("12月の月初", () => {
17
- expect(firstDayOfMonth(2026, 12)).toEqual(new Date(2026, 11, 1));
18
- });
19
- });
20
-
21
- describe("lastDayOfMonth", () => {
22
- test("30日で終わる月(4月は30日)", () => {
23
- expect(lastDayOfMonth(2026, 4)).toEqual(new Date(2026, 3, 30));
24
- });
25
-
26
- test("31日で終わる月", () => {
27
- expect(lastDayOfMonth(2026, 1)).toEqual(new Date(2026, 0, 31));
28
- });
29
-
30
- test("通常年の2月は28日", () => {
31
- expect(lastDayOfMonth(2026, 2)).toEqual(new Date(2026, 1, 28));
32
- });
33
-
34
- test("うるう年の2月は29日", () => {
35
- expect(lastDayOfMonth(2024, 2)).toEqual(new Date(2024, 1, 29));
36
- });
37
- });
38
-
39
- describe("buildMonthGrid", () => {
40
- test("gridは常に6行×7列", () => {
41
- const grid = buildMonthGrid(2026, 9, "sunday");
42
- expect(grid).toHaveLength(6);
43
- for (const row of grid) {
44
- expect(row).toHaveLength(7);
45
- }
46
- });
47
-
48
- test("9月2026(日曜始まり)は1日が火曜から開始", () => {
49
- const grid = buildMonthGrid(2026, 9, "sunday");
50
- expect(grid[0]).toEqual([null, null, 1, 2, 3, 4, 5]);
51
- });
52
-
53
- test("9月2026(月曜始まり)は1日が月曜から開始", () => {
54
- const grid = buildMonthGrid(2026, 9, "monday");
55
- expect(grid[0]).toEqual([null, 1, 2, 3, 4, 5, 6]);
56
- });
57
-
58
- test("日付の日数が月末を超えない", () => {
59
- const grid = buildMonthGrid(2026, 2, "sunday");
60
- const flat = grid.flat().filter((d) => d !== null);
61
- expect(Math.max(...(flat as number[]))).toBe(28);
62
- expect(flat).toHaveLength(28);
63
- });
64
- });
65
-
66
- describe("isDateInRange", () => {
67
- const range = { from: new Date(2026, 8, 1), to: new Date(2026, 8, 15) };
68
-
69
- test("範囲なしなら常にfalse", () => {
70
- expect(isDateInRange(new Date(2026, 8, 1))).toBe(false);
71
- });
72
-
73
- test("範囲内の日付はtrue", () => {
74
- expect(isDateInRange(new Date(2026, 8, 10), range)).toBe(true);
75
- });
76
-
77
- test("開始日は境界として含む", () => {
78
- expect(isDateInRange(new Date(2026, 8, 1), range)).toBe(true);
79
- });
80
-
81
- test("終了日は境界として含む", () => {
82
- expect(isDateInRange(new Date(2026, 8, 15), range)).toBe(true);
83
- });
84
-
85
- test("範囲外の前日はfalse", () => {
86
- expect(isDateInRange(new Date(2026, 7, 31), range)).toBe(false);
87
- });
88
-
89
- test("範囲外の翌日はfalse", () => {
90
- expect(isDateInRange(new Date(2026, 8, 16), range)).toBe(false);
91
- });
92
- });
93
-
94
- describe("isSameDay", () => {
95
- test("同じ日付はtrue", () => {
96
- expect(
97
- isSameDay(new Date(2026, 8, 8, 3, 30), new Date(2026, 8, 8, 20, 0)),
98
- ).toBe(true);
99
- });
100
-
101
- test("異なる日はfalse", () => {
102
- expect(isSameDay(new Date(2026, 8, 8), new Date(2026, 8, 9))).toBe(false);
103
- });
104
-
105
- test("同じ日だが異なる月はfalse", () => {
106
- expect(isSameDay(new Date(2026, 8, 8), new Date(2026, 7, 8))).toBe(false);
107
- });
108
- });
109
-
110
- describe("getMonthRange", () => {
111
- test("同月内は1件", () => {
112
- expect(getMonthRange(new Date(2026, 8, 1), new Date(2026, 8, 30))).toEqual([
113
- { year: 2026, month: 9 },
114
- ]);
115
- });
116
-
117
- test("複数月に跨る", () => {
118
- expect(getMonthRange(new Date(2026, 5, 1), new Date(2026, 8, 30))).toEqual([
119
- { year: 2026, month: 6 },
120
- { year: 2026, month: 7 },
121
- { year: 2026, month: 8 },
122
- { year: 2026, month: 9 },
123
- ]);
124
- });
125
-
126
- test("12月から翌年1月に跨る", () => {
127
- expect(
128
- getMonthRange(new Date(2025, 11, 31), new Date(2026, 0, 15)),
129
- ).toEqual([
130
- { year: 2025, month: 12 },
131
- { year: 2026, month: 1 },
132
- ]);
133
- });
134
-
135
- test("1年の範囲", () => {
136
- expect(
137
- getMonthRange(new Date(2026, 0, 1), new Date(2026, 11, 31)),
138
- ).toHaveLength(12);
139
- });
140
- });
package/src/utils.ts DELETED
@@ -1,94 +0,0 @@
1
- import type { WeekStart } from "./types.ts";
2
-
3
- /** 月初日(1日)を返す */
4
- export function firstDayOfMonth(year: number, month: number): Date {
5
- return new Date(year, month - 1, 1);
6
- }
7
-
8
- /** 月末日を返す */
9
- export function lastDayOfMonth(year: number, month: number): Date {
10
- return new Date(year, month, 0);
11
- }
12
-
13
- /**
14
- * 月の日付グリッドを生成する(6行×7列)
15
- * 各セルは日数(1-31)または null(空欄)
16
- */
17
- export function buildMonthGrid(
18
- year: number,
19
- month: number,
20
- weekStart: WeekStart,
21
- ): (number | null)[][] {
22
- const first = firstDayOfMonth(year, month);
23
- const last = lastDayOfMonth(year, month);
24
- const daysInMonth = last.getDate();
25
-
26
- let startDayOfWeek = first.getDay();
27
- if (weekStart === "monday") {
28
- startDayOfWeek = (startDayOfWeek + 6) % 7;
29
- }
30
-
31
- const grid: (number | null)[][] = [];
32
- let currentDay = 1;
33
-
34
- for (let week = 0; week < 6; week++) {
35
- const row: (number | null)[] = [];
36
- for (let dayOfWeek = 0; dayOfWeek < 7; dayOfWeek++) {
37
- if (
38
- (week === 0 && dayOfWeek < startDayOfWeek) ||
39
- currentDay > daysInMonth
40
- ) {
41
- row.push(null);
42
- } else {
43
- row.push(currentDay);
44
- currentDay++;
45
- }
46
- }
47
- grid.push(row);
48
- }
49
-
50
- return grid;
51
- }
52
-
53
- /** 日付が範囲内に含まれるか */
54
- export function isDateInRange(
55
- date: Date,
56
- range?: { from: Date; to: Date },
57
- ): boolean {
58
- if (!range) return false;
59
- const time = date.getTime();
60
- return time >= range.from.getTime() && time <= range.to.getTime();
61
- }
62
-
63
- /** 日付が今日と一致するか(日付のみ比較) */
64
- export function isSameDay(a: Date, b: Date): boolean {
65
- return (
66
- a.getFullYear() === b.getFullYear() &&
67
- a.getMonth() === b.getMonth() &&
68
- a.getDate() === b.getDate()
69
- );
70
- }
71
-
72
- /** 日付が範囲の年开始から範囲の年结束までの月リストを返す */
73
- export function getMonthRange(
74
- from: Date,
75
- to: Date,
76
- ): { year: number; month: number }[] {
77
- const months: { year: number; month: number }[] = [];
78
- let year = from.getFullYear();
79
- let month = from.getMonth() + 1;
80
-
81
- while (
82
- year < to.getFullYear() ||
83
- (year === to.getFullYear() && month <= to.getMonth() + 1)
84
- ) {
85
- months.push({ year, month });
86
- month++;
87
- if (month > 12) {
88
- month = 1;
89
- year++;
90
- }
91
- }
92
-
93
- return months;
94
- }
package/tsconfig.json DELETED
@@ -1,7 +0,0 @@
1
- {
2
- "extends": "../../tsconfig.json",
3
- "compilerOptions": {
4
- "rootDir": "src"
5
- },
6
- "include": ["src"]
7
- }