@nepdatejs/core 0.0.0-bootstrap → 0.1.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/dist/index.js ADDED
@@ -0,0 +1,1241 @@
1
+ import { t as BS_DATA } from "./years-CabTFbN9.js";
2
+ //#region src/errors.ts
3
+ /** Thrown for any invalid date or argument. Check `code`, not the message. */
4
+ var NepDateRangeError = class extends RangeError {
5
+ constructor(code, message) {
6
+ super(message);
7
+ this.name = "NepDateRangeError";
8
+ this.code = code;
9
+ }
10
+ };
11
+ /** Thrown when a BS table passed to `setBsData` or `patchBsYear` is not valid. */
12
+ var NepDateDataError = class extends Error {
13
+ constructor(code, message, year, month) {
14
+ super(message);
15
+ this.name = "NepDateDataError";
16
+ this.code = code;
17
+ this.year = year;
18
+ this.month = month;
19
+ }
20
+ };
21
+ //#endregion
22
+ //#region src/epoch.ts
23
+ /** Brand a whole number of days since 1970-01-01 as an `EpochDay`. */
24
+ function toEpochDay(value) {
25
+ if (!Number.isSafeInteger(value)) throw new NepDateRangeError("INVALID_ARGUMENT", `${String(value)} is not a whole number of days.`);
26
+ return value;
27
+ }
28
+ /** The weekday of a day count. 1970-01-01 was a Thursday. */
29
+ function weekdayOf(epoch) {
30
+ return ((epoch + 4) % 7 + 7) % 7;
31
+ }
32
+ //#endregion
33
+ //#region src/locale/en.ts
34
+ const bsMonths$1 = [
35
+ "Baisakh",
36
+ "Jestha",
37
+ "Asar",
38
+ "Shrawan",
39
+ "Bhadra",
40
+ "Ashwin",
41
+ "Kartik",
42
+ "Mangsir",
43
+ "Poush",
44
+ "Magh",
45
+ "Falgun",
46
+ "Chaitra"
47
+ ];
48
+ const adMonths$1 = [
49
+ "January",
50
+ "February",
51
+ "March",
52
+ "April",
53
+ "May",
54
+ "June",
55
+ "July",
56
+ "August",
57
+ "September",
58
+ "October",
59
+ "November",
60
+ "December"
61
+ ];
62
+ const en = {
63
+ digits: [
64
+ "0",
65
+ "1",
66
+ "2",
67
+ "3",
68
+ "4",
69
+ "5",
70
+ "6",
71
+ "7",
72
+ "8",
73
+ "9"
74
+ ],
75
+ weekdays: {
76
+ long: [
77
+ "Sunday",
78
+ "Monday",
79
+ "Tuesday",
80
+ "Wednesday",
81
+ "Thursday",
82
+ "Friday",
83
+ "Saturday"
84
+ ],
85
+ short: [
86
+ "Sun",
87
+ "Mon",
88
+ "Tue",
89
+ "Wed",
90
+ "Thu",
91
+ "Fri",
92
+ "Sat"
93
+ ],
94
+ narrow: [
95
+ "Su",
96
+ "Mo",
97
+ "Tu",
98
+ "We",
99
+ "Th",
100
+ "Fr",
101
+ "Sa"
102
+ ]
103
+ },
104
+ months: {
105
+ AD: {
106
+ long: adMonths$1,
107
+ short: adMonths$1.map((name) => name.slice(0, 3))
108
+ },
109
+ BS: {
110
+ long: bsMonths$1,
111
+ short: bsMonths$1.map((name) => name.slice(0, 3))
112
+ }
113
+ },
114
+ calendarNames: {
115
+ AD: "AD",
116
+ BS: "BS"
117
+ },
118
+ labels: {
119
+ today: "Today",
120
+ clear: "Clear date",
121
+ previousMonth: "Previous month",
122
+ nextMonth: "Next month",
123
+ previousYear: "Previous year",
124
+ nextYear: "Next year",
125
+ month: "Month",
126
+ year: "Year",
127
+ openCalendar: "Open calendar",
128
+ chooseDate: "Choose a date",
129
+ calendar: "Calendar",
130
+ presets: "Shortcuts",
131
+ fiscalYear: "Fiscal year",
132
+ time: "Time",
133
+ hour: "Hour",
134
+ minute: "Minute",
135
+ now: "Now",
136
+ done: "Done"
137
+ },
138
+ time: {
139
+ am: "AM",
140
+ pm: "PM"
141
+ },
142
+ presets: {
143
+ today: "Today",
144
+ yesterday: "Yesterday",
145
+ tomorrow: "Tomorrow",
146
+ thisWeek: "This week",
147
+ lastWeek: "Last week",
148
+ last7Days: "Last 7 days",
149
+ last30Days: "Last 30 days",
150
+ thisMonth: "This month",
151
+ lastMonth: "Last month",
152
+ thisYear: "This year",
153
+ thisFiscalYear: "This fiscal year",
154
+ lastFiscalYear: "Last fiscal year"
155
+ },
156
+ messages: {
157
+ invalidFormat: {
158
+ AD: "Enter an AD date as YYYY-MM-DD.",
159
+ BS: "Enter a BS date as YYYY-MM-DD."
160
+ },
161
+ outOfRange: "Choose a date from {min} to {max}.",
162
+ onOrAfter: "Choose {min} or later.",
163
+ onOrBefore: "Choose {max} or earlier.",
164
+ unavailable: "This date is not available.",
165
+ rangeTooShort: "Choose at least {min} days.",
166
+ rangeTooLong: "Choose at most {max} days.",
167
+ tooManyDates: "Choose at most {max} dates.",
168
+ invalidTime: "Enter a time as HH:MM."
169
+ }
170
+ };
171
+ //#endregion
172
+ //#region src/locale/ne.ts
173
+ const bsMonths = [
174
+ "बैशाख",
175
+ "जेठ",
176
+ "असार",
177
+ "साउन",
178
+ "भदौ",
179
+ "असोज",
180
+ "कार्तिक",
181
+ "मङ्सिर",
182
+ "पुस",
183
+ "माघ",
184
+ "फाल्गुण",
185
+ "चैत"
186
+ ];
187
+ const adMonths = [
188
+ "जनवरी",
189
+ "फेब्रुअरी",
190
+ "मार्च",
191
+ "अप्रिल",
192
+ "मे",
193
+ "जुन",
194
+ "जुलाई",
195
+ "अगस्ट",
196
+ "सेप्टेम्बर",
197
+ "अक्टोबर",
198
+ "नोभेम्बर",
199
+ "डिसेम्बर"
200
+ ];
201
+ //#endregion
202
+ //#region src/locale/registry.ts
203
+ const locales = /* @__PURE__ */ new Map([["en", en], ["ne", {
204
+ digits: [
205
+ "०",
206
+ "१",
207
+ "२",
208
+ "३",
209
+ "४",
210
+ "५",
211
+ "६",
212
+ "७",
213
+ "८",
214
+ "९"
215
+ ],
216
+ weekdays: {
217
+ long: [
218
+ "आइतबार",
219
+ "सोमबार",
220
+ "मङ्गलबार",
221
+ "बुधबार",
222
+ "बिहिबार",
223
+ "शुक्रबार",
224
+ "शनिबार"
225
+ ],
226
+ short: [
227
+ "आइत",
228
+ "सोम",
229
+ "मङ्गल",
230
+ "बुध",
231
+ "बिही",
232
+ "शुक्र",
233
+ "शनि"
234
+ ],
235
+ narrow: [
236
+ "आ",
237
+ "सो",
238
+ "मं",
239
+ "बु",
240
+ "बि",
241
+ "शु",
242
+ "श"
243
+ ]
244
+ },
245
+ months: {
246
+ AD: {
247
+ long: adMonths,
248
+ short: adMonths
249
+ },
250
+ BS: {
251
+ long: bsMonths,
252
+ short: bsMonths
253
+ }
254
+ },
255
+ calendarNames: {
256
+ AD: "ई.सं.",
257
+ BS: "वि.सं."
258
+ },
259
+ labels: {
260
+ today: "आज",
261
+ clear: "मिति हटाउनुहोस्",
262
+ previousMonth: "अघिल्लो महिना",
263
+ nextMonth: "अर्को महिना",
264
+ previousYear: "अघिल्लो वर्ष",
265
+ nextYear: "अर्को वर्ष",
266
+ month: "महिना",
267
+ year: "वर्ष",
268
+ openCalendar: "पात्रो खोल्नुहोस्",
269
+ chooseDate: "मिति छान्नुहोस्",
270
+ calendar: "पात्रो",
271
+ presets: "छिटो छनोट",
272
+ fiscalYear: "आर्थिक वर्ष",
273
+ time: "समय",
274
+ hour: "घण्टा",
275
+ minute: "मिनेट",
276
+ now: "अहिले",
277
+ done: "भयो"
278
+ },
279
+ time: {
280
+ am: "पूर्वाह्न",
281
+ pm: "अपराह्न"
282
+ },
283
+ presets: {
284
+ today: "आज",
285
+ yesterday: "हिजो",
286
+ tomorrow: "भोलि",
287
+ thisWeek: "यो हप्ता",
288
+ lastWeek: "गत हप्ता",
289
+ last7Days: "पछिल्लो ७ दिन",
290
+ last30Days: "पछिल्लो ३० दिन",
291
+ thisMonth: "यो महिना",
292
+ lastMonth: "गत महिना",
293
+ thisYear: "यो वर्ष",
294
+ thisFiscalYear: "यो आर्थिक वर्ष",
295
+ lastFiscalYear: "गत आर्थिक वर्ष"
296
+ },
297
+ messages: {
298
+ invalidFormat: {
299
+ AD: "ई.सं. मिति YYYY-MM-DD ढाँचामा लेख्नुहोस्।",
300
+ BS: "वि.सं. मिति YYYY-MM-DD ढाँचामा लेख्नुहोस्।"
301
+ },
302
+ outOfRange: "{min} देखि {max} सम्मको मिति छान्नुहोस्।",
303
+ onOrAfter: "{min} वा त्यसपछिको मिति छान्नुहोस्।",
304
+ onOrBefore: "{max} वा त्यसअघिको मिति छान्नुहोस्।",
305
+ unavailable: "यो मिति उपलब्ध छैन।",
306
+ rangeTooShort: "कम्तीमा {min} दिन छान्नुहोस्।",
307
+ rangeTooLong: "बढीमा {max} दिन छान्नुहोस्।",
308
+ tooManyDates: "बढीमा {max} मिति छान्नुहोस्।",
309
+ invalidTime: "समय HH:MM ढाँचामा लेख्नुहोस्।"
310
+ }
311
+ }]]);
312
+ /** Add or replace a language. Lists must have the right lengths (10 digits, 7 weekdays, 12 months). */
313
+ function registerLocale(code, data) {
314
+ const checks = [
315
+ [
316
+ "digits",
317
+ data.digits,
318
+ 10
319
+ ],
320
+ [
321
+ "weekdays.long",
322
+ data.weekdays.long,
323
+ 7
324
+ ],
325
+ [
326
+ "weekdays.short",
327
+ data.weekdays.short,
328
+ 7
329
+ ],
330
+ [
331
+ "weekdays.narrow",
332
+ data.weekdays.narrow,
333
+ 7
334
+ ],
335
+ [
336
+ "months.AD.long",
337
+ data.months.AD.long,
338
+ 12
339
+ ],
340
+ [
341
+ "months.AD.short",
342
+ data.months.AD.short,
343
+ 12
344
+ ],
345
+ [
346
+ "months.BS.long",
347
+ data.months.BS.long,
348
+ 12
349
+ ],
350
+ [
351
+ "months.BS.short",
352
+ data.months.BS.short,
353
+ 12
354
+ ]
355
+ ];
356
+ for (const [name, list, length] of checks) if (list.length !== length) throw new NepDateRangeError("INVALID_ARGUMENT", `Locale "${code}": ${name} must have ${length} entries, not ${list.length}.`);
357
+ locales.set(code, data);
358
+ }
359
+ function hasLocale(code) {
360
+ return locales.has(code);
361
+ }
362
+ /** The data for a locale, or English when the locale is not registered. */
363
+ function localeData(code) {
364
+ return locales.get(code) ?? en;
365
+ }
366
+ //#endregion
367
+ //#region src/config.ts
368
+ const DEFAULTS = Object.freeze({
369
+ timeZone: "Asia/Kathmandu",
370
+ weekStartsOn: 0,
371
+ weekendDays: Object.freeze([6]),
372
+ defaultLocale: "en"
373
+ });
374
+ let current = DEFAULTS;
375
+ /** Change one or more settings. Every value is checked before any is applied. */
376
+ function configure(changes) {
377
+ const next = {
378
+ ...current,
379
+ ...changes
380
+ };
381
+ if (!isTimeZone(next.timeZone)) throw new NepDateRangeError("INVALID_ARGUMENT", `"${next.timeZone}" is not a time zone.`);
382
+ if (!isWeekday(next.weekStartsOn)) throw new NepDateRangeError("INVALID_ARGUMENT", `weekStartsOn must be 0–6, not ${String(next.weekStartsOn)}.`);
383
+ if (!next.weekendDays.every(isWeekday) || new Set(next.weekendDays).size !== next.weekendDays.length) throw new NepDateRangeError("INVALID_ARGUMENT", "weekendDays must be different weekdays, each 0–6.");
384
+ if (!hasLocale(next.defaultLocale)) throw new NepDateRangeError("INVALID_ARGUMENT", `Locale "${next.defaultLocale}" is not registered.`);
385
+ current = Object.freeze({
386
+ ...next,
387
+ weekendDays: Object.freeze([...next.weekendDays])
388
+ });
389
+ }
390
+ function getConfig() {
391
+ return current;
392
+ }
393
+ /** Back to the defaults (Kathmandu, Sunday first, Saturday weekend, English). */
394
+ function resetConfig() {
395
+ current = DEFAULTS;
396
+ }
397
+ function isWeekday(value) {
398
+ return Number.isInteger(value) && value >= 0 && value <= 6;
399
+ }
400
+ function isTimeZone(value) {
401
+ try {
402
+ new Intl.DateTimeFormat("en-US", { timeZone: value });
403
+ return true;
404
+ } catch {
405
+ return false;
406
+ }
407
+ }
408
+ //#endregion
409
+ //#region src/ad/calendar.ts
410
+ /**
411
+ * The Gregorian (AD) calendar, by arithmetic only: no table, no BS knowledge,
412
+ * no `Date`. Works for any year in ±MAX_AD_YEAR (docs/plan D-001).
413
+ *
414
+ * The day-count algorithms are Howard Hinnant's days_from_civil and
415
+ * civil_from_days (proleptic Gregorian, March-based years).
416
+ */
417
+ /** AD years must lie within ±this value. */
418
+ const MAX_AD_YEAR = 999999;
419
+ const DAYS_BEFORE_EPOCH = 719468;
420
+ const DAYS_PER_ERA = 146097;
421
+ const MONTH_DAYS = [
422
+ 31,
423
+ 28,
424
+ 31,
425
+ 30,
426
+ 31,
427
+ 30,
428
+ 31,
429
+ 31,
430
+ 30,
431
+ 31,
432
+ 30,
433
+ 31
434
+ ];
435
+ function isAdLeapYear(year) {
436
+ return year % 4 === 0 && year % 100 !== 0 || year % 400 === 0;
437
+ }
438
+ /** Days in an AD month. The month must be 1–12. */
439
+ function adMonthLength(year, month) {
440
+ return month === 2 && isAdLeapYear(year) ? 29 : MONTH_DAYS[month - 1] ?? 0;
441
+ }
442
+ function isValidAdDate(year, month, day) {
443
+ return Number.isInteger(year) && Number.isInteger(month) && Number.isInteger(day) && Math.abs(year) <= 999999 && month >= 1 && month <= 12 && day >= 1 && day <= adMonthLength(year, month);
444
+ }
445
+ /** Days since 1970-01-01 of a valid AD date. */
446
+ function adToEpoch(year, month, day) {
447
+ const y = month <= 2 ? year - 1 : year;
448
+ const era = Math.floor(y / 400);
449
+ const yearOfEra = y - era * 400;
450
+ const dayOfYear = Math.floor((153 * (month + (month > 2 ? -3 : 9)) + 2) / 5) + day - 1;
451
+ const dayOfEra = yearOfEra * 365 + Math.floor(yearOfEra / 4) - Math.floor(yearOfEra / 100) + dayOfYear;
452
+ return era * DAYS_PER_ERA + dayOfEra - DAYS_BEFORE_EPOCH;
453
+ }
454
+ /** The AD year, month and day of a day count. */
455
+ function epochToAd(epoch) {
456
+ const z = epoch + DAYS_BEFORE_EPOCH;
457
+ const era = Math.floor(z / DAYS_PER_ERA);
458
+ const dayOfEra = z - era * DAYS_PER_ERA;
459
+ const yearOfEra = Math.floor((dayOfEra - Math.floor(dayOfEra / 1460) + Math.floor(dayOfEra / 36524) - Math.floor(dayOfEra / 146096)) / 365);
460
+ const dayOfYear = dayOfEra - (365 * yearOfEra + Math.floor(yearOfEra / 4) - Math.floor(yearOfEra / 100));
461
+ const shiftedMonth = Math.floor((5 * dayOfYear + 2) / 153);
462
+ const day = dayOfYear - Math.floor((153 * shiftedMonth + 2) / 5) + 1;
463
+ const month = shiftedMonth < 10 ? shiftedMonth + 3 : shiftedMonth - 9;
464
+ return {
465
+ year: yearOfEra + era * 400 + (month <= 2 ? 1 : 0),
466
+ month,
467
+ day
468
+ };
469
+ }
470
+ //#endregion
471
+ //#region src/bs/table.ts
472
+ /**
473
+ * The Bikram Sambat calendar, from its table only. Nothing here reads or
474
+ * produces an AD year: BS dates map to and from day counts (EpochDay) through
475
+ * `epochStart` and the month lengths (docs/plan D-001).
476
+ */
477
+ let data = freezeData(BS_DATA);
478
+ let table = null;
479
+ /** Replace the data the table is built from. Callers validate first (data-api.ts). */
480
+ function useBsData(next) {
481
+ data = freezeData(next);
482
+ table = null;
483
+ }
484
+ function freezeData(source) {
485
+ return Object.freeze({
486
+ firstYear: source.firstYear,
487
+ epochStart: source.epochStart,
488
+ verifiedThrough: source.verifiedThrough,
489
+ years: Object.freeze(source.years.map((months) => Object.freeze([...months])))
490
+ });
491
+ }
492
+ function currentBsData() {
493
+ return data;
494
+ }
495
+ function buildTable() {
496
+ const count = data.years.length;
497
+ const yearStart = new Float64Array(count + 1);
498
+ const monthOffset = new Uint16Array(count * 12);
499
+ let start = data.epochStart;
500
+ for (let i = 0; i < count; i++) {
501
+ yearStart[i] = start;
502
+ const months = data.years[i] ?? [];
503
+ let offset = 0;
504
+ for (let m = 0; m < 12; m++) {
505
+ monthOffset[i * 12 + m] = offset;
506
+ offset += months[m] ?? 0;
507
+ }
508
+ start += offset;
509
+ }
510
+ yearStart[count] = start;
511
+ return {
512
+ data,
513
+ lastYear: data.firstYear + count - 1,
514
+ yearStart,
515
+ monthOffset
516
+ };
517
+ }
518
+ function bsTable() {
519
+ return table ?? (table = buildTable());
520
+ }
521
+ function bsFirstYear() {
522
+ return data.firstYear;
523
+ }
524
+ function bsLastYear() {
525
+ return bsTable().lastYear;
526
+ }
527
+ /** The first and last day counts the table covers. */
528
+ function bsEpochRange() {
529
+ const { yearStart } = bsTable();
530
+ return {
531
+ first: yearStart[0] ?? 0,
532
+ last: (yearStart[yearStart.length - 1] ?? 0) - 1
533
+ };
534
+ }
535
+ /** Days in a BS month. Throws when the year is outside the table or the month is not 1–12. */
536
+ function bsMonthLength(year, month) {
537
+ const { lastYear } = bsTable();
538
+ if (!Number.isInteger(year) || year < data.firstYear || year > lastYear) throw new NepDateRangeError("OUT_OF_TABLE", `BS year ${String(year)} is outside the supported range (BS ${data.firstYear}–${lastYear}).`);
539
+ if (!Number.isInteger(month) || month < 1 || month > 12) throw new NepDateRangeError("INVALID_DATE", `BS month ${String(month)} does not exist.`);
540
+ return data.years[year - data.firstYear]?.[month - 1] ?? 0;
541
+ }
542
+ function isValidBsDate(year, month, day) {
543
+ try {
544
+ return Number.isInteger(day) && day >= 1 && day <= bsMonthLength(year, month);
545
+ } catch {
546
+ return false;
547
+ }
548
+ }
549
+ /** Day count of a valid BS date. */
550
+ function bsToEpoch(year, month, day) {
551
+ const { yearStart, monthOffset } = bsTable();
552
+ const index = year - data.firstYear;
553
+ return (yearStart[index] ?? 0) + (monthOffset[index * 12 + month - 1] ?? 0) + day - 1;
554
+ }
555
+ /** The BS year, month and day of a day count. Throws when it is outside the table. */
556
+ function epochToBs(epoch) {
557
+ const { yearStart, monthOffset } = bsTable();
558
+ const { first, last } = bsEpochRange();
559
+ if (epoch < first || epoch > last) throw new NepDateRangeError("OUT_OF_TABLE", `Day ${epoch} is outside the BS table (BS ${data.firstYear}–${bsLastYear()}).`);
560
+ let low = 0;
561
+ let high = yearStart.length - 2;
562
+ while (low < high) {
563
+ const middle = Math.ceil((low + high) / 2);
564
+ if ((yearStart[middle] ?? 0) <= epoch) low = middle;
565
+ else high = middle - 1;
566
+ }
567
+ const dayOfYear = epoch - (yearStart[low] ?? 0);
568
+ let month = 11;
569
+ while (month > 0 && (monthOffset[low * 12 + month] ?? 0) > dayOfYear) month--;
570
+ return {
571
+ year: data.firstYear + low,
572
+ month: month + 1,
573
+ day: dayOfYear - (monthOffset[low * 12 + month] ?? 0) + 1
574
+ };
575
+ }
576
+ /** Whether a BS year is after the last year checked against a published calendar. */
577
+ function isProjectedBsYear(year) {
578
+ return year > data.verifiedThrough;
579
+ }
580
+ //#endregion
581
+ //#region src/bs/data-api.ts
582
+ /** The first BS month of Nepal's fiscal year: Shrawan. */
583
+ const BS_FISCAL_YEAR_START_MONTH = 4;
584
+ /** Days a verified year may have. Projected years may be off by one (the bundled BS 2096 lists 364). */
585
+ const VERIFIED_YEAR_DAYS = [365, 366];
586
+ const PROJECTED_YEAR_DAYS = [
587
+ 364,
588
+ 365,
589
+ 366,
590
+ 367
591
+ ];
592
+ /** Throws NepDateDataError when the table is not usable. Same rules as scripts/gen-data.ts. */
593
+ function validateBsData(table) {
594
+ if (!Number.isSafeInteger(table.epochStart) || !Number.isInteger(table.firstYear)) throw new NepDateDataError("BAD_ANCHOR", "firstYear and epochStart must be whole numbers.");
595
+ if (!Number.isInteger(table.verifiedThrough)) throw new NepDateDataError("BAD_ANCHOR", "verifiedThrough must be a whole number.");
596
+ if (table.years.length === 0) throw new NepDateDataError("EMPTY", "The BS table has no years.");
597
+ table.years.forEach((months, index) => {
598
+ const year = table.firstYear + index;
599
+ if (months.length !== 12) throw new NepDateDataError("BAD_MONTH_COUNT", `BS ${year} lists ${months.length} months, not 12.`, year);
600
+ let total = 0;
601
+ months.forEach((days, m) => {
602
+ if (!Number.isInteger(days) || days < 29 || days > 32) throw new NepDateDataError("BAD_MONTH", `BS ${year} month ${m + 1} has ${String(days)} days; expected 29–32.`, year, m + 1);
603
+ total += days;
604
+ });
605
+ const allowed = year <= table.verifiedThrough ? VERIFIED_YEAR_DAYS : PROJECTED_YEAR_DAYS;
606
+ if (!allowed.includes(total)) throw new NepDateDataError("BAD_YEAR_TOTAL", `BS ${year} has ${total} days; expected ${allowed.join(" or ")}.`, year);
607
+ });
608
+ }
609
+ /** The BS table in use (frozen). */
610
+ function getBsData() {
611
+ return currentBsData();
612
+ }
613
+ /** Replace the whole BS table. Only BS results change; AD functions are unaffected. */
614
+ function setBsData(table) {
615
+ validateBsData(table);
616
+ useBsData(table);
617
+ }
618
+ /**
619
+ * Correct one year's month lengths, or add the year just before the first or
620
+ * just after the last. Years after a corrected year move with it; earlier years
621
+ * never move (adding a year before the first keeps every existing year in place).
622
+ */
623
+ function patchBsYear(year, months) {
624
+ const table = currentBsData();
625
+ const lastYear = table.firstYear + table.years.length - 1;
626
+ let next;
627
+ if (year >= table.firstYear && year <= lastYear) next = {
628
+ ...table,
629
+ years: table.years.map((row, index) => index === year - table.firstYear ? months : row)
630
+ };
631
+ else if (year === lastYear + 1) next = {
632
+ ...table,
633
+ years: [...table.years, months]
634
+ };
635
+ else if (year === table.firstYear - 1) {
636
+ const length = months.reduce((total, days) => total + days, 0);
637
+ next = {
638
+ ...table,
639
+ firstYear: year,
640
+ epochStart: table.epochStart - length,
641
+ years: [months, ...table.years]
642
+ };
643
+ } else throw new NepDateRangeError("OUT_OF_TABLE", `BS ${String(year)} is not in the table and does not extend it (BS ${table.firstYear}–${lastYear}).`);
644
+ setBsData(next);
645
+ }
646
+ /** Back to the table bundled with the package. */
647
+ function resetBsData() {
648
+ useBsData(BS_DATA);
649
+ }
650
+ //#endregion
651
+ //#region src/create.ts
652
+ /** An AD date. Any year within ±999,999 is allowed; only conversion to BS is limited. */
653
+ function adDate(year, month, day) {
654
+ if (!isValidAdDate(year, month, day)) throw new NepDateRangeError(Number.isInteger(year) && Number.isInteger(month) && Number.isInteger(day) ? "INVALID_DATE" : "INVALID_ARGUMENT", Math.abs(year) > 999999 ? `AD year ${String(year)} is outside ±${MAX_AD_YEAR}.` : `AD ${String(year)}-${String(month)}-${String(day)} is not a valid date.`);
655
+ return Object.freeze({
656
+ calendar: "AD",
657
+ year,
658
+ month,
659
+ day
660
+ });
661
+ }
662
+ /** A BS date. The year must be in the BS table. */
663
+ function bsDate(year, month, day) {
664
+ const length = bsMonthLength(year, month);
665
+ if (!Number.isInteger(day) || day < 1 || day > length) throw new NepDateRangeError(Number.isInteger(day) ? "INVALID_DATE" : "INVALID_ARGUMENT", `BS ${year}-${month} has ${length} days, not ${String(day)}.`);
666
+ return Object.freeze({
667
+ calendar: "BS",
668
+ year,
669
+ month,
670
+ day
671
+ });
672
+ }
673
+ function createDate(calendar, year, month, day) {
674
+ return calendar === "AD" ? adDate(year, month, day) : bsDate(year, month, day);
675
+ }
676
+ function isValidDate(calendar, year, month, day) {
677
+ return calendar === "AD" ? isValidAdDate(year, month, day) : isValidBsDate(year, month, day);
678
+ }
679
+ /** Days in a month of either calendar. */
680
+ function daysInMonth(calendar, year, month) {
681
+ if (calendar === "BS") return bsMonthLength(year, month);
682
+ if (!isValidAdDate(year, month, 1)) throw new NepDateRangeError("INVALID_DATE", `AD ${String(year)}-${String(month)} is not a month.`);
683
+ return adMonthLength(year, month);
684
+ }
685
+ /** True for a valid, frozen-or-not AD date object (e.g. from JSON). */
686
+ function isAdDate(value) {
687
+ return isDateLike(value, "AD") && isValidAdDate(value.year, value.month, value.day);
688
+ }
689
+ /** True for a valid BS date object (e.g. from JSON). */
690
+ function isBsDate(value) {
691
+ return isDateLike(value, "BS") && isValidBsDate(value.year, value.month, value.day);
692
+ }
693
+ function isCalendarDate(value) {
694
+ return isAdDate(value) || isBsDate(value);
695
+ }
696
+ function isDateLike(value, calendar) {
697
+ if (typeof value !== "object" || value === null) return false;
698
+ const record = value;
699
+ return record["calendar"] === calendar && typeof record["year"] === "number" && typeof record["month"] === "number" && typeof record["day"] === "number";
700
+ }
701
+ //#endregion
702
+ //#region src/convert.ts
703
+ /**
704
+ * The only place the AD and BS calendars meet, and only through EpochDay
705
+ * (docs/plan D-001). Nothing here looks up a year number across calendars.
706
+ */
707
+ /** The day count of a date in either calendar. */
708
+ function toEpoch(date) {
709
+ return toEpochDay(date.calendar === "AD" ? adToEpoch(date.year, date.month, date.day) : bsToEpoch(date.year, date.month, date.day));
710
+ }
711
+ /** The date of a day count in a calendar. BS throws OUT_OF_TABLE outside the table. */
712
+ function fromEpoch(epoch, calendar) {
713
+ if (calendar === "AD") {
714
+ const { year, month, day } = epochToAd(epoch);
715
+ return adDate(year, month, day);
716
+ }
717
+ assertConvertible(epoch);
718
+ const { year, month, day } = epochToBs(epoch);
719
+ return bsDate(year, month, day);
720
+ }
721
+ function adToBs(date) {
722
+ return fromEpoch(toEpoch(date), "BS");
723
+ }
724
+ function bsToAd(date) {
725
+ return fromEpoch(toEpoch(date), "AD");
726
+ }
727
+ /** The same day in the given calendar (the date itself when it is already in it). */
728
+ function toCalendar(date, calendar) {
729
+ if (date.calendar === calendar) return date;
730
+ return fromEpoch(toEpoch(date), calendar);
731
+ }
732
+ /** The first day that can be converted, in either calendar (the start of the BS table). */
733
+ function minDate(calendar) {
734
+ return fromEpoch(toEpochDay(bsEpochRange().first), calendar);
735
+ }
736
+ /** The last day that can be converted, in either calendar (the end of the BS table). */
737
+ function maxDate(calendar) {
738
+ return fromEpoch(toEpochDay(bsEpochRange().last), calendar);
739
+ }
740
+ /** Whether a date can be converted to the other calendar. */
741
+ function isConvertible(date) {
742
+ const epoch = toEpoch(date);
743
+ const { first, last } = bsEpochRange();
744
+ return epoch >= first && epoch <= last;
745
+ }
746
+ /** The AD day as "YYYY-MM-DD", the form to store and send (docs/plan D-003). */
747
+ function toIsoDate(date) {
748
+ const { year, month, day } = toCalendar(date, "AD");
749
+ if (year < 0 || year > 9999) throw new NepDateRangeError("INVALID_ARGUMENT", `AD year ${year} cannot be written as YYYY-MM-DD.`);
750
+ return `${pad(year, 4)}-${pad(month, 2)}-${pad(day, 2)}`;
751
+ }
752
+ /**
753
+ * An AD date from "YYYY-MM-DD", or from a longer ISO timestamp by its date part
754
+ * ("2026-10-18T09:30:00Z" → 2026-10-18). Null for anything else.
755
+ */
756
+ function fromIsoDate(value) {
757
+ const match = /^(\d{4})-(\d{2})-(\d{2})(?:$|[T ])/.exec(value ?? "");
758
+ if (!match) return null;
759
+ try {
760
+ return adDate(Number(match[1]), Number(match[2]), Number(match[3]));
761
+ } catch {
762
+ return null;
763
+ }
764
+ }
765
+ /** The AD calendar day an instant falls on in a time zone (Nepal by default). */
766
+ function fromJsDate(instant, timeZone = getConfig().timeZone) {
767
+ if (Number.isNaN(instant.getTime())) throw new NepDateRangeError("INVALID_ARGUMENT", "The Date is invalid.");
768
+ let parts;
769
+ try {
770
+ parts = new Intl.DateTimeFormat("en-US", {
771
+ timeZone,
772
+ year: "numeric",
773
+ month: "numeric",
774
+ day: "numeric",
775
+ era: "short"
776
+ }).formatToParts(instant);
777
+ } catch {
778
+ throw new NepDateRangeError("INVALID_ARGUMENT", `"${timeZone}" is not a time zone.`);
779
+ }
780
+ const part = (type) => parts.find((item) => item.type === type)?.value ?? "";
781
+ const year = Number(part("year"));
782
+ return adDate(part("era") === "BC" ? 1 - year : year, Number(part("month")), Number(part("day")));
783
+ }
784
+ /**
785
+ * The calendar day and time of an ISO timestamp *with a zone* ("…Z",
786
+ * "…+05:45"), read in `timeZone` (Nepal by default). Null for anything else,
787
+ * including plain dates, which have no instant.
788
+ */
789
+ function fromIsoTimestamp(value, timeZone = getConfig().timeZone) {
790
+ if (!value || !/T.*(?:Z|[+-]\d{2}:?\d{2})$/.test(value)) return null;
791
+ const instant = new Date(value);
792
+ if (Number.isNaN(instant.getTime())) return null;
793
+ const time = new Intl.DateTimeFormat("en-GB", {
794
+ timeZone,
795
+ hour: "2-digit",
796
+ minute: "2-digit",
797
+ hourCycle: "h23"
798
+ }).format(instant);
799
+ return {
800
+ date: fromJsDate(instant, timeZone),
801
+ time
802
+ };
803
+ }
804
+ function assertConvertible(epoch) {
805
+ const { first, last } = bsEpochRange();
806
+ if (epoch < first || epoch > last) {
807
+ const { year, month, day } = epochToAd(epoch);
808
+ const from = epochToAd(first);
809
+ const to = epochToAd(last);
810
+ throw new NepDateRangeError("OUT_OF_TABLE", `AD ${isoParts(year, month, day)} is outside the BS table (AD ${isoParts(from.year, from.month, from.day)} to ${isoParts(to.year, to.month, to.day)}).`);
811
+ }
812
+ }
813
+ function isoParts(year, month, day) {
814
+ return `${year}-${pad(month, 2)}-${pad(day, 2)}`;
815
+ }
816
+ function pad(value, length) {
817
+ return String(value).padStart(length, "0");
818
+ }
819
+ //#endregion
820
+ //#region src/projected.ts
821
+ /** Whether the date's BS year is after the last year checked against a published calendar. */
822
+ function isProjected(date) {
823
+ return isProjectedBsYear(toCalendar(date, "BS").year);
824
+ }
825
+ //#endregion
826
+ //#region src/clock.ts
827
+ function today(calendar = "AD", timeZone = getConfig().timeZone) {
828
+ return toCalendar(fromJsDate(/* @__PURE__ */ new Date(), timeZone), calendar);
829
+ }
830
+ /** The current calendar day and time (HH:MM) in `timeZone` (Nepal by default). */
831
+ function now(timeZone = getConfig().timeZone) {
832
+ const current = fromIsoTimestamp((/* @__PURE__ */ new Date()).toISOString(), timeZone);
833
+ const [hour = 0, minute = 0] = (current?.time ?? "00:00").split(":").map(Number);
834
+ return {
835
+ date: current?.date ?? fromJsDate(/* @__PURE__ */ new Date(), timeZone),
836
+ time: Object.freeze({
837
+ hour,
838
+ minute
839
+ })
840
+ };
841
+ }
842
+ //#endregion
843
+ //#region src/arithmetic.ts
844
+ function addDays(date, days) {
845
+ assertInteger(days, "days");
846
+ return fromEpoch(toEpochDay(toEpoch(date) + days), date.calendar);
847
+ }
848
+ /** Move by whole months in the date's own calendar; the day is capped to the new month's length. */
849
+ function addMonths(date, months) {
850
+ assertInteger(months, "months");
851
+ const index = date.year * 12 + (date.month - 1) + months;
852
+ const year = Math.floor(index / 12);
853
+ const month = index - year * 12 + 1;
854
+ const day = Math.min(date.day, daysInMonth(date.calendar, year, month));
855
+ return createDate(date.calendar, year, month, day);
856
+ }
857
+ /** Move by whole years in the date's own calendar; the day is capped (29 Feb, short BS months). */
858
+ function addYears(date, years) {
859
+ assertInteger(years, "years");
860
+ return addMonths(date, years * 12);
861
+ }
862
+ function startOfMonth(date) {
863
+ return createDate(date.calendar, date.year, date.month, 1);
864
+ }
865
+ function endOfMonth(date) {
866
+ return createDate(date.calendar, date.year, date.month, daysInMonth(date.calendar, date.year, date.month));
867
+ }
868
+ function startOfYear(date) {
869
+ return createDate(date.calendar, date.year, 1, 1);
870
+ }
871
+ function endOfYear(date) {
872
+ return createDate(date.calendar, date.year, 12, daysInMonth(date.calendar, date.year, 12));
873
+ }
874
+ function startOfWeek(date, weekStartsOn = getConfig().weekStartsOn) {
875
+ assertWeekday(weekStartsOn);
876
+ return addDays(date, -((weekdayOf(toEpoch(date)) - weekStartsOn + 7) % 7));
877
+ }
878
+ function endOfWeek(date, weekStartsOn = getConfig().weekStartsOn) {
879
+ return addDays(startOfWeek(date, weekStartsOn), 6);
880
+ }
881
+ /** Whole days from `b` to `a` (positive when `a` is later). The calendars may differ. */
882
+ function differenceInDays(a, b) {
883
+ return toEpoch(a) - toEpoch(b);
884
+ }
885
+ /** Every day from `start` to `end` inclusive, in `start`'s calendar. Nothing when end < start. */
886
+ function* eachDayOfInterval(start, end) {
887
+ const last = toEpoch(end);
888
+ for (let epoch = toEpoch(start); epoch <= last; epoch++) yield fromEpoch(toEpochDay(epoch), start.calendar);
889
+ }
890
+ /** Every day of the month the date is in. */
891
+ function eachDayOfMonth(date) {
892
+ return [...eachDayOfInterval(startOfMonth(date), endOfMonth(date))];
893
+ }
894
+ /** The date converted to `target`'s calendar; a small helper used by compare and fiscal code. */
895
+ function inCalendarOf(target, date) {
896
+ return toCalendar(date, target.calendar);
897
+ }
898
+ function assertInteger(value, name) {
899
+ if (!Number.isSafeInteger(value)) throw new NepDateRangeError("INVALID_ARGUMENT", `${name} must be a whole number, not ${String(value)}.`);
900
+ }
901
+ function assertWeekday(value) {
902
+ if (!isWeekday(value)) throw new NepDateRangeError("INVALID_ARGUMENT", `${String(value)} is not a weekday (0–6).`);
903
+ }
904
+ //#endregion
905
+ //#region src/compare.ts
906
+ /** -1, 0 or 1. Dates in different calendars compare by the day they fall on. */
907
+ function compareDates(a, b) {
908
+ return Math.sign(toEpoch(a) - toEpoch(b));
909
+ }
910
+ /** Same day, in any calendars. False when either is missing. */
911
+ function isSameDay(a, b) {
912
+ return a != null && b != null && toEpoch(a) === toEpoch(b);
913
+ }
914
+ /** Whether `b` falls in the same month as `a`, judged in `a`'s calendar. */
915
+ function isSameMonth(a, b) {
916
+ const other = inCalendarOf(a, b);
917
+ return other.year === a.year && other.month === a.month;
918
+ }
919
+ /** Whether `b` falls in the same year as `a`, judged in `a`'s calendar. */
920
+ function isSameYear(a, b) {
921
+ return inCalendarOf(a, b).year === a.year;
922
+ }
923
+ function isBefore(a, b) {
924
+ return toEpoch(a) < toEpoch(b);
925
+ }
926
+ function isAfter(a, b) {
927
+ return toEpoch(a) > toEpoch(b);
928
+ }
929
+ /** On or after `min` and on or before `max`; a missing bound is open. */
930
+ function isWithin(date, min, max) {
931
+ const day = toEpoch(date);
932
+ return (min == null || day >= toEpoch(min)) && (max == null || day <= toEpoch(max));
933
+ }
934
+ /** The date, or the nearest bound when it is outside, in the date's calendar. */
935
+ function clampDate(date, min, max) {
936
+ if (min != null && isBefore(date, min)) return toCalendar(min, date.calendar);
937
+ if (max != null && isAfter(date, max)) return toCalendar(max, date.calendar);
938
+ return date;
939
+ }
940
+ /** 0 = Sunday … 6 = Saturday. The same in both calendars. */
941
+ function dayOfWeek(date) {
942
+ return weekdayOf(toEpoch(date));
943
+ }
944
+ /** Saturday by default (Nepal); set `weekendDays` in configure() or pass them here. */
945
+ function isWeekend(date, weekendDays = getConfig().weekendDays) {
946
+ return weekendDays.includes(dayOfWeek(date));
947
+ }
948
+ //#endregion
949
+ //#region src/locale/index.ts
950
+ const DEVANAGARI = [
951
+ "०",
952
+ "१",
953
+ "२",
954
+ "३",
955
+ "४",
956
+ "५",
957
+ "६",
958
+ "७",
959
+ "८",
960
+ "९"
961
+ ];
962
+ /** The data for a locale (the configured default when omitted; English when unknown). */
963
+ function getLocale(locale = getConfig().defaultLocale) {
964
+ return localeData(locale);
965
+ }
966
+ function monthNames(calendar, locale, width = "long") {
967
+ return getLocale(locale).months[calendar][width];
968
+ }
969
+ function weekdayNames(locale, width = "long") {
970
+ return getLocale(locale).weekdays[width];
971
+ }
972
+ /** Replace Devanagari digits with ASCII digits. */
973
+ function toAsciiDigits(value) {
974
+ return value.replace(/[०-९]/g, (digit) => String(DEVANAGARI.indexOf(digit)));
975
+ }
976
+ /** Replace ASCII digits with Devanagari digits. */
977
+ function toDevanagariDigits(value) {
978
+ return String(value).replace(/[0-9]/g, (digit) => DEVANAGARI[Number(digit)] ?? digit);
979
+ }
980
+ /** Write a number (or digit string) with the locale's digits. */
981
+ function formatNumber(value, locale) {
982
+ const { digits } = getLocale(locale);
983
+ return String(value).replace(/[0-9]/g, (digit) => digits[Number(digit)] ?? digit);
984
+ }
985
+ /** Fill {name} placeholders in a locale message. */
986
+ function formatMessage(template, values) {
987
+ return template.replace(/\{(\w+)\}/g, (whole, name) => values[name] ?? whole);
988
+ }
989
+ //#endregion
990
+ //#region src/format.ts
991
+ /** Named formats. `cbms` is the dotted form Nepal's tax system uses. */
992
+ const DATE_FORMATS = Object.freeze({
993
+ iso: "Y-m-d",
994
+ cbms: "Y.m.d",
995
+ short: "j M Y",
996
+ long: "j F Y",
997
+ full: "l, j F Y"
998
+ });
999
+ /**
1000
+ * Format a date in its own calendar. Tokens: Y 2083, y 83, m 06, n 6, d 09,
1001
+ * j 9, F month name, M short month name, l weekday, D short weekday. Put a
1002
+ * backslash before a letter to print it as is. Digits follow the locale.
1003
+ *
1004
+ * formatDate(bsDate(2082, 4, 1), 'full') → "Thursday, 1 Shrawan 2082"
1005
+ * formatDate(bsDate(2082, 4, 1), 'long', 'ne') → "१ साउन २०८२"
1006
+ */
1007
+ function formatDate(date, pattern = "iso", locale) {
1008
+ const tokens = pattern in DATE_FORMATS ? DATE_FORMATS[pattern] : pattern;
1009
+ const data = getLocale(locale);
1010
+ const months = data.months[date.calendar];
1011
+ const digits = (value) => formatNumber(value, locale);
1012
+ const pad = (value) => String(value).padStart(2, "0");
1013
+ let output = "";
1014
+ for (let index = 0; index < tokens.length; index++) {
1015
+ const token = tokens.charAt(index);
1016
+ switch (token) {
1017
+ case "\\":
1018
+ index++;
1019
+ output += tokens.charAt(index);
1020
+ break;
1021
+ case "Y":
1022
+ output += digits(date.year);
1023
+ break;
1024
+ case "y":
1025
+ output += digits(pad((date.year % 100 + 100) % 100));
1026
+ break;
1027
+ case "m":
1028
+ output += digits(pad(date.month));
1029
+ break;
1030
+ case "n":
1031
+ output += digits(date.month);
1032
+ break;
1033
+ case "d":
1034
+ output += digits(pad(date.day));
1035
+ break;
1036
+ case "j":
1037
+ output += digits(date.day);
1038
+ break;
1039
+ case "F":
1040
+ output += months.long[date.month - 1] ?? "";
1041
+ break;
1042
+ case "M":
1043
+ output += months.short[date.month - 1] ?? "";
1044
+ break;
1045
+ case "l":
1046
+ output += data.weekdays.long[dayOfWeek(date)] ?? "";
1047
+ break;
1048
+ case "D":
1049
+ output += data.weekdays.short[dayOfWeek(date)] ?? "";
1050
+ break;
1051
+ default: output += token;
1052
+ }
1053
+ }
1054
+ return output;
1055
+ }
1056
+ //#endregion
1057
+ //#region src/fiscal.ts
1058
+ /** The fiscal year a date falls in: 1 Shrawan to the end of Asar. */
1059
+ function fiscalYear(date) {
1060
+ const bs = toCalendar(date, "BS");
1061
+ return fiscalYearRange(bs.month >= 4 ? bs.year : bs.year - 1);
1062
+ }
1063
+ /** The fiscal year that starts in BS `startYear` (2083 → FY 2083/84). */
1064
+ function fiscalYearRange(startYear) {
1065
+ const endYear = startYear + 1;
1066
+ const lastMonth = 3;
1067
+ return Object.freeze({
1068
+ startYear,
1069
+ label: `${startYear}/${String(endYear % 100).padStart(2, "0")}`,
1070
+ firstDay: bsDate(startYear, 4, 1),
1071
+ lastDay: bsDate(endYear, lastMonth, daysInMonth("BS", endYear, lastMonth))
1072
+ });
1073
+ }
1074
+ /**
1075
+ * label "2083/84" · full "2083/2084" · cbms "2083.084" (the form Nepal's tax
1076
+ * system uses). Digits follow the locale.
1077
+ */
1078
+ function formatFiscalYear(fy, style = "label", locale) {
1079
+ const end = fy.startYear + 1;
1080
+ return formatNumber(style === "full" ? `${fy.startYear}/${end}` : style === "cbms" ? `${fy.startYear}.${String(end % 1e3).padStart(3, "0")}` : fy.label, locale);
1081
+ }
1082
+ function isInFiscalYear(date, fy) {
1083
+ return compareDates(date, fy.firstDay) >= 0 && compareDates(date, fy.lastDay) <= 0;
1084
+ }
1085
+ //#endregion
1086
+ //#region src/parse.ts
1087
+ const YEAR_FIRST = /^(\d{4})[-./](\d{1,2})[-./](\d{1,2})$/;
1088
+ const COMPACT = /^(\d{4})(\d{2})(\d{2})$/;
1089
+ /**
1090
+ * A date written year first ("2083-06-24", "2083/6/24", "2083.06.24", or
1091
+ * compact "20830624") in ASCII or Devanagari digits, in the calendar the caller names. Other orders are
1092
+ * rejected, not guessed. Returns null when the text is not a valid date.
1093
+ */
1094
+ function parseDate(text, calendar) {
1095
+ try {
1096
+ return parseDateStrict(text ?? "", calendar);
1097
+ } catch {
1098
+ return null;
1099
+ }
1100
+ }
1101
+ /** Like parseDate, but throws a NepDateRangeError that says what is wrong. */
1102
+ function parseDateStrict(text, calendar) {
1103
+ const ascii = toAsciiDigits(text).trim();
1104
+ const match = YEAR_FIRST.exec(ascii) ?? COMPACT.exec(ascii);
1105
+ if (!match) throw new NepDateRangeError("INVALID_ARGUMENT", `"${text}" is not a date written as YYYY-MM-DD.`);
1106
+ return createDate(calendar, Number(match[1]), Number(match[2]), Number(match[3]));
1107
+ }
1108
+ /**
1109
+ * A fiscal year from its label: "2083/84", "2083/2084", "2083-84" or the
1110
+ * tax form "2083.084", in ASCII or Devanagari digits. Null when not valid.
1111
+ */
1112
+ function parseFiscalYear(text) {
1113
+ const match = /^(\d{4})\s*[-/.]\s*(\d{2,4})$/.exec(toAsciiDigits(text ?? "").trim());
1114
+ if (!match) return null;
1115
+ const start = Number(match[1]);
1116
+ const end = match[2] ?? "";
1117
+ const next = start + 1;
1118
+ if (end !== (end.length === 4 ? String(next) : String(next % 10 ** end.length).padStart(end.length, "0"))) return null;
1119
+ try {
1120
+ return fiscalYearRange(start);
1121
+ } catch {
1122
+ return null;
1123
+ }
1124
+ }
1125
+ //#endregion
1126
+ //#region src/time.ts
1127
+ function timeOfDay(hour, minute = 0) {
1128
+ if (!isValidTime(hour, minute)) throw new NepDateRangeError("INVALID_ARGUMENT", `${String(hour)}:${String(minute)} is not a time of day.`);
1129
+ return Object.freeze({
1130
+ hour,
1131
+ minute
1132
+ });
1133
+ }
1134
+ function isValidTime(hour, minute) {
1135
+ return Number.isInteger(hour) && Number.isInteger(minute) && hour >= 0 && hour <= 23 && minute >= 0 && minute <= 59;
1136
+ }
1137
+ /** -1, 0 or 1. */
1138
+ function compareTimes(a, b) {
1139
+ return Math.sign(a.hour * 60 + a.minute - (b.hour * 60 + b.minute));
1140
+ }
1141
+ /**
1142
+ * "14:30", "2:30 pm", "2 PM", "1430", "१४:३०", "२:३० अपराह्न" → TimeOfDay.
1143
+ * Null when the text is not a time.
1144
+ */
1145
+ function parseTime(text) {
1146
+ let value = toAsciiDigits(text ?? "").trim().toLowerCase();
1147
+ let meridiem = null;
1148
+ for (const code of ["en", "ne"]) {
1149
+ const { am, pm } = getLocale(code).time;
1150
+ for (const [word, kind] of [
1151
+ [am.toLowerCase(), "am"],
1152
+ [pm.toLowerCase(), "pm"],
1153
+ [am.toLowerCase().replace(/(.)/g, "$1."), "am"],
1154
+ [pm.toLowerCase().replace(/(.)/g, "$1."), "pm"]
1155
+ ]) if (value.endsWith(word)) {
1156
+ meridiem = kind;
1157
+ value = value.slice(0, -word.length).trim();
1158
+ break;
1159
+ }
1160
+ if (meridiem) break;
1161
+ }
1162
+ const match = /^(\d{1,2})(?:[:.](\d{2}))?$/.exec(value) ?? /^(\d{2})(\d{2})$/.exec(value);
1163
+ if (!match) return null;
1164
+ let hour = Number(match[1]);
1165
+ const minute = Number(match[2] ?? 0);
1166
+ if (meridiem) {
1167
+ if (hour < 1 || hour > 12) return null;
1168
+ hour = hour % 12 + (meridiem === "pm" ? 12 : 0);
1169
+ }
1170
+ return isValidTime(hour, minute) ? timeOfDay(hour, minute) : null;
1171
+ }
1172
+ /** "14:30" (24-hour) or "2:30 PM" (hour12), in the locale's digits and words. */
1173
+ function formatTime(time, options = {}) {
1174
+ const minute = String(time.minute).padStart(2, "0");
1175
+ if (!options.hour12) return formatNumber(`${String(time.hour).padStart(2, "0")}:${minute}`, options.locale);
1176
+ const { am, pm } = getLocale(options.locale).time;
1177
+ return `${formatNumber(`${time.hour % 12 === 0 ? 12 : time.hour % 12}:${minute}`, options.locale)} ${time.hour < 12 ? am : pm}`;
1178
+ }
1179
+ /**
1180
+ * The instant of a calendar day and time in a zone (Nepal by default), as an
1181
+ * ISO timestamp with that zone's offset: "2026-10-18T14:30:00+05:45".
1182
+ */
1183
+ function toIsoDateTime(date, time, timeZone = getConfig().timeZone) {
1184
+ const { year, month, day } = toCalendar(date, "AD");
1185
+ if (year < 0 || year > 9999) throw new NepDateRangeError("INVALID_ARGUMENT", `AD year ${year} cannot be written in ISO form.`);
1186
+ const wall = Date.UTC(year, month - 1, day, time.hour, time.minute);
1187
+ const offset = offsetMinutes(wall - offsetMinutes(wall, timeZone) * 6e4, timeZone);
1188
+ const sign = offset < 0 ? "-" : "+";
1189
+ const abs = Math.abs(offset);
1190
+ const pad = (value, length = 2) => String(value).padStart(length, "0");
1191
+ return `${pad(year, 4)}-${pad(month)}-${pad(day)}T${pad(time.hour)}:${pad(time.minute)}:00${sign}${pad(Math.floor(abs / 60))}:${pad(abs % 60)}`;
1192
+ }
1193
+ /**
1194
+ * The day and time of an ISO timestamp in a zone (Nepal by default). A value
1195
+ * without an offset ("2026-10-18T14:30", as from <input type="datetime-local">)
1196
+ * is read as a wall time already in that zone. Null for anything else.
1197
+ */
1198
+ function fromIsoDateTime(value, timeZone = getConfig().timeZone) {
1199
+ const zoned = fromIsoTimestamp(value, timeZone);
1200
+ if (zoned) {
1201
+ const time = parseTime(zoned.time);
1202
+ return time ? {
1203
+ date: zoned.date,
1204
+ time
1205
+ } : null;
1206
+ }
1207
+ const local = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2})(?::\d{2}(?:\.\d+)?)?$/.exec(value ?? "");
1208
+ if (!local) return null;
1209
+ try {
1210
+ return {
1211
+ date: adDate(Number(local[1]), Number(local[2]), Number(local[3])),
1212
+ time: timeOfDay(Number(local[4]), Number(local[5]))
1213
+ };
1214
+ } catch {
1215
+ return null;
1216
+ }
1217
+ }
1218
+ /** Minutes the zone is ahead of UTC at an instant. */
1219
+ function offsetMinutes(instant, timeZone) {
1220
+ let parts;
1221
+ try {
1222
+ parts = new Intl.DateTimeFormat("en-US", {
1223
+ timeZone,
1224
+ hourCycle: "h23",
1225
+ year: "numeric",
1226
+ month: "numeric",
1227
+ day: "numeric",
1228
+ hour: "numeric",
1229
+ minute: "numeric"
1230
+ }).formatToParts(new Date(instant));
1231
+ } catch {
1232
+ throw new NepDateRangeError("INVALID_ARGUMENT", `"${timeZone}" is not a time zone.`);
1233
+ }
1234
+ const part = (type) => Number(parts.find((item) => item.type === type)?.value ?? 0);
1235
+ const wall = Date.UTC(part("year"), part("month") - 1, part("day"), part("hour"), part("minute"));
1236
+ return Math.round((wall - Math.floor(instant / 6e4) * 6e4) / 6e4);
1237
+ }
1238
+ //#endregion
1239
+ export { BS_FISCAL_YEAR_START_MONTH, DATE_FORMATS, MAX_AD_YEAR, NepDateDataError, NepDateRangeError, adDate, adToBs, addDays, addMonths, addYears, bsDate, bsFirstYear, bsLastYear, bsToAd, clampDate, compareDates, compareTimes, configure, createDate, dayOfWeek, daysInMonth, differenceInDays, eachDayOfInterval, eachDayOfMonth, endOfMonth, endOfWeek, endOfYear, fiscalYear, fiscalYearRange, formatDate, formatFiscalYear, formatMessage, formatNumber, formatTime, fromEpoch, fromIsoDate, fromIsoDateTime, fromIsoTimestamp, fromJsDate, getBsData, getConfig, getLocale, hasLocale, isAdDate, isAdLeapYear, isAfter, isBefore, isBsDate, isCalendarDate, isConvertible, isInFiscalYear, isProjected, isSameDay, isSameMonth, isSameYear, isValidDate, isValidTime, isWeekend, isWithin, maxDate, minDate, monthNames, now, parseDate, parseDateStrict, parseFiscalYear, parseTime, patchBsYear, registerLocale, resetBsData, resetConfig, setBsData, startOfMonth, startOfWeek, startOfYear, timeOfDay, toAsciiDigits, toCalendar, toDevanagariDigits, toEpoch, toEpochDay, toIsoDate, toIsoDateTime, today, validateBsData, weekdayNames, weekdayOf };
1240
+
1241
+ //# sourceMappingURL=index.js.map