@timestamp-js/core 0.1.0-rc.0 → 0.1.0-rc.2

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 CHANGED
@@ -1,3 +1,5 @@
1
+ import { formatCalendarDate, gregorianCalendar } from './calendar.js';
2
+ export { formatCalendarDate, gregorianCalendar } from './calendar.js';
1
3
  /**
2
4
  * Matches supported date and date-time input.
3
5
  *
@@ -132,7 +134,7 @@ export const SECONDS_IN_DAY = TIME_CONSTANTS.SECONDS_IN.DAY;
132
134
  * instead of mutating this shared default.
133
135
  */
134
136
  export const Timestamp = freezeTimestamp({
135
- date: "",
137
+ date: '',
136
138
  hasDay: false,
137
139
  year: 0,
138
140
  month: 0,
@@ -161,17 +163,46 @@ function freezeTimestamp(timestamp) {
161
163
  function cloneTimestamp(timestamp) {
162
164
  return { ...timestamp };
163
165
  }
166
+ /**
167
+ * Extracts calendar date fields from a timestamp-shaped value.
168
+ *
169
+ * @param timestamp Timestamp or timestamp-like object to read.
170
+ * @returns Plain calendar date fields.
171
+ * @category calendar
172
+ */
173
+ export function toCalendarDateParts(timestamp) {
174
+ return {
175
+ year: timestamp.year,
176
+ month: timestamp.month,
177
+ day: timestamp.day,
178
+ };
179
+ }
180
+ function getCalendarTimestampOptions(timestamp) {
181
+ return {
182
+ hasTime: timestamp.hasTime,
183
+ hour: timestamp.hour,
184
+ minute: timestamp.minute,
185
+ second: timestamp.second,
186
+ millisecond: timestamp.millisecond,
187
+ timezone: timestamp.timezone,
188
+ };
189
+ }
190
+ function getCalendarWorkWeek(date, calendar) {
191
+ const firstWeekday = calendar.getWeekday({ year: date.year, month: 1, day: 1 });
192
+ return Math.floor((calendar.getDayOfYear(date) + firstWeekday - 1) / 7) + 1;
193
+ }
164
194
  function parseMillisecond(value) {
165
- return value === undefined ? undefined : parseInt(value.padEnd(3, "0"), 10);
195
+ return value === undefined ? undefined : parseInt(value.padEnd(3, '0'), 10);
166
196
  }
167
197
  /**
168
198
  * Validates whether an input string matches the supported timestamp grammar.
169
199
  *
170
200
  * @param {string} input A string in the form `YYYY-MM-DD`, `YYYY-MM-DD HH:mm`, or a full ISO-like date time.
171
201
  * @returns {boolean} True if parseable
202
+ * @category validation
172
203
  */
173
204
  export function validateTimestamp(input) {
174
- if (typeof input !== "string")
205
+ if (typeof input !== 'string')
175
206
  return false;
176
207
  return PARSE_DATETIME.test(input);
177
208
  }
@@ -184,18 +215,19 @@ export function validateTimestamp(input) {
184
215
  *
185
216
  * @param {string} input In the form `YYYY-MM-DD`, `YYYY-MM-DD HH:mm:ss`, or an ISO-like date time with optional milliseconds and timezone suffix.
186
217
  * @returns {Timestamp} Minimal Timestamp object, or `null` when the input cannot be parsed.
218
+ * @category parsing
187
219
  */
188
220
  export function parsed(input) {
189
- if (typeof input !== "string")
221
+ if (typeof input !== 'string')
190
222
  return null;
191
223
  const parts = PARSE_DATETIME.exec(input);
192
224
  if (!parts || !parts[1] || !parts[2])
193
225
  return null;
194
226
  const year = parseInt(parts[1], 10);
195
227
  const month = parseInt(parts[2], 10);
196
- const day = parseInt(parts[3] || "1", 10);
197
- const hour = parseInt(parts[4] || "0", 10);
198
- const minute = parseInt(parts[5] || "0", 10);
228
+ const day = parseInt(parts[3] || '1', 10);
229
+ const hour = parseInt(parts[4] || '0', 10);
230
+ const minute = parseInt(parts[5] || '0', 10);
199
231
  const second = parts[6] === undefined ? undefined : parseInt(parts[6], 10);
200
232
  const millisecond = parseMillisecond(parts[7]);
201
233
  const timestamp = {
@@ -232,17 +264,17 @@ function parseDateByMode(date, utc) {
232
264
  return null;
233
265
  if (Number.isNaN(date.getTime()))
234
266
  return null;
235
- const UTC = utc ? "UTC" : "";
267
+ const UTC = utc ? 'UTC' : '';
236
268
  const second = date[`get${UTC}Seconds`]();
237
269
  const millisecond = date[`get${UTC}Milliseconds`]();
238
270
  const timestamp = {
239
271
  date: padNumber(date[`get${UTC}FullYear`](), 4) +
240
- "-" +
272
+ '-' +
241
273
  padNumber(date[`get${UTC}Month`]() + 1, 2) +
242
- "-" +
274
+ '-' +
243
275
  padNumber(date[`get${UTC}Date`](), 2),
244
276
  time: padNumber(date[`get${UTC}Hours`]() || 0, 2) +
245
- ":" +
277
+ ':' +
246
278
  padNumber(date[`get${UTC}Minutes`]() || 0, 2),
247
279
  year: date[`get${UTC}FullYear`](),
248
280
  month: date[`get${UTC}Month`]() + 1,
@@ -275,6 +307,7 @@ function parseDateByMode(date, utc) {
275
307
  *
276
308
  * @param {Date} date JavaScript Date to convert.
277
309
  * @returns {Timestamp} Formatted Timestamp object, or `null` for invalid input.
310
+ * @category parsing
278
311
  */
279
312
  export function parseDate(date) {
280
313
  return parseDateByMode(date, false);
@@ -287,6 +320,7 @@ export function parseDate(date) {
287
320
  *
288
321
  * @param {Date} date JavaScript Date to convert.
289
322
  * @returns {Timestamp} Formatted Timestamp object, or `null` for invalid input.
323
+ * @category parsing
290
324
  */
291
325
  export function parseDateUTC(date) {
292
326
  return parseDateByMode(date, true);
@@ -298,11 +332,12 @@ export function parseDateUTC(date) {
298
332
  * @param {number} x The number to pad
299
333
  * @param {number} length The length of the required number as a string
300
334
  * @returns {string} The padded number (as a string). (ie: 5 = '05')
335
+ * @category formatting
301
336
  */
302
337
  export function padNumber(x, length) {
303
338
  let padded = String(x);
304
339
  while (padded.length < length) {
305
- padded = "0" + padded;
340
+ padded = '0' + padded;
306
341
  }
307
342
  return padded;
308
343
  }
@@ -310,34 +345,35 @@ export function padNumber(x, length) {
310
345
  * Returns if the passed year is a leap year
311
346
  * @param {number} year The year to check (ie: 1999, 2020)
312
347
  * @returns {boolean} True if the year is a leap year
348
+ * @category calendar
313
349
  */
314
350
  export function isLeapYear(year) {
315
- // A year is a Gregorian leap year if it is divisible by 4,
316
- // but not by 100, unless it is also divisible by 400.
317
- return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;
351
+ return gregorianCalendar.isLeapYear(year);
318
352
  }
319
353
  /**
320
354
  * Returns the days of the specified month in a year
321
355
  * @param {number} year The year (ie: 1999, 2020)
322
356
  * @param {number} month The Gregorian month number, where January is `1`
323
357
  * @returns {number} The number of days in the month (corrected for leap years)
358
+ * @category calendar
324
359
  */
325
360
  export function daysInMonth(year, month) {
326
- return (isLeapYear(year) ? DAYS_IN_MONTH_LEAP[month] : DAYS_IN_MONTH[month]);
361
+ return gregorianCalendar.daysInMonth(year, month);
327
362
  }
328
363
  /**
329
364
  * Returns a new Timestamp for the next calendar day.
330
365
  *
331
366
  * @param {Timestamp} timestamp Base Timestamp object.
332
367
  * @returns {Timestamp} New Timestamp representing the next day.
368
+ * @category arithmetic
333
369
  */
334
370
  export function nextDay(timestamp) {
335
- const date = new Date(timestamp.year, timestamp.month - 1, timestamp.day + 1);
371
+ const date = gregorianCalendar.nextDay(toCalendarDateParts(timestamp));
336
372
  return updateFormatted(normalizeTimestamp({
337
373
  ...timestamp,
338
- year: date.getFullYear(),
339
- month: date.getMonth() + 1,
340
- day: date.getDate(),
374
+ year: date.year,
375
+ month: date.month,
376
+ day: date.day,
341
377
  }));
342
378
  }
343
379
  /**
@@ -345,14 +381,15 @@ export function nextDay(timestamp) {
345
381
  *
346
382
  * @param {Timestamp} timestamp Base Timestamp object.
347
383
  * @returns {Timestamp} New Timestamp representing the previous day.
384
+ * @category arithmetic
348
385
  */
349
386
  export function prevDay(timestamp) {
350
- const date = new Date(timestamp.year, timestamp.month - 1, timestamp.day - 1);
387
+ const date = gregorianCalendar.prevDay(toCalendarDateParts(timestamp));
351
388
  return updateFormatted(normalizeTimestamp({
352
389
  ...timestamp,
353
- year: date.getFullYear(),
354
- month: date.getMonth() + 1,
355
- day: date.getDate(),
390
+ year: date.year,
391
+ month: date.month,
392
+ day: date.day,
356
393
  }));
357
394
  }
358
395
  /**
@@ -363,10 +400,11 @@ export function prevDay(timestamp) {
363
400
  * wants a stable UTC calendar date instead.
364
401
  *
365
402
  * @returns {string} Date string in the form `YYYY-MM-DD`
403
+ * @category state
366
404
  */
367
405
  export function today() {
368
406
  const d = new Date(), month = d.getMonth() + 1, day = d.getDate(), year = d.getFullYear();
369
- return [year, padNumber(month, 2), padNumber(day, 2)].join("-");
407
+ return [year, padNumber(month, 2), padNumber(day, 2)].join('-');
370
408
  }
371
409
  /**
372
410
  * Returns today's date using UTC calendar fields.
@@ -377,13 +415,14 @@ export function today() {
377
415
  *
378
416
  * @param {Date} date Date source to read. Defaults to the current Date.
379
417
  * @returns {string} UTC date string in the form `YYYY-MM-DD`
418
+ * @category state
380
419
  */
381
420
  export function todayUTC(date = new Date()) {
382
421
  return [
383
422
  padNumber(date.getUTCFullYear(), 4),
384
423
  padNumber(date.getUTCMonth() + 1, 2),
385
424
  padNumber(date.getUTCDate(), 2),
386
- ].join("-");
425
+ ].join('-');
387
426
  }
388
427
  /**
389
428
  * Returns the current date-time as an immutable Timestamp using UTC fields.
@@ -394,6 +433,7 @@ export function todayUTC(date = new Date()) {
394
433
  *
395
434
  * @param {Date} date Date source to read. Defaults to the current Date.
396
435
  * @returns {Timestamp} Immutable Timestamp built from UTC fields.
436
+ * @category state
397
437
  */
398
438
  export function nowUTC(date = new Date()) {
399
439
  return parseDateUTC(date);
@@ -402,6 +442,7 @@ export function nowUTC(date = new Date()) {
402
442
  * Takes a date string ('YYYY-MM-DD') and validates if it is today's date
403
443
  * @param {string} date Date string in the form 'YYYY-MM-DD'
404
444
  * @returns {boolean} True if the date is today's date
445
+ * @category comparison
405
446
  */
406
447
  export function isToday(date) {
407
448
  return date === today();
@@ -415,6 +456,7 @@ export function isToday(date) {
415
456
  * @param {string} date Date string in the form `YYYY-MM-DD`.
416
457
  * @param {Date} now Date source to read. Defaults to the current Date.
417
458
  * @returns {boolean} True when the date matches the UTC date.
459
+ * @category comparison
418
460
  */
419
461
  export function isTodayUTC(date, now = new Date()) {
420
462
  return date === todayUTC(now);
@@ -426,6 +468,7 @@ export function isTodayUTC(date, now = new Date()) {
426
468
  * @param {number[]} weekdays The array is [0,1,2,3,4,5,6] where 0=Sunday and 6=Saturday
427
469
  * @param {Timestamp=} today Current timestamp used to update relative information
428
470
  * @returns {Timestamp} A new Timestamp representing the start of the week
471
+ * @category ranges
429
472
  */
430
473
  export function getStartOfWeek(timestamp, weekdays, today) {
431
474
  let start = cloneTimestamp(timestamp);
@@ -451,6 +494,7 @@ export function getStartOfWeek(timestamp, weekdays, today) {
451
494
  * @param {number[]} weekdays The array is [0,1,2,3,4,5,6] where 0=Sunday and 6=Saturday
452
495
  * @param {Timestamp=} today Current timestamp used to update relative information
453
496
  * @returns {Timestamp} A new Timestamp representing the end of the week
497
+ * @category ranges
454
498
  */
455
499
  export function getEndOfWeek(timestamp, weekdays, today) {
456
500
  let end = cloneTimestamp(timestamp);
@@ -475,6 +519,7 @@ export function getEndOfWeek(timestamp, weekdays, today) {
475
519
  * Finds the start of the month based on the passed in Timestamp
476
520
  * @param {Timestamp} timestamp The Timestamp to use to find the start of the month
477
521
  * @returns {Timestamp} A Timestamp of the start of the month
522
+ * @category ranges
478
523
  */
479
524
  export function getStartOfMonth(timestamp) {
480
525
  let start = cloneTimestamp(timestamp);
@@ -486,6 +531,7 @@ export function getStartOfMonth(timestamp) {
486
531
  * Finds the end of the month based on the passed in Timestamp
487
532
  * @param {Timestamp} timestamp The Timestamp to use to find the end of the month
488
533
  * @returns {Timestamp} A Timestamp of the end of the month
534
+ * @category ranges
489
535
  */
490
536
  export function getEndOfMonth(timestamp) {
491
537
  let end = cloneTimestamp(timestamp);
@@ -501,29 +547,30 @@ export function getEndOfMonth(timestamp) {
501
547
  *
502
548
  * @param input - Minutes since midnight, a time string, or an object with hour and minute fields.
503
549
  * @returns Minutes since midnight, or `false` when the input cannot be parsed.
550
+ * @category parsing
504
551
  */
505
552
  export function parseTime(input) {
506
553
  const type = Object.prototype.toString.call(input);
507
554
  switch (type) {
508
- case "[object Number]":
555
+ case '[object Number]':
509
556
  // when a number is given, it's minutes since 12:00am
510
557
  return input;
511
- case "[object String]": {
558
+ case '[object String]': {
512
559
  // when a string is given, it's a hh:mm:ss format where seconds are optional, but not used for minute math
513
560
  const parts = PARSE_TIME.exec(input);
514
561
  if (!parts) {
515
562
  return false;
516
563
  }
517
- return parseInt(parts[1], 10) * 60 + parseInt(parts[2] || "0", 10);
564
+ return parseInt(parts[1], 10) * 60 + parseInt(parts[2] || '0', 10);
518
565
  }
519
- case "[object Object]":
566
+ case '[object Object]':
520
567
  // when an object is given, it must have hour and minute
521
- if (typeof input !== "object" ||
522
- typeof input.hour !== "number" ||
523
- typeof input.minute !== "number") {
568
+ if (typeof input !== 'object' ||
569
+ typeof input.hour !== 'number' ||
570
+ typeof input.minute !== 'number') {
524
571
  return false;
525
572
  }
526
- if (typeof input === "object" && "hour" in input && "minute" in input) {
573
+ if (typeof input === 'object' && 'hour' in input && 'minute' in input) {
527
574
  return input.hour * 60 + input.minute;
528
575
  }
529
576
  return false;
@@ -536,6 +583,7 @@ export function parseTime(input) {
536
583
  * @param {Timestamp} ts1 First Timestamp object.
537
584
  * @param {Timestamp} ts2 Second Timestamp object.
538
585
  * @returns {boolean} True when both timestamps match exactly.
586
+ * @category comparison
539
587
  */
540
588
  export function compareTimestamps(ts1, ts2) {
541
589
  if (!ts1 || !ts2)
@@ -555,6 +603,7 @@ export function compareTimestamps(ts1, ts2) {
555
603
  * @param {Timestamp} ts1 First Timestamp object.
556
604
  * @param {Timestamp} ts2 Second Timestamp object.
557
605
  * @returns {boolean} True when both dates are the same.
606
+ * @category comparison
558
607
  */
559
608
  export function compareDate(ts1, ts2) {
560
609
  return getDate(ts1) === getDate(ts2);
@@ -565,6 +614,7 @@ export function compareDate(ts1, ts2) {
565
614
  * @param {Timestamp} ts1 First Timestamp object.
566
615
  * @param {Timestamp} ts2 Second Timestamp object.
567
616
  * @returns {boolean} True when both times are the same.
617
+ * @category comparison
568
618
  */
569
619
  export function compareTime(ts1, ts2) {
570
620
  return getTime(ts1) === getTime(ts2);
@@ -575,6 +625,7 @@ export function compareTime(ts1, ts2) {
575
625
  * @param {Timestamp} ts1 First Timestamp object.
576
626
  * @param {Timestamp} ts2 Second Timestamp object.
577
627
  * @returns {boolean} True when both date-time values are the same.
628
+ * @category comparison
578
629
  */
579
630
  export function compareDateTime(ts1, ts2) {
580
631
  return getDateTime(ts1) === getDateTime(ts2);
@@ -588,6 +639,7 @@ export function compareDateTime(ts1, ts2) {
588
639
  * @param {string} input Date or date-time string, such as `YYYY-MM-DD`, `YYYY-MM-DD HH:mm:ss`, or an ISO-like value with optional milliseconds and timezone suffix.
589
640
  * @param {Timestamp} now Optional Timestamp used to calculate relative flags.
590
641
  * @returns {Timestamp} Formatted Timestamp object, or `null` when the input cannot be parsed.
642
+ * @category parsing
591
643
  */
592
644
  export function parseTimestamp(input, now = null) {
593
645
  let timestamp = parsed(input);
@@ -604,17 +656,418 @@ export function parseTimestamp(input, now = null) {
604
656
  *
605
657
  * @param {Timestamp} timestamp Timestamp object to read.
606
658
  * @returns {number} Numeric date identifier.
659
+ * @category conversion
607
660
  */
608
661
  export function getDayIdentifier(timestamp) {
609
662
  return ((timestamp.year ?? 0) * 100000000 +
610
663
  (timestamp.month ?? 0) * 1000000 +
611
664
  (timestamp.day ?? 0) * 10000);
612
665
  }
666
+ /**
667
+ * Converts a Timestamp date into a stable serial day for a calendar system.
668
+ *
669
+ * The default Gregorian value is the number of UTC days since 1970-01-01.
670
+ * Alternate calendar adapters should map their year/month/day fields to the
671
+ * same serial day space so ranges and comparisons can be calendar-agnostic.
672
+ *
673
+ * @param {Timestamp} timestamp Timestamp object to read.
674
+ * @param {CalendarSystem=} calendar Calendar implementation to use.
675
+ * @returns {number} Stable serial day.
676
+ * @category conversion
677
+ */
678
+ export function getEpochDay(timestamp, calendar = gregorianCalendar) {
679
+ return calendar.toEpochDay(toCalendarDateParts(timestamp));
680
+ }
681
+ /**
682
+ * Converts a Timestamp date into a stable serial-day identifier for a calendar system.
683
+ *
684
+ * This is an alias-friendly helper for calendar views that need sorted keys,
685
+ * range comparisons, or virtualized day rows across different calendar adapters.
686
+ *
687
+ * @param timestamp Timestamp object to read.
688
+ * @param calendar Calendar implementation to use.
689
+ * @returns Stable serial day.
690
+ * @category calendar
691
+ */
692
+ export function getCalendarDayIdentifier(timestamp, calendar = gregorianCalendar) {
693
+ return getEpochDay(timestamp, calendar);
694
+ }
695
+ /**
696
+ * Returns true when calendar date fields are valid for a calendar system.
697
+ *
698
+ * @param date Calendar date fields to validate.
699
+ * @param calendar Calendar implementation to use.
700
+ * @returns True when year, month, and day can exist in the calendar.
701
+ * @category calendar
702
+ */
703
+ export function isValidCalendarDate(date, calendar = gregorianCalendar) {
704
+ if (date.year < 1)
705
+ return false;
706
+ if (date.month < 1 || date.month > calendar.monthsInYear(date.year))
707
+ return false;
708
+ return date.day >= 1 && date.day <= calendar.daysInMonth(date.year, date.month);
709
+ }
710
+ /**
711
+ * Creates an immutable Timestamp-shaped value from calendar adapter fields.
712
+ *
713
+ * The returned timestamp uses adapter year/month/day fields, is tagged with
714
+ * `calendarId`, and derives weekday/day-of-year values from the adapter.
715
+ *
716
+ * @param date Calendar date fields.
717
+ * @param calendar Calendar implementation to use.
718
+ * @param options Optional time and relative comparison settings.
719
+ * @returns Timestamp-shaped calendar value.
720
+ * @category calendar
721
+ */
722
+ export function createCalendarTimestamp(date, calendar = gregorianCalendar, options = {}) {
723
+ const timestamp = {
724
+ calendarId: calendar.id,
725
+ date: formatCalendarDate(date),
726
+ hasDay: true,
727
+ year: date.year,
728
+ month: date.month,
729
+ day: date.day,
730
+ hasTime: options.hasTime ?? true,
731
+ hour: options.hour ?? 0,
732
+ minute: options.minute ?? 0,
733
+ past: false,
734
+ current: false,
735
+ future: false,
736
+ disabled: false,
737
+ weekday: calendar.getWeekday(date),
738
+ doy: calendar.getDayOfYear(date),
739
+ workweek: getCalendarWorkWeek(date, calendar),
740
+ };
741
+ if (options.second !== undefined) {
742
+ timestamp.second = options.second;
743
+ }
744
+ if (options.millisecond !== undefined) {
745
+ timestamp.millisecond = options.millisecond;
746
+ }
747
+ if (options.timezone !== undefined) {
748
+ timestamp.timezone = options.timezone;
749
+ }
750
+ timestamp.time = getTime(timestamp);
751
+ const formatted = freezeTimestamp(timestamp);
752
+ return options.now
753
+ ? updateCalendarRelative(formatted, options.now, calendar, formatted.hasTime)
754
+ : formatted;
755
+ }
756
+ /**
757
+ * Parses a date or date-time string as calendar adapter fields.
758
+ *
759
+ * Unlike parseTimestamp(), this helper validates the date against the supplied
760
+ * calendar and derives weekday/day-of-year values through the adapter.
761
+ *
762
+ * @param input Date or date-time string.
763
+ * @param calendar Calendar implementation to use.
764
+ * @param now Optional comparison timestamp from the same calendar system.
765
+ * @returns Calendar timestamp, or `null` when the input cannot be parsed or validated.
766
+ * @category calendar
767
+ */
768
+ export function parseCalendarTimestamp(input, calendar = gregorianCalendar, now = null) {
769
+ const timestamp = parsed(input);
770
+ if (timestamp === null)
771
+ return null;
772
+ const date = toCalendarDateParts(timestamp);
773
+ if (isValidCalendarDate(date, calendar) === false)
774
+ return null;
775
+ return createCalendarTimestamp(date, calendar, {
776
+ ...getCalendarTimestampOptions(timestamp),
777
+ now,
778
+ });
779
+ }
780
+ /**
781
+ * Creates a calendar timestamp from a stable serial day.
782
+ *
783
+ * @param epochDay Stable serial day.
784
+ * @param calendar Calendar implementation to use.
785
+ * @param options Optional time and relative comparison settings.
786
+ * @returns Timestamp-shaped calendar value.
787
+ * @category calendar
788
+ */
789
+ export function createCalendarTimestampFromEpochDay(epochDay, calendar = gregorianCalendar, options = {}) {
790
+ return createCalendarTimestamp(calendar.fromEpochDay(epochDay), calendar, options);
791
+ }
792
+ /**
793
+ * Updates formatted calendar metadata on a timestamp-shaped value.
794
+ *
795
+ * @param timestamp Timestamp object to transform.
796
+ * @param calendar Calendar implementation to use.
797
+ * @returns Timestamp with adapter date formatting and metadata.
798
+ * @category calendar
799
+ */
800
+ export function updateCalendarFormatted(timestamp, calendar = gregorianCalendar) {
801
+ return createCalendarTimestamp(toCalendarDateParts(timestamp), calendar, getCalendarTimestampOptions(timestamp));
802
+ }
803
+ /**
804
+ * Returns a timestamp with relative flags compared through a calendar adapter.
805
+ *
806
+ * @param timestamp Timestamp object to update.
807
+ * @param now Timestamp representing the comparison point in the same calendar.
808
+ * @param calendar Calendar implementation to use.
809
+ * @param time Include time-of-day in the comparison when true.
810
+ * @returns New Timestamp object with relative flags.
811
+ * @category calendar
812
+ */
813
+ export function updateCalendarRelative(timestamp, now, calendar = gregorianCalendar, time = false) {
814
+ const ts = cloneTimestamp(timestamp);
815
+ let a = getCalendarDayIdentifier(now, calendar);
816
+ let b = getCalendarDayIdentifier(ts, calendar);
817
+ let current = a === b;
818
+ if (ts.hasTime && time && current) {
819
+ a = getTimeComparisonValue(now);
820
+ b = getTimeComparisonValue(ts);
821
+ current = a === b;
822
+ }
823
+ ts.past = b < a;
824
+ ts.current = current;
825
+ ts.future = b > a;
826
+ ts.currentWeekday = ts.weekday === now.weekday;
827
+ return freezeTimestamp(ts);
828
+ }
829
+ /**
830
+ * Returns a calendar timestamp for the next day.
831
+ *
832
+ * @param timestamp Base timestamp.
833
+ * @param calendar Calendar implementation to use.
834
+ * @returns New timestamp for the next adapter day.
835
+ * @category calendar
836
+ */
837
+ export function nextCalendarDay(timestamp, calendar = gregorianCalendar) {
838
+ return createCalendarTimestamp(calendar.nextDay(toCalendarDateParts(timestamp)), calendar, getCalendarTimestampOptions(timestamp));
839
+ }
840
+ /**
841
+ * Returns a calendar timestamp for the previous day.
842
+ *
843
+ * @param timestamp Base timestamp.
844
+ * @param calendar Calendar implementation to use.
845
+ * @returns New timestamp for the previous adapter day.
846
+ * @category calendar
847
+ */
848
+ export function prevCalendarDay(timestamp, calendar = gregorianCalendar) {
849
+ return createCalendarTimestamp(calendar.prevDay(toCalendarDateParts(timestamp)), calendar, getCalendarTimestampOptions(timestamp));
850
+ }
851
+ /**
852
+ * Adds a number of days through a calendar adapter.
853
+ *
854
+ * @param timestamp Base timestamp.
855
+ * @param amount Number of days to move.
856
+ * @param calendar Calendar implementation to use.
857
+ * @returns New timestamp after moving.
858
+ * @category calendar
859
+ */
860
+ export function addCalendarDays(timestamp, amount, calendar = gregorianCalendar) {
861
+ return createCalendarTimestamp(calendar.addDays(toCalendarDateParts(timestamp), amount), calendar, getCalendarTimestampOptions(timestamp));
862
+ }
863
+ /**
864
+ * Adds a number of months through a calendar adapter and clamps invalid days.
865
+ *
866
+ * @param timestamp Base timestamp.
867
+ * @param amount Number of calendar months to move.
868
+ * @param calendar Calendar implementation to use.
869
+ * @returns New timestamp after moving.
870
+ * @category calendar
871
+ */
872
+ export function addCalendarMonths(timestamp, amount, calendar = gregorianCalendar) {
873
+ let { year, month, day } = toCalendarDateParts(timestamp);
874
+ let remaining = Math.trunc(amount);
875
+ while (remaining > 0) {
876
+ month += 1;
877
+ if (month > calendar.monthsInYear(year)) {
878
+ year += 1;
879
+ month = 1;
880
+ }
881
+ remaining -= 1;
882
+ }
883
+ while (remaining < 0) {
884
+ month -= 1;
885
+ if (month < 1) {
886
+ year -= 1;
887
+ month = calendar.monthsInYear(year);
888
+ }
889
+ remaining += 1;
890
+ }
891
+ day = Math.min(day, calendar.daysInMonth(year, month));
892
+ return createCalendarTimestamp({ year, month, day }, calendar, getCalendarTimestampOptions(timestamp));
893
+ }
894
+ /**
895
+ * Adds a number of years through a calendar adapter and clamps invalid days.
896
+ *
897
+ * @param timestamp Base timestamp.
898
+ * @param amount Number of calendar years to move.
899
+ * @param calendar Calendar implementation to use.
900
+ * @returns New timestamp after moving.
901
+ * @category calendar
902
+ */
903
+ export function addCalendarYears(timestamp, amount, calendar = gregorianCalendar) {
904
+ let { year, month, day } = toCalendarDateParts(timestamp);
905
+ year += Math.trunc(amount);
906
+ month = Math.min(month, calendar.monthsInYear(year));
907
+ day = Math.min(day, calendar.daysInMonth(year, month));
908
+ return createCalendarTimestamp({ year, month, day }, calendar, getCalendarTimestampOptions(timestamp));
909
+ }
910
+ /**
911
+ * Returns the start of the calendar month for a timestamp.
912
+ *
913
+ * @param timestamp Base timestamp.
914
+ * @param calendar Calendar implementation to use.
915
+ * @returns First day of the adapter month.
916
+ * @category calendar
917
+ */
918
+ export function getCalendarStartOfMonth(timestamp, calendar = gregorianCalendar) {
919
+ return createCalendarTimestamp({ ...toCalendarDateParts(timestamp), day: 1 }, calendar, getCalendarTimestampOptions(timestamp));
920
+ }
921
+ /**
922
+ * Returns the end of the calendar month for a timestamp.
923
+ *
924
+ * @param timestamp Base timestamp.
925
+ * @param calendar Calendar implementation to use.
926
+ * @returns Last day of the adapter month.
927
+ * @category calendar
928
+ */
929
+ export function getCalendarEndOfMonth(timestamp, calendar = gregorianCalendar) {
930
+ const date = toCalendarDateParts(timestamp);
931
+ return createCalendarTimestamp({
932
+ ...date,
933
+ day: calendar.daysInMonth(date.year, date.month),
934
+ }, calendar, getCalendarTimestampOptions(timestamp));
935
+ }
936
+ /**
937
+ * Returns the start of the calendar year for a timestamp.
938
+ *
939
+ * @param timestamp Base timestamp.
940
+ * @param calendar Calendar implementation to use.
941
+ * @returns First day of the adapter year.
942
+ * @category calendar
943
+ */
944
+ export function getCalendarStartOfYear(timestamp, calendar = gregorianCalendar) {
945
+ return createCalendarTimestamp({ year: timestamp.year, month: 1, day: 1 }, calendar, getCalendarTimestampOptions(timestamp));
946
+ }
947
+ /**
948
+ * Returns the end of the calendar year for a timestamp.
949
+ *
950
+ * @param timestamp Base timestamp.
951
+ * @param calendar Calendar implementation to use.
952
+ * @returns Last day of the adapter year.
953
+ * @category calendar
954
+ */
955
+ export function getCalendarEndOfYear(timestamp, calendar = gregorianCalendar) {
956
+ const month = calendar.monthsInYear(timestamp.year);
957
+ return createCalendarTimestamp({ year: timestamp.year, month, day: calendar.daysInMonth(timestamp.year, month) }, calendar, getCalendarTimestampOptions(timestamp));
958
+ }
959
+ /**
960
+ * Finds the nearest matching weekday through a calendar adapter.
961
+ *
962
+ * @param timestamp Base timestamp.
963
+ * @param weekday Weekday number to find.
964
+ * @param calendar Calendar implementation to use.
965
+ * @param direction Direction to search.
966
+ * @param maxDays Maximum days to inspect.
967
+ * @returns Matching timestamp, or the last inspected timestamp.
968
+ * @category calendar
969
+ */
970
+ export function findCalendarWeekday(timestamp, weekday, calendar = gregorianCalendar, direction = 'next', maxDays = 6) {
971
+ let ts = copyTimestamp(timestamp);
972
+ const mover = direction === 'next' ? nextCalendarDay : prevCalendarDay;
973
+ while (ts.weekday !== weekday && --maxDays >= 0) {
974
+ ts = mover(ts, calendar);
975
+ }
976
+ return ts;
977
+ }
978
+ /**
979
+ * Returns the start of a calendar week for a timestamp and weekday set.
980
+ *
981
+ * @param timestamp Base timestamp.
982
+ * @param weekdays Weekday numbers to include, from `0` Sunday to `6` Saturday.
983
+ * @param calendar Calendar implementation to use.
984
+ * @param now Optional comparison timestamp from the same calendar.
985
+ * @returns Start of the adapter week.
986
+ * @category calendar
987
+ */
988
+ export function getCalendarStartOfWeek(timestamp, weekdays, calendar = gregorianCalendar, now = null) {
989
+ let start = updateCalendarFormatted(timestamp, calendar);
990
+ if (!weekdays || !Array.isArray(weekdays)) {
991
+ return start;
992
+ }
993
+ if (start.day === 1 || start.weekday === 0) {
994
+ while (!weekdays.includes(Number(start.weekday))) {
995
+ start = nextCalendarDay(start, calendar);
996
+ }
997
+ }
998
+ start = findCalendarWeekday(start, weekdays[0], calendar, 'prev');
999
+ return now ? updateCalendarRelative(start, now, calendar, start.hasTime) : start;
1000
+ }
1001
+ /**
1002
+ * Returns the end of a calendar week for a timestamp and weekday set.
1003
+ *
1004
+ * @param timestamp Base timestamp.
1005
+ * @param weekdays Weekday numbers to include, from `0` Sunday to `6` Saturday.
1006
+ * @param calendar Calendar implementation to use.
1007
+ * @param now Optional comparison timestamp from the same calendar.
1008
+ * @returns End of the adapter week.
1009
+ * @category calendar
1010
+ */
1011
+ export function getCalendarEndOfWeek(timestamp, weekdays, calendar = gregorianCalendar, now = null) {
1012
+ let end = updateCalendarFormatted(timestamp, calendar);
1013
+ if (!weekdays || !Array.isArray(weekdays)) {
1014
+ return end;
1015
+ }
1016
+ const lastDay = calendar.daysInMonth(end.year, end.month);
1017
+ if (lastDay === end.day || end.weekday === weekdays[weekdays.length - 1]) {
1018
+ while (!weekdays.includes(Number(end.weekday))) {
1019
+ end = prevCalendarDay(end, calendar);
1020
+ }
1021
+ }
1022
+ end = findCalendarWeekday(end, weekdays[weekdays.length - 1], calendar, 'next');
1023
+ return now ? updateCalendarRelative(end, now, calendar, end.hasTime) : end;
1024
+ }
1025
+ /**
1026
+ * Creates an inclusive list of calendar adapter days between start and end.
1027
+ *
1028
+ * @param start First day in the list.
1029
+ * @param end Last day boundary for the list.
1030
+ * @param now Timestamp used to calculate relative flags.
1031
+ * @param calendar Calendar implementation to use.
1032
+ * @param options Optional weekday, disabled, and size filters.
1033
+ * @returns Timestamp days for the adapter calendar.
1034
+ * @category calendar
1035
+ */
1036
+ export function createCalendarDayList(start, end, now, calendar = gregorianCalendar, options = {}) {
1037
+ const { weekdays = [0, 1, 2, 3, 4, 5, 6], disabledBefore, disabledAfter, disabledWeekdays = [], disabledDays = [], max = 42, min = 0, } = options;
1038
+ const begin = getCalendarDayIdentifier(start, calendar);
1039
+ const stop = getCalendarDayIdentifier(end, calendar);
1040
+ const days = [];
1041
+ let current = updateCalendarFormatted(start, calendar);
1042
+ let currentIdentifier = 0;
1043
+ let stopped = currentIdentifier === stop;
1044
+ if (stop < begin) {
1045
+ return days;
1046
+ }
1047
+ while ((!stopped || days.length < min) && days.length < max) {
1048
+ currentIdentifier = getCalendarDayIdentifier(current, calendar);
1049
+ stopped = stopped || (currentIdentifier > stop && days.length >= min);
1050
+ if (stopped) {
1051
+ break;
1052
+ }
1053
+ if (!weekdays.includes(Number(current.weekday))) {
1054
+ current = nextCalendarDay(current, calendar);
1055
+ continue;
1056
+ }
1057
+ let day = updateCalendarFormatted(current, calendar);
1058
+ day = updateCalendarRelative(day, now, calendar);
1059
+ day = updateDisabled(day, disabledBefore, disabledAfter, disabledWeekdays, disabledDays);
1060
+ days.push(day);
1061
+ current = nextCalendarDay(current, calendar);
1062
+ }
1063
+ return days;
1064
+ }
613
1065
  /**
614
1066
  * Converts a Timestamp time into a sortable numeric identifier.
615
1067
  *
616
1068
  * @param {Timestamp} timestamp Timestamp object to read.
617
1069
  * @returns {number} Numeric time identifier.
1070
+ * @category conversion
618
1071
  */
619
1072
  export function getTimeIdentifier(timestamp) {
620
1073
  return (timestamp.hour ?? 0) * 100 + (timestamp.minute ?? 0);
@@ -631,6 +1084,7 @@ function getTimeComparisonValue(timestamp) {
631
1084
  *
632
1085
  * @param {Timestamp} timestamp Timestamp object to read.
633
1086
  * @returns {number} Numeric date-time identifier.
1087
+ * @category conversion
634
1088
  */
635
1089
  export function getDayTimeIdentifier(timestamp) {
636
1090
  return getDayIdentifier(timestamp) + getTimeIdentifier(timestamp);
@@ -641,6 +1095,7 @@ export function getDayTimeIdentifier(timestamp) {
641
1095
  * @param {Timestamp} ts2 The second Timestamp
642
1096
  * @param {boolean=} strict Optional flag to not to return negative numbers
643
1097
  * @returns {number} The difference
1098
+ * @category arithmetic
644
1099
  */
645
1100
  export function diffTimestamp(ts1, ts2, strict = false) {
646
1101
  const utc1 = Date.UTC(ts1.year ?? 0, (ts1.month ?? 1) - 1, ts1.day ?? 1, ts1.hour ?? 0, ts1.minute ?? 0, ts1.second ?? 0, ts1.millisecond ?? 0);
@@ -663,6 +1118,7 @@ export function diffTimestamp(ts1, ts2, strict = false) {
663
1118
  * @param {Timestamp} now Timestamp representing the comparison point.
664
1119
  * @param {boolean=} time Include time-of-day in the comparison when true.
665
1120
  * @returns {Timestamp} New Timestamp object with relative flags.
1121
+ * @category state
666
1122
  */
667
1123
  export function updateRelative(timestamp, now, time = false) {
668
1124
  const ts = cloneTimestamp(timestamp);
@@ -690,6 +1146,7 @@ export function updateRelative(timestamp, now, time = false) {
690
1146
  * @param {number} minutes The number of minutes to set from midnight
691
1147
  * @param {Timestamp=} now Optional Timestamp representing current date and time
692
1148
  * @returns {Timestamp} A new Timestamp
1149
+ * @category arithmetic
693
1150
  */
694
1151
  export function updateMinutes(timestamp, minutes, now = null) {
695
1152
  let ts = cloneTimestamp(timestamp);
@@ -708,6 +1165,7 @@ export function updateMinutes(timestamp, minutes, now = null) {
708
1165
  * Updates the Timestamp with the weekday
709
1166
  * @param {Timestamp} timestamp The Timestamp to transform
710
1167
  * @returns A new Timestamp
1168
+ * @category formatting
711
1169
  */
712
1170
  export function updateWeekday(timestamp) {
713
1171
  const ts = cloneTimestamp(timestamp);
@@ -718,6 +1176,7 @@ export function updateWeekday(timestamp) {
718
1176
  * Updates the Timestamp with the day of the year (doy)
719
1177
  * @param {Timestamp} timestamp The Timestamp to transform
720
1178
  * @returns A new Timestamp
1179
+ * @category formatting
721
1180
  */
722
1181
  export function updateDayOfYear(timestamp) {
723
1182
  const ts = cloneTimestamp(timestamp);
@@ -728,6 +1187,7 @@ export function updateDayOfYear(timestamp) {
728
1187
  * Updates the Timestamp with the workweek
729
1188
  * @param {Timestamp} timestamp The Timestamp to transform
730
1189
  * @returns A new Timestamp
1190
+ * @category formatting
731
1191
  */
732
1192
  export function updateWorkWeek(timestamp) {
733
1193
  const ts = cloneTimestamp(timestamp);
@@ -735,7 +1195,7 @@ export function updateWorkWeek(timestamp) {
735
1195
  return freezeTimestamp(ts);
736
1196
  }
737
1197
  function isDisabledDayConfig(value) {
738
- return typeof value === "object" && value !== null && Array.isArray(value) === false;
1198
+ return typeof value === 'object' && value !== null && Array.isArray(value) === false;
739
1199
  }
740
1200
  function applyDisabledDayConfig(timestamp, config) {
741
1201
  timestamp.disabled = true;
@@ -787,6 +1247,7 @@ function isTimestampInDisabledDay(timestamp, day) {
787
1247
  * @param {number[]} [disabledWeekdays] An array of numbers representing weekdays [0 = Sun, ..., 6 = Sat]
788
1248
  * @param {DisabledDays} [disabledDays] An array of days in 'YYYY-MM-DD' format. If an array with a pair of dates is in first array, then this is treated as a range. Object entries can include date/from/to plus color metadata.
789
1249
  * @returns A new Timestamp
1250
+ * @category state
790
1251
  */
791
1252
  export function updateDisabled(timestamp, disabledBefore, disabledAfter, disabledWeekdays, disabledDays) {
792
1253
  let ts = cloneTimestamp(timestamp);
@@ -831,6 +1292,7 @@ export function updateDisabled(timestamp, disabledBefore, disabledAfter, disable
831
1292
  * Updates the passed Timestamp with formatted data (time string, date string, weekday, day of year and workweek)
832
1293
  * @param {Timestamp} timestamp The Timestamp to transform
833
1294
  * @returns A new Timestamp
1295
+ * @category formatting
834
1296
  */
835
1297
  export function updateFormatted(timestamp) {
836
1298
  const ts = cloneTimestamp(timestamp);
@@ -846,21 +1308,18 @@ export function updateFormatted(timestamp) {
846
1308
  * Returns day of the year (doy) for the passed in Timestamp
847
1309
  * @param {Timestamp} timestamp The Timestamp to use
848
1310
  * @returns {number} The day of the year
1311
+ * @category formatting
849
1312
  */
850
1313
  export function getDayOfYear(timestamp) {
851
1314
  if (timestamp.year === 0)
852
1315
  return;
853
- return ((Date.UTC(timestamp.year, timestamp.month - 1, timestamp.day) -
854
- Date.UTC(timestamp.year, 0, 0)) /
855
- 24 /
856
- 60 /
857
- 60 /
858
- 1000);
1316
+ return gregorianCalendar.getDayOfYear(toCalendarDateParts(timestamp));
859
1317
  }
860
1318
  /**
861
1319
  * Returns workweek for the passed in Timestamp
862
1320
  * @param {Timestamp} timestamp The Timestamp to use
863
1321
  * @returns {number} The work week
1322
+ * @category formatting
864
1323
  */
865
1324
  export function getWorkWeek(timestamp) {
866
1325
  let ts = timestamp;
@@ -888,25 +1347,12 @@ export function getWorkWeek(timestamp) {
888
1347
  * Returns weekday for the passed in Timestamp
889
1348
  * @param {Timestamp} timestamp The Timestamp to use
890
1349
  * @returns {number} The weekday
1350
+ * @category formatting
891
1351
  */
892
1352
  export function getWeekday(timestamp) {
893
1353
  let weekday = timestamp.weekday;
894
1354
  if (timestamp.hasDay) {
895
- const floor = Math.floor;
896
- const day = timestamp.day;
897
- const month = ((timestamp.month + 9) % MONTH_MAX) + 1;
898
- const century = floor(timestamp.year / 100);
899
- const year = (timestamp.year % 100) - (timestamp.month <= 2 ? 1 : 0);
900
- weekday =
901
- (((day +
902
- floor(2.6 * month - 0.2) -
903
- 2 * century +
904
- year +
905
- floor(year / 4) +
906
- floor(century / 4)) %
907
- 7) +
908
- 7) %
909
- 7;
1355
+ weekday = gregorianCalendar.getWeekday(toCalendarDateParts(timestamp));
910
1356
  }
911
1357
  return weekday ?? 0;
912
1358
  }
@@ -915,6 +1361,7 @@ export function getWeekday(timestamp) {
915
1361
  *
916
1362
  * @param {Timestamp} timestamp Timestamp object to copy.
917
1363
  * @returns {Timestamp} Frozen Timestamp copy.
1364
+ * @category state
918
1365
  */
919
1366
  export function copyTimestamp(timestamp) {
920
1367
  return freezeTimestamp(timestamp);
@@ -943,6 +1390,7 @@ function setTimeParts(timestamp, hour, minute, second, millisecond) {
943
1390
  *
944
1391
  * @param {Timestamp} timestamp Timestamp object to transform.
945
1392
  * @returns {Timestamp} New Timestamp at `00:00`.
1393
+ * @category ranges
946
1394
  */
947
1395
  export function getStartOfDay(timestamp) {
948
1396
  return setTimeParts(timestamp, 0, 0);
@@ -952,6 +1400,7 @@ export function getStartOfDay(timestamp) {
952
1400
  *
953
1401
  * @param {Timestamp} timestamp Timestamp object to transform.
954
1402
  * @returns {Timestamp} New Timestamp at `23:59:59.999`.
1403
+ * @category ranges
955
1404
  */
956
1405
  export function getEndOfDay(timestamp) {
957
1406
  return setTimeParts(timestamp, 23, 59, 59, 999);
@@ -961,6 +1410,7 @@ export function getEndOfDay(timestamp) {
961
1410
  *
962
1411
  * @param {Timestamp} timestamp Timestamp object to transform.
963
1412
  * @returns {Timestamp} New Timestamp for January 1 at `00:00`.
1413
+ * @category ranges
964
1414
  */
965
1415
  export function getStartOfYear(timestamp) {
966
1416
  const ts = cloneTimestamp(timestamp);
@@ -973,6 +1423,7 @@ export function getStartOfYear(timestamp) {
973
1423
  *
974
1424
  * @param {Timestamp} timestamp Timestamp object to transform.
975
1425
  * @returns {Timestamp} New Timestamp for December 31 at `23:59:59.999`.
1426
+ * @category ranges
976
1427
  */
977
1428
  export function getEndOfYear(timestamp) {
978
1429
  const ts = cloneTimestamp(timestamp);
@@ -985,6 +1436,7 @@ export function getEndOfYear(timestamp) {
985
1436
  *
986
1437
  * @param {Timestamp} timestamp Timestamp object to format.
987
1438
  * @returns {string} Date string such as `YYYY-MM-DD`.
1439
+ * @category conversion
988
1440
  */
989
1441
  export function getDate(timestamp) {
990
1442
  let str = `${padNumber(timestamp.year, 4)}-${padNumber(timestamp.month, 2)}`;
@@ -1000,10 +1452,11 @@ export function getDate(timestamp) {
1000
1452
  *
1001
1453
  * @param {Timestamp} timestamp Timestamp object to format.
1002
1454
  * @returns {string} Time string, or an empty string when the timestamp has no time.
1455
+ * @category conversion
1003
1456
  */
1004
1457
  export function getTime(timestamp) {
1005
1458
  if (!timestamp.hasTime) {
1006
- return "";
1459
+ return '';
1007
1460
  }
1008
1461
  let time = `${padNumber(timestamp.hour, 2)}:${padNumber(timestamp.minute, 2)}`;
1009
1462
  if (timestamp.second !== undefined || timestamp.millisecond !== undefined) {
@@ -1019,9 +1472,10 @@ export function getTime(timestamp) {
1019
1472
  *
1020
1473
  * @param {Timestamp} timestamp Timestamp object to format.
1021
1474
  * @returns {string} Date-time string such as `YYYY-MM-DD HH:mm`.
1475
+ * @category conversion
1022
1476
  */
1023
1477
  export function getDateTime(timestamp) {
1024
- return getDate(timestamp) + " " + (timestamp.hasTime ? getTime(timestamp) : "00:00");
1478
+ return getDate(timestamp) + ' ' + (timestamp.hasTime ? getTime(timestamp) : '00:00');
1025
1479
  }
1026
1480
  /**
1027
1481
  * Alias for relativeDays.
@@ -1030,6 +1484,7 @@ export function getDateTime(timestamp) {
1030
1484
  * @param {number} [days=1] The number of days to move.
1031
1485
  * @param {number[]} [allowedWeekdays=[ 0, 1, 2, 3, 4, 5, 6 ]] An array of numbers representing the weekdays. ie: [0 = Sun, ..., 6 = Sat].
1032
1486
  * @returns A new Timestamp
1487
+ * @category ranges
1033
1488
  */
1034
1489
  export function moveRelativeDays(timestamp, mover = nextDay, days = 1, allowedWeekdays = [0, 1, 2, 3, 4, 5, 6]) {
1035
1490
  return relativeDays(timestamp, mover, days, allowedWeekdays);
@@ -1041,6 +1496,7 @@ export function moveRelativeDays(timestamp, mover = nextDay, days = 1, allowedWe
1041
1496
  * @param {number} [days=1] The number of days to move.
1042
1497
  * @param {number[]} [allowedWeekdays=[ 0, 1, 2, 3, 4, 5, 6 ]] An array of numbers representing the weekdays. ie: [0 = Sun, ..., 6 = Sat].
1043
1498
  * @returns A new Timestamp
1499
+ * @category ranges
1044
1500
  */
1045
1501
  export function relativeDays(timestamp, mover = nextDay, days = 1, allowedWeekdays = [0, 1, 2, 3, 4, 5, 6]) {
1046
1502
  let ts = copyTimestamp(timestamp);
@@ -1062,6 +1518,7 @@ export function relativeDays(timestamp, mover = nextDay, days = 1, allowedWeekda
1062
1518
  * @param {function} [mover=nextDay] The function to use ({prevDay} or {nextDay}).
1063
1519
  * @param {number} [maxDays=6] The number of days to look forward or back.
1064
1520
  * @returns A new Timestamp
1521
+ * @category ranges
1065
1522
  */
1066
1523
  export function findWeekday(timestamp, weekday, mover = nextDay, maxDays = 6) {
1067
1524
  let ts = copyTimestamp(timestamp);
@@ -1086,6 +1543,7 @@ export function findWeekday(timestamp, weekday, mover = nextDay, maxDays = 6) {
1086
1543
  * @param {number} [max=42] Maximum number of days to return.
1087
1544
  * @param {number} [min=0] Minimum number of days to return.
1088
1545
  * @returns {Timestamp[]} Timestamp days.
1546
+ * @category ranges
1089
1547
  */
1090
1548
  export function createDayList(start, end, now, weekdays = [0, 1, 2, 3, 4, 5, 6], disabledBefore = undefined, disabledAfter = undefined, disabledWeekdays = [], disabledDays = [], max = 42, min = 0) {
1091
1549
  const begin = getDayIdentifier(start);
@@ -1125,6 +1583,7 @@ export function createDayList(start, end, now, weekdays = [0, 1, 2, 3, 4, 5, 6],
1125
1583
  * @param {number} count Number of intervals to create.
1126
1584
  * @param {Timestamp} now Timestamp used to calculate relative flags.
1127
1585
  * @returns {Timestamp[]} Interval Timestamp objects.
1586
+ * @category ranges
1128
1587
  */
1129
1588
  export function createIntervalList(timestamp, first, minutes, count, now) {
1130
1589
  const intervals = [];
@@ -1147,8 +1606,8 @@ export function createIntervalList(timestamp, first, minutes, count, now) {
1147
1606
  * @returns {string} The localized string of the formatted Timestamp
1148
1607
  */
1149
1608
  function createNativeLocaleFormatterByMode(locale, cb, toDate) {
1150
- const emptyFormatter = () => "";
1151
- if (typeof Intl === "undefined" || typeof Intl.DateTimeFormat === "undefined") {
1609
+ const emptyFormatter = () => '';
1610
+ if (typeof Intl === 'undefined' || typeof Intl.DateTimeFormat === 'undefined') {
1152
1611
  return emptyFormatter;
1153
1612
  }
1154
1613
  return (timestamp, short) => {
@@ -1158,7 +1617,7 @@ function createNativeLocaleFormatterByMode(locale, cb, toDate) {
1158
1617
  }
1159
1618
  catch (e) {
1160
1619
  console.error(`Intl.DateTimeFormat: ${e.message} -> ${getDateTime(timestamp)}`);
1161
- return "";
1620
+ return '';
1162
1621
  }
1163
1622
  };
1164
1623
  }
@@ -1175,6 +1634,7 @@ function createNativeLocaleFormatterByMode(locale, cb, toDate) {
1175
1634
  * @param {string} locale The locale to use (ie: en-US)
1176
1635
  * @param {getOptions} cb The function to call for options. This function should return an Intl formatted object. The function is passed (timestamp, short).
1177
1636
  * @returns {formatter} The function has params (timestamp, short). The short is to use the short options.
1637
+ * @category locale
1178
1638
  */
1179
1639
  export function createNativeLocaleFormatter(locale, cb) {
1180
1640
  return createNativeLocaleFormatterByMode(locale, cb, makeDateTime);
@@ -1189,15 +1649,50 @@ export function createNativeLocaleFormatter(locale, cb) {
1189
1649
  * @param {string} locale The locale to use (ie: en-US)
1190
1650
  * @param {getOptions} cb The function to call for options. This function should return an Intl formatted object. The function is passed (timestamp, short).
1191
1651
  * @returns {formatter} The function has params (timestamp, short). The short is to use the short options.
1652
+ * @category locale
1192
1653
  */
1193
1654
  export function createNativeLocaleFormatterUTC(locale, cb) {
1194
1655
  return createNativeLocaleFormatterByMode(locale, cb, makeDateTimeUTC);
1195
1656
  }
1657
+ /**
1658
+ * Returns a UTC locale formatter for a calendar adapter.
1659
+ *
1660
+ * The formatter converts adapter date fields through the adapter's serial day
1661
+ * before constructing the native Date. When the adapter has an Intl calendar id,
1662
+ * it is supplied to Intl.DateTimeFormat unless the caller already provided a
1663
+ * `calendar` option. The helper supplies `timeZone: "UTC"` unless the caller
1664
+ * provides a different timezone.
1665
+ *
1666
+ * @param calendar Calendar implementation to use.
1667
+ * @param locale The locale to use.
1668
+ * @param cb Callback that returns Intl formatting options.
1669
+ * @returns Function that formats a calendar timestamp.
1670
+ * @category calendar
1671
+ */
1672
+ export function createCalendarLocaleFormatterUTC(calendar, locale, cb) {
1673
+ return createNativeLocaleFormatterByMode(locale, (timestamp, short) => {
1674
+ const options = cb(timestamp, short);
1675
+ const resolvedOptions = options.timeZone === undefined
1676
+ ? {
1677
+ ...options,
1678
+ timeZone: 'UTC',
1679
+ }
1680
+ : options;
1681
+ if (calendar.intlCalendar === undefined || resolvedOptions.calendar !== undefined) {
1682
+ return resolvedOptions;
1683
+ }
1684
+ return {
1685
+ ...resolvedOptions,
1686
+ calendar: calendar.intlCalendar,
1687
+ };
1688
+ }, (timestamp) => makeCalendarDateTimeUTC(timestamp, calendar));
1689
+ }
1196
1690
  /**
1197
1691
  * Converts a Timestamp date into a host-local JavaScript Date.
1198
1692
  *
1199
1693
  * @param {Timestamp} timestamp Timestamp object to convert.
1200
1694
  * @returns {Date} Host-local JavaScript Date object.
1695
+ * @category conversion
1201
1696
  */
1202
1697
  export function makeDate(timestamp) {
1203
1698
  return new Date(timestamp.year, timestamp.month - 1, timestamp.day, 0, 0);
@@ -1207,24 +1702,54 @@ export function makeDate(timestamp) {
1207
1702
  *
1208
1703
  * @param {Timestamp} timestamp Timestamp object to convert.
1209
1704
  * @returns {Date} JavaScript Date object built with `Date.UTC()`.
1705
+ * @category conversion
1210
1706
  */
1211
1707
  export function makeDateUTC(timestamp) {
1212
1708
  return new Date(Date.UTC(timestamp.year, timestamp.month - 1, timestamp.day, 0, 0));
1213
1709
  }
1710
+ /**
1711
+ * Converts an adapter calendar timestamp date into a UTC JavaScript Date.
1712
+ *
1713
+ * The native Date always represents the equivalent Gregorian civil day so Intl
1714
+ * can format the adapter date correctly when paired with a calendar option.
1715
+ *
1716
+ * @param timestamp Calendar timestamp object to convert.
1717
+ * @param calendar Calendar implementation to use.
1718
+ * @returns JavaScript Date object built with `Date.UTC()`.
1719
+ * @category calendar
1720
+ */
1721
+ export function makeCalendarDateUTC(timestamp, calendar = gregorianCalendar) {
1722
+ const date = gregorianCalendar.fromEpochDay(getCalendarDayIdentifier(timestamp, calendar));
1723
+ return new Date(Date.UTC(date.year, date.month - 1, date.day, 0, 0));
1724
+ }
1214
1725
  /**
1215
1726
  * Converts a Timestamp date and time into a host-local JavaScript Date.
1216
1727
  *
1217
1728
  * @param {Timestamp} timestamp Timestamp object to convert.
1218
1729
  * @returns {Date} Host-local JavaScript Date object.
1730
+ * @category conversion
1219
1731
  */
1220
1732
  export function makeDateTime(timestamp) {
1221
1733
  return new Date(timestamp.year, timestamp.month - 1, timestamp.day, timestamp.hour, timestamp.minute, timestamp.second ?? 0, timestamp.millisecond ?? 0);
1222
1734
  }
1735
+ /**
1736
+ * Converts an adapter calendar timestamp date-time into a UTC JavaScript Date.
1737
+ *
1738
+ * @param timestamp Calendar timestamp object to convert.
1739
+ * @param calendar Calendar implementation to use.
1740
+ * @returns JavaScript Date object built with `Date.UTC()`.
1741
+ * @category calendar
1742
+ */
1743
+ export function makeCalendarDateTimeUTC(timestamp, calendar = gregorianCalendar) {
1744
+ const date = gregorianCalendar.fromEpochDay(getCalendarDayIdentifier(timestamp, calendar));
1745
+ return new Date(Date.UTC(date.year, date.month - 1, date.day, timestamp.hour, timestamp.minute, timestamp.second ?? 0, timestamp.millisecond ?? 0));
1746
+ }
1223
1747
  /**
1224
1748
  * Converts a Timestamp date and time into a UTC JavaScript Date.
1225
1749
  *
1226
1750
  * @param {Timestamp} timestamp Timestamp object to convert.
1227
1751
  * @returns {Date} JavaScript Date object built with `Date.UTC()`.
1752
+ * @category conversion
1228
1753
  */
1229
1754
  export function makeDateTimeUTC(timestamp) {
1230
1755
  return new Date(Date.UTC(timestamp.year, timestamp.month - 1, timestamp.day, timestamp.hour, timestamp.minute, timestamp.second ?? 0, timestamp.millisecond ?? 0));
@@ -1237,6 +1762,7 @@ export function makeDateTimeUTC(timestamp) {
1237
1762
  *
1238
1763
  * @param {Timestamp} timestamp Timestamp object to convert.
1239
1764
  * @returns {number} Unix milliseconds.
1765
+ * @category conversion
1240
1766
  */
1241
1767
  export function toUnixMilliseconds(timestamp) {
1242
1768
  return makeDateTimeUTC(timestamp).getTime();
@@ -1248,6 +1774,7 @@ export function toUnixMilliseconds(timestamp) {
1248
1774
  *
1249
1775
  * @param {Timestamp} timestamp Timestamp object to convert.
1250
1776
  * @returns {number} Unix seconds.
1777
+ * @category conversion
1251
1778
  */
1252
1779
  export function toUnixSeconds(timestamp) {
1253
1780
  return Math.floor(toUnixMilliseconds(timestamp) / MILLISECONDS_IN_SECOND);
@@ -1257,6 +1784,7 @@ export function toUnixSeconds(timestamp) {
1257
1784
  *
1258
1785
  * @param {number} milliseconds Unix milliseconds.
1259
1786
  * @returns {Timestamp | null} Timestamp built from UTC fields, or `null` for invalid input.
1787
+ * @category conversion
1260
1788
  */
1261
1789
  export function fromUnixMilliseconds(milliseconds) {
1262
1790
  return parseDateUTC(new Date(milliseconds));
@@ -1266,6 +1794,7 @@ export function fromUnixMilliseconds(milliseconds) {
1266
1794
  *
1267
1795
  * @param {number} seconds Unix seconds.
1268
1796
  * @returns {Timestamp | null} Timestamp built from UTC fields, or `null` for invalid input.
1797
+ * @category conversion
1269
1798
  */
1270
1799
  export function fromUnixSeconds(seconds) {
1271
1800
  return fromUnixMilliseconds(seconds * MILLISECONDS_IN_SECOND);
@@ -1277,6 +1806,7 @@ export function fromUnixSeconds(seconds) {
1277
1806
  *
1278
1807
  * @param {Timestamp} timestamp Timestamp object to convert.
1279
1808
  * @returns {Date} Local JavaScript Date object.
1809
+ * @category conversion
1280
1810
  */
1281
1811
  export function getDateObject(timestamp) {
1282
1812
  return makeDateTime(timestamp);
@@ -1287,6 +1817,7 @@ export function getDateObject(timestamp) {
1287
1817
  * @param input - The value to be validated. Can be a string or a number.
1288
1818
  * @returns A boolean indicating whether the input is a finite number.
1289
1819
  * Returns true if the input is a finite number, false otherwise.
1820
+ * @category validation
1290
1821
  */
1291
1822
  export function validateNumber(input) {
1292
1823
  return isFinite(Number(input));
@@ -1297,6 +1828,7 @@ export function validateNumber(input) {
1297
1828
  * @param {Timestamp[]} timestamps Timestamp objects to compare.
1298
1829
  * @param {boolean=} useTime Include time-of-day in the comparison when true.
1299
1830
  * @returns Latest Timestamp object.
1831
+ * @category comparison
1300
1832
  */
1301
1833
  export function maxTimestamp(timestamps, useTime = false) {
1302
1834
  const func = useTime === true ? getDayTimeIdentifier : getDayIdentifier;
@@ -1310,6 +1842,7 @@ export function maxTimestamp(timestamps, useTime = false) {
1310
1842
  * @param {Timestamp[]} timestamps Timestamp objects to compare.
1311
1843
  * @param {boolean=} useTime Include time-of-day in the comparison when true.
1312
1844
  * @returns Earliest Timestamp object.
1845
+ * @category comparison
1313
1846
  */
1314
1847
  export function minTimestamp(timestamps, useTime = false) {
1315
1848
  const func = useTime === true ? getDayTimeIdentifier : getDayIdentifier;
@@ -1321,7 +1854,7 @@ function getTimestampSortValue(timestamp, useTime) {
1321
1854
  if (useTime === true) {
1322
1855
  return toUnixMilliseconds(timestamp);
1323
1856
  }
1324
- return Date.UTC(timestamp.year, timestamp.month - 1, timestamp.day);
1857
+ return getEpochDay(timestamp) * MILLISECONDS_IN_DAY;
1325
1858
  }
1326
1859
  function compareTimestampOrder(first, second, useTime) {
1327
1860
  return getTimestampSortValue(first, useTime) - getTimestampSortValue(second, useTime);
@@ -1346,6 +1879,7 @@ function moveBoundary(timestamp, amount, useTime) {
1346
1879
  * @param {Timestamp} end Second boundary.
1347
1880
  * @param {boolean=} useTime Include time-of-day when ordering boundaries.
1348
1881
  * @returns {TimestampRange} Frozen inclusive Timestamp range.
1882
+ * @category ranges
1349
1883
  */
1350
1884
  export function createTimestampRange(start, end, useTime = false) {
1351
1885
  if (compareTimestampOrder(start, end, useTime) <= 0) {
@@ -1360,6 +1894,7 @@ export function createTimestampRange(start, end, useTime = false) {
1360
1894
  * @param {TimestampRange} range Inclusive range to test against.
1361
1895
  * @param {boolean=} useTime Include time-of-day in the comparison.
1362
1896
  * @returns {boolean} True when the timestamp is inside the range.
1897
+ * @category comparison
1363
1898
  */
1364
1899
  export function isTimestampInRange(timestamp, range, useTime = false) {
1365
1900
  return isBetweenDates(timestamp, range.start, range.end, useTime);
@@ -1371,6 +1906,7 @@ export function isTimestampInRange(timestamp, range, useTime = false) {
1371
1906
  * @param {TimestampRange} second Second range.
1372
1907
  * @param {boolean=} useTime Include time-of-day in the comparison.
1373
1908
  * @returns {boolean} True when the ranges overlap.
1909
+ * @category comparison
1374
1910
  */
1375
1911
  export function isRangeOverlapping(first, second, useTime = false) {
1376
1912
  const firstRange = createTimestampRange(first.start, first.end, useTime);
@@ -1387,6 +1923,7 @@ export function isRangeOverlapping(first, second, useTime = false) {
1387
1923
  * @param {TimestampRange} second Second range.
1388
1924
  * @param {boolean=} useTime Include time-of-day in the comparison.
1389
1925
  * @returns {TimestampRange | null} Intersected range, or `null` when the ranges do not overlap.
1926
+ * @category ranges
1390
1927
  */
1391
1928
  export function intersectRanges(first, second, useTime = false) {
1392
1929
  if (isRangeOverlapping(first, second, useTime) === false) {
@@ -1406,6 +1943,7 @@ export function intersectRanges(first, second, useTime = false) {
1406
1943
  * @param {TimestampRange[]} ranges Ranges to merge.
1407
1944
  * @param {boolean=} useTime Include time-of-day in the comparison.
1408
1945
  * @returns {TimestampRange[]} Merged ranges sorted by start boundary.
1946
+ * @category ranges
1409
1947
  */
1410
1948
  export function mergeRanges(ranges, useTime = false) {
1411
1949
  const sorted = ranges
@@ -1433,6 +1971,7 @@ export function mergeRanges(ranges, useTime = false) {
1433
1971
  * @param {TimestampRange[]} blocked Ranges to remove from the source.
1434
1972
  * @param {boolean=} useTime Include time-of-day in the comparison.
1435
1973
  * @returns {TimestampRange[]} Remaining ranges.
1974
+ * @category ranges
1436
1975
  */
1437
1976
  export function subtractRanges(source, blocked, useTime = false) {
1438
1977
  const normalizedSource = createTimestampRange(source.start, source.end, useTime);
@@ -1467,6 +2006,7 @@ export function subtractRanges(source, blocked, useTime = false) {
1467
2006
  * @param {TimestampRange[]} occupied Ranges that are not available.
1468
2007
  * @param {boolean=} useTime Include time-of-day in the comparison.
1469
2008
  * @returns {TimestampRange[]} Gap ranges.
2009
+ * @category ranges
1470
2010
  */
1471
2011
  export function findRangeGaps(source, occupied, useTime = false) {
1472
2012
  return subtractRanges(source, occupied, useTime);
@@ -1479,6 +2019,7 @@ export function findRangeGaps(source, occupied, useTime = false) {
1479
2019
  * @param {Timestamp} endTimestamp Inclusive end boundary.
1480
2020
  * @param {boolean=} useTime Include time-of-day in the comparison when true.
1481
2021
  * @returns {boolean} True when the timestamp is inside the range.
2022
+ * @category comparison
1482
2023
  */
1483
2024
  export function isBetweenDates(timestamp, startTimestamp, endTimestamp, useTime = false) {
1484
2025
  const cd = getDayIdentifier(timestamp) + (useTime === true ? getTimeIdentifier(timestamp) : 0);
@@ -1494,6 +2035,7 @@ export function isBetweenDates(timestamp, startTimestamp, endTimestamp, useTime
1494
2035
  * @param {Timestamp} firstTimestamp Start of the second range.
1495
2036
  * @param {Timestamp} lastTimestamp End of the second range.
1496
2037
  * @returns {boolean} True when the ranges overlap.
2038
+ * @category comparison
1497
2039
  */
1498
2040
  export function isOverlappingDates(startTimestamp, endTimestamp, firstTimestamp, lastTimestamp) {
1499
2041
  const start = getDayIdentifier(startTimestamp);
@@ -1522,6 +2064,7 @@ export function isOverlappingDates(startTimestamp, endTimestamp, firstTimestamp,
1522
2064
  * @param {number=} options.second If positive, adds seconds. If negative, removes seconds.
1523
2065
  * @param {number=} options.millisecond If positive, adds milliseconds. If negative, removes milliseconds.
1524
2066
  * @returns {Timestamp} New normalized Timestamp object.
2067
+ * @category arithmetic
1525
2068
  */
1526
2069
  export function addToDate(timestamp, options) {
1527
2070
  const ts = cloneTimestamp(timestamp);
@@ -1563,6 +2106,7 @@ export function addToDate(timestamp, options) {
1563
2106
  * @param {number=} options.second If positive, adds seconds. If negative, removes seconds.
1564
2107
  * @param {number=} options.millisecond If positive, adds milliseconds. If negative, removes milliseconds.
1565
2108
  * @returns {Timestamp} New normalized Timestamp object.
2109
+ * @category arithmetic
1566
2110
  */
1567
2111
  export function addToDateClamped(timestamp, options) {
1568
2112
  const ts = cloneTimestamp(timestamp);
@@ -1588,6 +2132,7 @@ export function addToDateClamped(timestamp, options) {
1588
2132
  * Normalizes a year/month pair while keeping the day out of the calculation.
1589
2133
  * This lets clamped date math choose the final day explicitly instead of
1590
2134
  * letting JavaScript Date roll an overflowing day into the next month.
2135
+ * @category arithmetic
1591
2136
  */
1592
2137
  function normalizeYearMonth(year, month) {
1593
2138
  const date = new Date(year, month - 1, 1);
@@ -1633,6 +2178,7 @@ function normalizeTimestamp(ts) {
1633
2178
  * @param {Timestamp} ts1 The first Timestamp
1634
2179
  * @param {Timestamp} ts2 The second Timestamp
1635
2180
  * @returns Number of days
2181
+ * @category arithmetic
1636
2182
  */
1637
2183
  export function daysBetween(ts1, ts2) {
1638
2184
  const diff = diffTimestamp(ts1, ts2, true);
@@ -1642,6 +2188,7 @@ export function daysBetween(ts1, ts2) {
1642
2188
  * Returns number of weeks between two Timestamps
1643
2189
  * @param {Timestamp} ts1 The first Timestamp
1644
2190
  * @param {Timestamp} ts2 The second Timestamp
2191
+ * @category arithmetic
1645
2192
  */
1646
2193
  export function weeksBetween(ts1, ts2) {
1647
2194
  let t1 = copyTimestamp(ts1);
@@ -1655,6 +2202,7 @@ export function weeksBetween(ts1, ts2) {
1655
2202
  *
1656
2203
  * @param {number} milliseconds Signed elapsed milliseconds.
1657
2204
  * @returns {TimestampDuration} Frozen duration object.
2205
+ * @category duration
1658
2206
  */
1659
2207
  export function createDuration(milliseconds) {
1660
2208
  const sign = milliseconds === 0 ? 0 : milliseconds < 0 ? -1 : 1;
@@ -1687,6 +2235,7 @@ export function createDuration(milliseconds) {
1687
2235
  * @param {Timestamp} start Start timestamp.
1688
2236
  * @param {Timestamp} end End timestamp.
1689
2237
  * @returns {TimestampDuration} Frozen duration object.
2238
+ * @category duration
1690
2239
  */
1691
2240
  export function durationBetween(start, end) {
1692
2241
  return createDuration(toUnixMilliseconds(end) - toUnixMilliseconds(start));
@@ -1701,9 +2250,10 @@ export function durationBetween(start, end) {
1701
2250
  * @param {Timestamp} timestamp Timestamp object to offset.
1702
2251
  * @param {TimestampDuration | number} duration Duration object or signed milliseconds.
1703
2252
  * @returns {Timestamp} Offset Timestamp.
2253
+ * @category duration
1704
2254
  */
1705
2255
  export function addDuration(timestamp, duration) {
1706
- const milliseconds = typeof duration === "number" ? duration : duration.totalMilliseconds;
2256
+ const milliseconds = typeof duration === 'number' ? duration : duration.totalMilliseconds;
1707
2257
  return fromUnixMilliseconds(toUnixMilliseconds(timestamp) + milliseconds);
1708
2258
  }
1709
2259
  /**
@@ -1712,9 +2262,10 @@ export function addDuration(timestamp, duration) {
1712
2262
  * @param {Timestamp} timestamp Timestamp object to offset.
1713
2263
  * @param {TimestampDuration | number} duration Duration object or signed milliseconds.
1714
2264
  * @returns {Timestamp} Offset Timestamp.
2265
+ * @category duration
1715
2266
  */
1716
2267
  export function subtractDuration(timestamp, duration) {
1717
- const milliseconds = typeof duration === "number" ? duration : duration.totalMilliseconds;
2268
+ const milliseconds = typeof duration === 'number' ? duration : duration.totalMilliseconds;
1718
2269
  return addDuration(timestamp, -milliseconds);
1719
2270
  }
1720
2271
  /**
@@ -1725,11 +2276,12 @@ export function subtractDuration(timestamp, duration) {
1725
2276
  * @param {TimestampDuration | number} duration Duration object or signed milliseconds.
1726
2277
  * @param {FormatDurationOptions=} options Formatting options.
1727
2278
  * @returns {string} Formatted duration.
2279
+ * @category duration
1728
2280
  */
1729
2281
  export function formatDuration(duration, options = {}) {
1730
- const value = typeof duration === "number" ? createDuration(duration) : duration;
2282
+ const value = typeof duration === 'number' ? createDuration(duration) : duration;
1731
2283
  const hours = value.days * HOURS_IN_DAY + value.hours;
1732
- const sign = options.signed === true && value.sign < 0 ? "-" : "";
2284
+ const sign = options.signed === true && value.sign < 0 ? '-' : '';
1733
2285
  let formatted = `${sign}${padNumber(hours, 2)}:${padNumber(value.minutes, 2)}:${padNumber(value.seconds, 2)}`;
1734
2286
  if (options.milliseconds === true) {
1735
2287
  formatted += `.${padNumber(value.milliseconds, 3)}`;
@@ -1750,6 +2302,7 @@ function roundTimestampToInterval(timestamp, minutes, rounder) {
1750
2302
  * @param {Timestamp} timestamp Timestamp object to round.
1751
2303
  * @param {number} minutes Interval size in minutes.
1752
2304
  * @returns {Timestamp} Rounded Timestamp.
2305
+ * @category arithmetic
1753
2306
  */
1754
2307
  export function floorToInterval(timestamp, minutes) {
1755
2308
  return roundTimestampToInterval(timestamp, minutes, Math.floor);
@@ -1760,6 +2313,7 @@ export function floorToInterval(timestamp, minutes) {
1760
2313
  * @param {Timestamp} timestamp Timestamp object to round.
1761
2314
  * @param {number} minutes Interval size in minutes.
1762
2315
  * @returns {Timestamp} Rounded Timestamp.
2316
+ * @category arithmetic
1763
2317
  */
1764
2318
  export function ceilToInterval(timestamp, minutes) {
1765
2319
  return roundTimestampToInterval(timestamp, minutes, Math.ceil);
@@ -1770,22 +2324,23 @@ export function ceilToInterval(timestamp, minutes) {
1770
2324
  * @param {Timestamp} timestamp Timestamp object to round.
1771
2325
  * @param {number} minutes Interval size in minutes.
1772
2326
  * @returns {Timestamp} Rounded Timestamp.
2327
+ * @category arithmetic
1773
2328
  */
1774
2329
  export function roundToInterval(timestamp, minutes) {
1775
2330
  return roundTimestampToInterval(timestamp, minutes, Math.round);
1776
2331
  }
1777
2332
  // Known dates
1778
2333
  const weekdayDateMap = {
1779
- Sun: new Date("2020-01-05T00:00:00.000Z"),
1780
- Mon: new Date("2020-01-06T00:00:00.000Z"),
1781
- Tue: new Date("2020-01-07T00:00:00.000Z"),
1782
- Wed: new Date("2020-01-08T00:00:00.000Z"),
1783
- Thu: new Date("2020-01-09T00:00:00.000Z"),
1784
- Fri: new Date("2020-01-10T00:00:00.000Z"),
1785
- Sat: new Date("2020-01-11T00:00:00.000Z"),
2334
+ Sun: new Date('2020-01-05T00:00:00.000Z'),
2335
+ Mon: new Date('2020-01-06T00:00:00.000Z'),
2336
+ Tue: new Date('2020-01-07T00:00:00.000Z'),
2337
+ Wed: new Date('2020-01-08T00:00:00.000Z'),
2338
+ Thu: new Date('2020-01-09T00:00:00.000Z'),
2339
+ Fri: new Date('2020-01-10T00:00:00.000Z'),
2340
+ Sat: new Date('2020-01-11T00:00:00.000Z'),
1786
2341
  };
1787
2342
  function resolveIntlNameFormat(options, type) {
1788
- if (type === "long" || type === "short" || type === "narrow") {
2343
+ if (type === 'long' || type === 'short' || type === 'narrow') {
1789
2344
  return options[type];
1790
2345
  }
1791
2346
  return options.long;
@@ -1806,15 +2361,16 @@ function resolveIntlNameFormat(options, type) {
1806
2361
  * @param {string} [locale=''] - The locale to use for formatting.
1807
2362
  *
1808
2363
  * @returns {string} The formatted weekday.
2364
+ * @category locale
1809
2365
  */
1810
2366
  export function getWeekdayFormatter() {
1811
- const emptyFormatter = () => "";
2367
+ const emptyFormatter = () => '';
1812
2368
  const options = {
1813
- long: { timeZone: "UTC", weekday: "long" },
1814
- short: { timeZone: "UTC", weekday: "short" },
1815
- narrow: { timeZone: "UTC", weekday: "narrow" },
2369
+ long: { timeZone: 'UTC', weekday: 'long' },
2370
+ short: { timeZone: 'UTC', weekday: 'short' },
2371
+ narrow: { timeZone: 'UTC', weekday: 'narrow' },
1816
2372
  };
1817
- if (typeof Intl === "undefined" || typeof Intl.DateTimeFormat === "undefined") {
2373
+ if (typeof Intl === 'undefined' || typeof Intl.DateTimeFormat === 'undefined') {
1818
2374
  return emptyFormatter;
1819
2375
  }
1820
2376
  /**
@@ -1834,7 +2390,7 @@ export function getWeekdayFormatter() {
1834
2390
  if (e instanceof Error) {
1835
2391
  console.error(`Intl.DateTimeFormat: ${e.message} -> day of week: ${weekday}`);
1836
2392
  }
1837
- return "";
2393
+ return '';
1838
2394
  }
1839
2395
  }
1840
2396
  return weekdayFormatter;
@@ -1845,6 +2401,7 @@ export function getWeekdayFormatter() {
1845
2401
  * @param {string} type Format type: `narrow`, `short`, or `long`.
1846
2402
  * @param {string} locale Locale to use for formatting, such as `en-US`.
1847
2403
  * @returns {string[]} Localized weekday names in Sunday-first order.
2404
+ * @category locale
1848
2405
  */
1849
2406
  export function getWeekdayNames(type, locale) {
1850
2407
  const shortWeekdays = Object.keys(weekdayDateMap);
@@ -1862,15 +2419,16 @@ export function getWeekdayNames(type, locale) {
1862
2419
  * @returns {string} The formatted month name.
1863
2420
  *
1864
2421
  * @throws {Error} If Intl or Intl.DateTimeFormat is not supported in the environment.
2422
+ * @category locale
1865
2423
  */
1866
2424
  export function getMonthFormatter() {
1867
- const emptyFormatter = () => "";
2425
+ const emptyFormatter = () => '';
1868
2426
  const options = {
1869
- long: { timeZone: "UTC", month: "long" },
1870
- short: { timeZone: "UTC", month: "short" },
1871
- narrow: { timeZone: "UTC", month: "narrow" },
2427
+ long: { timeZone: 'UTC', month: 'long' },
2428
+ short: { timeZone: 'UTC', month: 'short' },
2429
+ narrow: { timeZone: 'UTC', month: 'narrow' },
1872
2430
  };
1873
- if (typeof Intl === "undefined" || typeof Intl.DateTimeFormat === "undefined") {
2431
+ if (typeof Intl === 'undefined' || typeof Intl.DateTimeFormat === 'undefined') {
1874
2432
  return emptyFormatter;
1875
2433
  }
1876
2434
  /**
@@ -1881,7 +2439,7 @@ export function getMonthFormatter() {
1881
2439
  * @param {string} [locale] - The locale to use for formatting (defaults to the system locale if not provided).
1882
2440
  * @returns {string} The formatted month string.
1883
2441
  */
1884
- function monthFormatter(month, type = "long", locale) {
2442
+ function monthFormatter(month, type = 'long', locale) {
1885
2443
  try {
1886
2444
  const intlFormatter = new Intl.DateTimeFormat(locale || undefined, resolveIntlNameFormat(options, type));
1887
2445
  const date = new Date();
@@ -1893,7 +2451,7 @@ export function getMonthFormatter() {
1893
2451
  if (e instanceof Error) {
1894
2452
  console.error(`Intl.DateTimeFormat: ${e.message} -> month: ${month}`);
1895
2453
  }
1896
- return "";
2454
+ return '';
1897
2455
  }
1898
2456
  }
1899
2457
  return monthFormatter;
@@ -1904,6 +2462,7 @@ export function getMonthFormatter() {
1904
2462
  * @param {string} type Format type: `narrow`, `short`, or `long`.
1905
2463
  * @param {string} locale Locale to use for formatting, such as `en-US`.
1906
2464
  * @returns {string[]} Localized month names in January-first order.
2465
+ * @category locale
1907
2466
  */
1908
2467
  export function getMonthNames(type, locale) {
1909
2468
  const monthFormatter = getMonthFormatter();