@timestamp-js/core 0.1.0-alpha.2 → 0.1.0-beta.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 CHANGED
@@ -97,6 +97,10 @@ export const HOURS_IN_DAY = TIME_CONSTANTS.HOURS_IN.DAY;
97
97
  * Number of milliseconds in one minute.
98
98
  */
99
99
  export const MILLISECONDS_IN_MINUTE = TIME_CONSTANTS.MILLISECONDS_IN.MINUTE;
100
+ /**
101
+ * Number of milliseconds in one second.
102
+ */
103
+ export const MILLISECONDS_IN_SECOND = TIME_CONSTANTS.MILLISECONDS_IN.SECOND;
100
104
  /**
101
105
  * Number of milliseconds in one hour.
102
106
  */
@@ -109,10 +113,22 @@ export const MILLISECONDS_IN_DAY = TIME_CONSTANTS.MILLISECONDS_IN.DAY;
109
113
  * Number of milliseconds in one week.
110
114
  */
111
115
  export const MILLISECONDS_IN_WEEK = TIME_CONSTANTS.MILLISECONDS_IN.WEEK;
116
+ /**
117
+ * Number of seconds in one minute.
118
+ */
119
+ export const SECONDS_IN_MINUTE = TIME_CONSTANTS.SECONDS_IN.MINUTE;
120
+ /**
121
+ * Number of seconds in one hour.
122
+ */
123
+ export const SECONDS_IN_HOUR = TIME_CONSTANTS.SECONDS_IN.HOUR;
124
+ /**
125
+ * Number of seconds in one day.
126
+ */
127
+ export const SECONDS_IN_DAY = TIME_CONSTANTS.SECONDS_IN.DAY;
112
128
  /**
113
129
  * Frozen empty timestamp template.
114
130
  *
115
- * Use {@link copyTimestamp} or parser helpers to create new timestamp objects
131
+ * Use copyTimestamp or parser helpers to create new timestamp objects
116
132
  * instead of mutating this shared default.
117
133
  */
118
134
  export const Timestamp = freezeTimestamp({
@@ -163,11 +179,11 @@ export function validateTimestamp(input) {
163
179
  * Fast low-level parser for date and date-time strings.
164
180
  *
165
181
  * This parser fills numeric fields, but does not update formatted date,
166
- * weekday, day-of-year, workweek, or relative flags. Use
167
- * {@link parseTimestamp} when those derived fields are needed.
182
+ * weekday, day-of-year, workweek, or relative flags. Use parseTimestamp()
183
+ * when those derived fields are needed.
168
184
  *
169
185
  * @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.
170
- * @returns {Timestamp} This {@link Timestamp} is minimally filled in. The {@link Timestamp.date} and {@link Timestamp.time} as well as relative data will not be filled in.
186
+ * @returns {Timestamp} Minimal Timestamp object, or `null` when the input cannot be parsed.
171
187
  */
172
188
  export function parsed(input) {
173
189
  if (typeof input !== "string")
@@ -211,15 +227,11 @@ export function parsed(input) {
211
227
  timestamp.time = getTime(timestamp);
212
228
  return freezeTimestamp(timestamp);
213
229
  }
214
- /**
215
- * Takes a JavaScript Date and returns a {@link Timestamp}. The {@link Timestamp} is not updated with relative information.
216
- * @param {Date} date JavaScript Date
217
- * @param {boolean} utc If set the {@link Timestamp} will parse the Date as UTC
218
- * @returns {Timestamp} A minimal {@link Timestamp} without updated or relative updates.
219
- */
220
- export function parseDate(date, utc = false) {
230
+ function parseDateByMode(date, utc) {
221
231
  if (!(date instanceof Date))
222
232
  return null;
233
+ if (Number.isNaN(date.getTime()))
234
+ return null;
223
235
  const UTC = utc ? "UTC" : "";
224
236
  const second = date[`get${UTC}Seconds`]();
225
237
  const millisecond = date[`get${UTC}Milliseconds`]();
@@ -255,6 +267,30 @@ export function parseDate(date, utc = false) {
255
267
  }
256
268
  return updateFormatted(timestamp);
257
269
  }
270
+ /**
271
+ * Converts a JavaScript Date into a formatted Timestamp using host-local fields.
272
+ *
273
+ * Use parseDateUTC() when the Date represents an instant that should be read
274
+ * with UTC getters instead of host-local getters.
275
+ *
276
+ * @param {Date} date JavaScript Date to convert.
277
+ * @returns {Timestamp} Formatted Timestamp object, or `null` for invalid input.
278
+ */
279
+ export function parseDate(date) {
280
+ return parseDateByMode(date, false);
281
+ }
282
+ /**
283
+ * Converts a JavaScript Date into a formatted Timestamp using UTC fields.
284
+ *
285
+ * Use this when server and client output should agree on the same UTC calendar
286
+ * and time fields for a native Date instant.
287
+ *
288
+ * @param {Date} date JavaScript Date to convert.
289
+ * @returns {Timestamp} Formatted Timestamp object, or `null` for invalid input.
290
+ */
291
+ export function parseDateUTC(date) {
292
+ return parseDateByMode(date, true);
293
+ }
258
294
  /**
259
295
  * Pads a number to a requested string length.
260
296
  *
@@ -290,9 +326,10 @@ export function daysInMonth(year, month) {
290
326
  return (isLeapYear(year) ? DAYS_IN_MONTH_LEAP[month] : DAYS_IN_MONTH[month]);
291
327
  }
292
328
  /**
293
- * Returns a {@link Timestamp} of next day from passed in {@link Timestamp}
294
- * @param {Timestamp} timestamp The {@link Timestamp} to use
295
- * @returns {Timestamp} A new {@link Timestamp} representing the next day
329
+ * Returns a new Timestamp for the next calendar day.
330
+ *
331
+ * @param {Timestamp} timestamp Base Timestamp object.
332
+ * @returns {Timestamp} New Timestamp representing the next day.
296
333
  */
297
334
  export function nextDay(timestamp) {
298
335
  const date = new Date(timestamp.year, timestamp.month - 1, timestamp.day + 1);
@@ -304,9 +341,10 @@ export function nextDay(timestamp) {
304
341
  }));
305
342
  }
306
343
  /**
307
- * Returns a {@link Timestamp} of previous day from passed in {@link Timestamp}
308
- * @param {Timestamp} timestamp The {@link Timestamp} to use
309
- * @returns {Timestamp} A new {@link Timestamp} representing the previous day
344
+ * Returns a new Timestamp for the previous calendar day.
345
+ *
346
+ * @param {Timestamp} timestamp Base Timestamp object.
347
+ * @returns {Timestamp} New Timestamp representing the previous day.
310
348
  */
311
349
  export function prevDay(timestamp) {
312
350
  const date = new Date(timestamp.year, timestamp.month - 1, timestamp.day - 1);
@@ -318,13 +356,48 @@ export function prevDay(timestamp) {
318
356
  }));
319
357
  }
320
358
  /**
321
- * Returns today's date
359
+ * Returns today's date using the host runtime timezone.
360
+ *
361
+ * For SSR or static rendering, server and client runtimes can produce different
362
+ * values when they run in different timezones. Use todayUTC() when the app
363
+ * wants a stable UTC calendar date instead.
364
+ *
322
365
  * @returns {string} Date string in the form `YYYY-MM-DD`
323
366
  */
324
367
  export function today() {
325
368
  const d = new Date(), month = d.getMonth() + 1, day = d.getDate(), year = d.getFullYear();
326
369
  return [year, padNumber(month, 2), padNumber(day, 2)].join("-");
327
370
  }
371
+ /**
372
+ * Returns today's date using UTC calendar fields.
373
+ *
374
+ * Pass a Date fixture to make SSR, tests, and hydration-sensitive render paths
375
+ * deterministic. This helper reads UTC fields only; it does not convert an
376
+ * existing Timestamp or timezone-suffixed string.
377
+ *
378
+ * @param {Date} date Date source to read. Defaults to the current Date.
379
+ * @returns {string} UTC date string in the form `YYYY-MM-DD`
380
+ */
381
+ export function todayUTC(date = new Date()) {
382
+ return [
383
+ padNumber(date.getUTCFullYear(), 4),
384
+ padNumber(date.getUTCMonth() + 1, 2),
385
+ padNumber(date.getUTCDate(), 2),
386
+ ].join("-");
387
+ }
388
+ /**
389
+ * Returns the current date-time as an immutable Timestamp using UTC fields.
390
+ *
391
+ * Use this when server and client output should agree on UTC calendar and time
392
+ * values. For fully deterministic SSR output, pass a Date captured by the
393
+ * caller instead of allowing each runtime to create its own current Date.
394
+ *
395
+ * @param {Date} date Date source to read. Defaults to the current Date.
396
+ * @returns {Timestamp} Immutable Timestamp built from UTC fields.
397
+ */
398
+ export function nowUTC(date = new Date()) {
399
+ return parseDateUTC(date);
400
+ }
328
401
  /**
329
402
  * Takes a date string ('YYYY-MM-DD') and validates if it is today's date
330
403
  * @param {string} date Date string in the form 'YYYY-MM-DD'
@@ -334,12 +407,25 @@ export function isToday(date) {
334
407
  return date === today();
335
408
  }
336
409
  /**
337
- * Returns the start of the week give a {@link Timestamp} and weekdays (in which it finds the day representing the start of the week).
338
- * If today {@link Timestamp} is passed in then this is used to update relative information in the returned {@link Timestamp}.
339
- * @param {Timestamp} timestamp The {@link Timestamp} to use to find the start of the week
410
+ * Checks whether a date string matches today's UTC date.
411
+ *
412
+ * Pass a Date fixture when SSR, tests, or hydration-sensitive render paths need
413
+ * deterministic behavior.
414
+ *
415
+ * @param {string} date Date string in the form `YYYY-MM-DD`.
416
+ * @param {Date} now Date source to read. Defaults to the current Date.
417
+ * @returns {boolean} True when the date matches the UTC date.
418
+ */
419
+ export function isTodayUTC(date, now = new Date()) {
420
+ return date === todayUTC(now);
421
+ }
422
+ /**
423
+ * Returns the start of the week for a Timestamp and weekday set.
424
+ * If a current Timestamp is provided, the returned Timestamp includes updated relative information.
425
+ * @param {Timestamp} timestamp The Timestamp to use to find the start of the week
340
426
  * @param {number[]} weekdays The array is [0,1,2,3,4,5,6] where 0=Sunday and 6=Saturday
341
- * @param {Timestamp=} today If passed in then the {@link Timestamp} is updated with relative information
342
- * @returns {Timestamp} A new {@link Timestamp} representing the start of the week
427
+ * @param {Timestamp=} today Current timestamp used to update relative information
428
+ * @returns {Timestamp} A new Timestamp representing the start of the week
343
429
  */
344
430
  export function getStartOfWeek(timestamp, weekdays, today) {
345
431
  let start = cloneTimestamp(timestamp);
@@ -359,12 +445,12 @@ export function getStartOfWeek(timestamp, weekdays, today) {
359
445
  return start;
360
446
  }
361
447
  /**
362
- * Returns the end of the week give a {@link Timestamp} and weekdays (in which it finds the day representing the last of the week).
363
- * If today {@link Timestamp} is passed in then this is used to update relative information in the returned {@link Timestamp}.
364
- * @param {Timestamp} timestamp The {@link Timestamp} to use to find the end of the week
448
+ * Returns the end of the week for a Timestamp and weekday set.
449
+ * If a current Timestamp is provided, the returned Timestamp includes updated relative information.
450
+ * @param {Timestamp} timestamp The Timestamp to use to find the end of the week
365
451
  * @param {number[]} weekdays The array is [0,1,2,3,4,5,6] where 0=Sunday and 6=Saturday
366
- * @param {Timestamp=} today If passed in then the {@link Timestamp} is updated with relative information
367
- * @returns {Timestamp} A new {@link Timestamp} representing the end of the week
452
+ * @param {Timestamp=} today Current timestamp used to update relative information
453
+ * @returns {Timestamp} A new Timestamp representing the end of the week
368
454
  */
369
455
  export function getEndOfWeek(timestamp, weekdays, today) {
370
456
  let end = cloneTimestamp(timestamp);
@@ -386,9 +472,9 @@ export function getEndOfWeek(timestamp, weekdays, today) {
386
472
  return end;
387
473
  }
388
474
  /**
389
- * Finds the start of the month based on the passed in {@link Timestamp}
390
- * @param {Timestamp} timestamp The {@link Timestamp} to use to find the start of the month
391
- * @returns {Timestamp} A {@link Timestamp} of the start of the month
475
+ * Finds the start of the month based on the passed in Timestamp
476
+ * @param {Timestamp} timestamp The Timestamp to use to find the start of the month
477
+ * @returns {Timestamp} A Timestamp of the start of the month
392
478
  */
393
479
  export function getStartOfMonth(timestamp) {
394
480
  let start = cloneTimestamp(timestamp);
@@ -397,9 +483,9 @@ export function getStartOfMonth(timestamp) {
397
483
  return start;
398
484
  }
399
485
  /**
400
- * Finds the end of the month based on the passed in {@link Timestamp}
401
- * @param {Timestamp} timestamp The {@link Timestamp} to use to find the end of the month
402
- * @returns {Timestamp} A {@link Timestamp} of the end of the month
486
+ * Finds the end of the month based on the passed in Timestamp
487
+ * @param {Timestamp} timestamp The Timestamp to use to find the end of the month
488
+ * @returns {Timestamp} A Timestamp of the end of the month
403
489
  */
404
490
  export function getEndOfMonth(timestamp) {
405
491
  let end = cloneTimestamp(timestamp);
@@ -445,10 +531,11 @@ export function parseTime(input) {
445
531
  return false;
446
532
  }
447
533
  /**
448
- * Compares two {@link Timestamp}s for exactness
449
- * @param {Timestamp} ts1 The first {@link Timestamp}
450
- * @param {Timestamp} ts2 The second {@link Timestamp}
451
- * @returns {boolean} True if the two {@link Timestamp}s are an exact match
534
+ * Compares two Timestamp objects for exact date, time, and timezone equality.
535
+ *
536
+ * @param {Timestamp} ts1 First Timestamp object.
537
+ * @param {Timestamp} ts2 Second Timestamp object.
538
+ * @returns {boolean} True when both timestamps match exactly.
452
539
  */
453
540
  export function compareTimestamps(ts1, ts2) {
454
541
  if (!ts1 || !ts2)
@@ -463,41 +550,44 @@ export function compareTimestamps(ts1, ts2) {
463
550
  ts1.timezone === ts2.timezone);
464
551
  }
465
552
  /**
466
- * Compares the date of two {@link Timestamp}s that have been updated with relative data
467
- * @param {Timestamp} ts1 The first {@link Timestamp}
468
- * @param {Timestamp} ts2 The second {@link Timestamp}
469
- * @returns {boolean} True if the two dates are the same
553
+ * Compares the calendar date portion of two Timestamp objects.
554
+ *
555
+ * @param {Timestamp} ts1 First Timestamp object.
556
+ * @param {Timestamp} ts2 Second Timestamp object.
557
+ * @returns {boolean} True when both dates are the same.
470
558
  */
471
559
  export function compareDate(ts1, ts2) {
472
560
  return getDate(ts1) === getDate(ts2);
473
561
  }
474
562
  /**
475
- * Compares the time of two {@link Timestamp}s that have been updated with relative data
476
- * @param {Timestamp} ts1 The first {@link Timestamp}
477
- * @param {Timestamp} ts2 The second {@link Timestamp}
478
- * @returns {boolean} True if the two times are an exact match
563
+ * Compares the formatted time portion of two Timestamp objects.
564
+ *
565
+ * @param {Timestamp} ts1 First Timestamp object.
566
+ * @param {Timestamp} ts2 Second Timestamp object.
567
+ * @returns {boolean} True when both times are the same.
479
568
  */
480
569
  export function compareTime(ts1, ts2) {
481
570
  return getTime(ts1) === getTime(ts2);
482
571
  }
483
572
  /**
484
- * Compares the date and time of two {@link Timestamp}s that have been updated with relative data
485
- * @param {Timestamp} ts1 The first {@link Timestamp}
486
- * @param {Timestamp} ts2 The second {@link Timestamp}
487
- * @returns {boolean} True if the date and time are an exact match
573
+ * Compares the formatted date and time portions of two Timestamp objects.
574
+ *
575
+ * @param {Timestamp} ts1 First Timestamp object.
576
+ * @param {Timestamp} ts2 Second Timestamp object.
577
+ * @returns {boolean} True when both date-time values are the same.
488
578
  */
489
579
  export function compareDateTime(ts1, ts2) {
490
580
  return getDateTime(ts1) === getDateTime(ts2);
491
581
  }
492
582
  /**
493
- * High-level parser that converts a string to a fully formatted {@link Timestamp}.
583
+ * Converts a supported date or date-time string into a formatted Timestamp object.
494
584
  *
495
585
  * If `now` is supplied, the returned timestamp also includes relative flags
496
586
  * such as `past`, `current`, `future`, and `currentWeekday`.
497
587
  *
498
- * @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.
499
- * @param {Timestamp} now A {@link Timestamp} to use for relative data updates
500
- * @returns {Timestamp} The {@link Timestamp.date} will be filled in as well as the {@link Timestamp.time} if a time is supplied and formatted fields (doy, weekday, workweek, etc). If 'now' is supplied, then relative data will also be updated.
588
+ * @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
+ * @param {Timestamp} now Optional Timestamp used to calculate relative flags.
590
+ * @returns {Timestamp} Formatted Timestamp object, or `null` when the input cannot be parsed.
501
591
  */
502
592
  export function parseTimestamp(input, now = null) {
503
593
  let timestamp = parsed(input);
@@ -510,9 +600,10 @@ export function parseTimestamp(input, now = null) {
510
600
  return timestamp;
511
601
  }
512
602
  /**
513
- * Converts a {@link Timestamp} into a numeric date identifier based on the passed {@link Timestamp}'s date
514
- * @param {Timestamp} timestamp The {@link Timestamp} to use
515
- * @returns {number} The numeric date identifier
603
+ * Converts a Timestamp date into a sortable numeric identifier.
604
+ *
605
+ * @param {Timestamp} timestamp Timestamp object to read.
606
+ * @returns {number} Numeric date identifier.
516
607
  */
517
608
  export function getDayIdentifier(timestamp) {
518
609
  return ((timestamp.year ?? 0) * 100000000 +
@@ -520,9 +611,10 @@ export function getDayIdentifier(timestamp) {
520
611
  (timestamp.day ?? 0) * 10000);
521
612
  }
522
613
  /**
523
- * Converts a {@link Timestamp} into a numeric time identifier based on the passed {@link Timestamp}'s time
524
- * @param {Timestamp} timestamp The {@link Timestamp} to use
525
- * @returns {number} The numeric time identifier
614
+ * Converts a Timestamp time into a sortable numeric identifier.
615
+ *
616
+ * @param {Timestamp} timestamp Timestamp object to read.
617
+ * @returns {number} Numeric time identifier.
526
618
  */
527
619
  export function getTimeIdentifier(timestamp) {
528
620
  return (timestamp.hour ?? 0) * 100 + (timestamp.minute ?? 0);
@@ -535,17 +627,18 @@ function getTimeComparisonValue(timestamp) {
535
627
  (timestamp.millisecond ?? 0));
536
628
  }
537
629
  /**
538
- * Converts a {@link Timestamp} into a numeric date and time identifier based on the passed {@link Timestamp}'s date and time
539
- * @param {Timestamp} timestamp The {@link Timestamp} to use
540
- * @returns {number} The numeric date+time identifier
630
+ * Converts a Timestamp date and time into a sortable numeric identifier.
631
+ *
632
+ * @param {Timestamp} timestamp Timestamp object to read.
633
+ * @returns {number} Numeric date-time identifier.
541
634
  */
542
635
  export function getDayTimeIdentifier(timestamp) {
543
636
  return getDayIdentifier(timestamp) + getTimeIdentifier(timestamp);
544
637
  }
545
638
  /**
546
- * Returns the difference between two {@link Timestamp}s
547
- * @param {Timestamp} ts1 The first {@link Timestamp}
548
- * @param {Timestamp} ts2 The second {@link Timestamp}
639
+ * Returns the difference between two Timestamps
640
+ * @param {Timestamp} ts1 The first Timestamp
641
+ * @param {Timestamp} ts2 The second Timestamp
549
642
  * @param {boolean=} strict Optional flag to not to return negative numbers
550
643
  * @returns {number} The difference
551
644
  */
@@ -560,11 +653,16 @@ export function diffTimestamp(ts1, ts2, strict = false) {
560
653
  return utc2 - utc1;
561
654
  }
562
655
  /**
563
- * Updates a {@link Timestamp} with relative data (past, current and future)
564
- * @param {Timestamp} timestamp The {@link Timestamp} that needs relative data updated
565
- * @param {Timestamp} now {@link Timestamp} that represents the current date (optional time)
566
- * @param {boolean=} time Optional flag to include time ('timestamp' and 'now' params should have time values)
567
- * @returns {Timestamp} A new {@link Timestamp}
656
+ * Returns a Timestamp with relative flags compared to a supplied `now` value.
657
+ *
658
+ * The returned object includes `past`, `current`, `future`, and
659
+ * `currentWeekday` flags. Pass `true` for `time` when both values should be
660
+ * compared at time-of-day precision.
661
+ *
662
+ * @param {Timestamp} timestamp Timestamp object to update.
663
+ * @param {Timestamp} now Timestamp representing the comparison point.
664
+ * @param {boolean=} time Include time-of-day in the comparison when true.
665
+ * @returns {Timestamp} New Timestamp object with relative flags.
568
666
  */
569
667
  export function updateRelative(timestamp, now, time = false) {
570
668
  const ts = cloneTimestamp(timestamp);
@@ -588,10 +686,10 @@ export function updateRelative(timestamp, now, time = false) {
588
686
  * The returned timestamp has updated hour/minute fields and clears second and
589
687
  * millisecond precision because this helper is minute-oriented.
590
688
  *
591
- * @param {Timestamp} timestamp The {@link Timestamp} to transform
689
+ * @param {Timestamp} timestamp The Timestamp to transform
592
690
  * @param {number} minutes The number of minutes to set from midnight
593
- * @param {Timestamp=} now Optional {@link Timestamp} representing current date and time
594
- * @returns {Timestamp} A new {@link Timestamp}
691
+ * @param {Timestamp=} now Optional Timestamp representing current date and time
692
+ * @returns {Timestamp} A new Timestamp
595
693
  */
596
694
  export function updateMinutes(timestamp, minutes, now = null) {
597
695
  let ts = cloneTimestamp(timestamp);
@@ -607,8 +705,8 @@ export function updateMinutes(timestamp, minutes, now = null) {
607
705
  return freezeTimestamp(ts);
608
706
  }
609
707
  /**
610
- * Updates the {@link Timestamp} with the weekday
611
- * @param {Timestamp} timestamp The {@link Timestamp} to transform
708
+ * Updates the Timestamp with the weekday
709
+ * @param {Timestamp} timestamp The Timestamp to transform
612
710
  * @returns A new Timestamp
613
711
  */
614
712
  export function updateWeekday(timestamp) {
@@ -617,8 +715,8 @@ export function updateWeekday(timestamp) {
617
715
  return freezeTimestamp(ts);
618
716
  }
619
717
  /**
620
- * Updates the {@link Timestamp} with the day of the year (doy)
621
- * @param {Timestamp} timestamp The {@link Timestamp} to transform
718
+ * Updates the Timestamp with the day of the year (doy)
719
+ * @param {Timestamp} timestamp The Timestamp to transform
622
720
  * @returns A new Timestamp
623
721
  */
624
722
  export function updateDayOfYear(timestamp) {
@@ -627,9 +725,9 @@ export function updateDayOfYear(timestamp) {
627
725
  return freezeTimestamp(ts);
628
726
  }
629
727
  /**
630
- * Updates the {@link Timestamp} with the workweek
631
- * @param {Timestamp} timestamp The {@link Timestamp} to transform
632
- * @returns A new {@link Timestamp}
728
+ * Updates the Timestamp with the workweek
729
+ * @param {Timestamp} timestamp The Timestamp to transform
730
+ * @returns A new Timestamp
633
731
  */
634
732
  export function updateWorkWeek(timestamp) {
635
733
  const ts = cloneTimestamp(timestamp);
@@ -682,13 +780,13 @@ function isTimestampInDisabledDay(timestamp, day) {
682
780
  return disabledDay !== null && getDayIdentifier(disabledDay) === target;
683
781
  }
684
782
  /**
685
- * Updates the passed {@link Timestamp} with disabled, if needed
686
- * @param {Timestamp} timestamp The {@link Timestamp} to transform
783
+ * Updates the passed Timestamp with disabled, if needed
784
+ * @param {Timestamp} timestamp The Timestamp to transform
687
785
  * @param {string} [disabledBefore] In `YYYY-MM-DD` format
688
786
  * @param {string} [disabledAfter] In `YYYY-MM-DD` format
689
787
  * @param {number[]} [disabledWeekdays] An array of numbers representing weekdays [0 = Sun, ..., 6 = Sat]
690
788
  * @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.
691
- * @returns A new {@link Timestamp}
789
+ * @returns A new Timestamp
692
790
  */
693
791
  export function updateDisabled(timestamp, disabledBefore, disabledAfter, disabledWeekdays, disabledDays) {
694
792
  let ts = cloneTimestamp(timestamp);
@@ -730,9 +828,9 @@ export function updateDisabled(timestamp, disabledBefore, disabledAfter, disable
730
828
  return freezeTimestamp(ts);
731
829
  }
732
830
  /**
733
- * Updates the passed {@link Timestamp} with formatted data (time string, date string, weekday, day of year and workweek)
734
- * @param {Timestamp} timestamp The {@link Timestamp} to transform
735
- * @returns A new {@link Timestamp}
831
+ * Updates the passed Timestamp with formatted data (time string, date string, weekday, day of year and workweek)
832
+ * @param {Timestamp} timestamp The Timestamp to transform
833
+ * @returns A new Timestamp
736
834
  */
737
835
  export function updateFormatted(timestamp) {
738
836
  const ts = cloneTimestamp(timestamp);
@@ -745,8 +843,8 @@ export function updateFormatted(timestamp) {
745
843
  return freezeTimestamp(ts);
746
844
  }
747
845
  /**
748
- * Returns day of the year (doy) for the passed in {@link Timestamp}
749
- * @param {Timestamp} timestamp The {@link Timestamp} to use
846
+ * Returns day of the year (doy) for the passed in Timestamp
847
+ * @param {Timestamp} timestamp The Timestamp to use
750
848
  * @returns {number} The day of the year
751
849
  */
752
850
  export function getDayOfYear(timestamp) {
@@ -760,8 +858,8 @@ export function getDayOfYear(timestamp) {
760
858
  1000);
761
859
  }
762
860
  /**
763
- * Returns workweek for the passed in {@link Timestamp}
764
- * @param {Timestamp} timestamp The {@link Timestamp} to use
861
+ * Returns workweek for the passed in Timestamp
862
+ * @param {Timestamp} timestamp The Timestamp to use
765
863
  * @returns {number} The work week
766
864
  */
767
865
  export function getWorkWeek(timestamp) {
@@ -787,8 +885,8 @@ export function getWorkWeek(timestamp) {
787
885
  return weekNumber;
788
886
  }
789
887
  /**
790
- * Returns weekday for the passed in {@link Timestamp}
791
- * @param {Timestamp} timestamp The {@link Timestamp} to use
888
+ * Returns weekday for the passed in Timestamp
889
+ * @param {Timestamp} timestamp The Timestamp to use
792
890
  * @returns {number} The weekday
793
891
  */
794
892
  export function getWeekday(timestamp) {
@@ -813,17 +911,80 @@ export function getWeekday(timestamp) {
813
911
  return weekday ?? 0;
814
912
  }
815
913
  /**
816
- * Makes a copy of the passed in {@link Timestamp}
817
- * @param {Timestamp} timestamp The original {@link Timestamp}
818
- * @returns {Timestamp} A copy of the original {@link Timestamp}
914
+ * Returns an immutable copy of a Timestamp object.
915
+ *
916
+ * @param {Timestamp} timestamp Timestamp object to copy.
917
+ * @returns {Timestamp} Frozen Timestamp copy.
819
918
  */
820
919
  export function copyTimestamp(timestamp) {
821
920
  return freezeTimestamp(timestamp);
822
921
  }
922
+ function setTimeParts(timestamp, hour, minute, second, millisecond) {
923
+ const ts = cloneTimestamp(timestamp);
924
+ ts.hasTime = true;
925
+ ts.hour = hour;
926
+ ts.minute = minute;
927
+ if (second === undefined) {
928
+ delete ts.second;
929
+ }
930
+ else {
931
+ ts.second = second;
932
+ }
933
+ if (millisecond === undefined) {
934
+ delete ts.millisecond;
935
+ }
936
+ else {
937
+ ts.millisecond = millisecond;
938
+ }
939
+ return updateFormatted(ts);
940
+ }
941
+ /**
942
+ * Returns a Timestamp at the start of the same calendar day.
943
+ *
944
+ * @param {Timestamp} timestamp Timestamp object to transform.
945
+ * @returns {Timestamp} New Timestamp at `00:00`.
946
+ */
947
+ export function getStartOfDay(timestamp) {
948
+ return setTimeParts(timestamp, 0, 0);
949
+ }
950
+ /**
951
+ * Returns a Timestamp at the end of the same calendar day.
952
+ *
953
+ * @param {Timestamp} timestamp Timestamp object to transform.
954
+ * @returns {Timestamp} New Timestamp at `23:59:59.999`.
955
+ */
956
+ export function getEndOfDay(timestamp) {
957
+ return setTimeParts(timestamp, 23, 59, 59, 999);
958
+ }
959
+ /**
960
+ * Returns a Timestamp at the start of the same Gregorian year.
961
+ *
962
+ * @param {Timestamp} timestamp Timestamp object to transform.
963
+ * @returns {Timestamp} New Timestamp for January 1 at `00:00`.
964
+ */
965
+ export function getStartOfYear(timestamp) {
966
+ const ts = cloneTimestamp(timestamp);
967
+ ts.month = MONTH_MIN;
968
+ ts.day = DAY_MIN;
969
+ return getStartOfDay(updateFormatted(ts));
970
+ }
971
+ /**
972
+ * Returns a Timestamp at the end of the same Gregorian year.
973
+ *
974
+ * @param {Timestamp} timestamp Timestamp object to transform.
975
+ * @returns {Timestamp} New Timestamp for December 31 at `23:59:59.999`.
976
+ */
977
+ export function getEndOfYear(timestamp) {
978
+ const ts = cloneTimestamp(timestamp);
979
+ ts.month = MONTH_MAX;
980
+ ts.day = daysInMonth(ts.year, MONTH_MAX);
981
+ return getEndOfDay(updateFormatted(ts));
982
+ }
823
983
  /**
824
- * Used internally to convert {@link Timestamp} used with 'parsed' or 'parseDate' so the 'date' portion of the {@link Timestamp} is correct.
825
- * @param {Timestamp} timestamp The (raw) {@link Timestamp}
826
- * @returns {string} A formatted date ('YYYY-MM-DD')
984
+ * Formats the date portion of a Timestamp object.
985
+ *
986
+ * @param {Timestamp} timestamp Timestamp object to format.
987
+ * @returns {string} Date string such as `YYYY-MM-DD`.
827
988
  */
828
989
  export function getDate(timestamp) {
829
990
  let str = `${padNumber(timestamp.year, 4)}-${padNumber(timestamp.month, 2)}`;
@@ -832,9 +993,13 @@ export function getDate(timestamp) {
832
993
  return str;
833
994
  }
834
995
  /**
835
- * Used internally to convert {@link Timestamp} with 'parsed' or 'parseDate' so the 'time' portion of the {@link Timestamp} is correct.
836
- * @param {Timestamp} timestamp The (raw) {@link Timestamp}
837
- * @returns {string} A formatted time ('hh:mm')
996
+ * Formats the time portion of a Timestamp object.
997
+ *
998
+ * Minute precision is formatted as `HH:mm`; second precision as `HH:mm:ss`;
999
+ * millisecond precision as `HH:mm:ss.SSS`.
1000
+ *
1001
+ * @param {Timestamp} timestamp Timestamp object to format.
1002
+ * @returns {string} Time string, or an empty string when the timestamp has no time.
838
1003
  */
839
1004
  export function getTime(timestamp) {
840
1005
  if (!timestamp.hasTime) {
@@ -850,31 +1015,32 @@ export function getTime(timestamp) {
850
1015
  return time;
851
1016
  }
852
1017
  /**
853
- * Returns a formatted string date and time ('YYYY-YY-MM hh:mm')
854
- * @param {Timestamp} timestamp The {@link Timestamp}
855
- * @returns {string} A formatted date time ('YYYY-MM-DD HH:mm')
1018
+ * Formats a Timestamp as date plus time.
1019
+ *
1020
+ * @param {Timestamp} timestamp Timestamp object to format.
1021
+ * @returns {string} Date-time string such as `YYYY-MM-DD HH:mm`.
856
1022
  */
857
1023
  export function getDateTime(timestamp) {
858
1024
  return getDate(timestamp) + " " + (timestamp.hasTime ? getTime(timestamp) : "00:00");
859
1025
  }
860
1026
  /**
861
- * An alias for {relativeDays}
862
- * @param {Timestamp} timestamp The {@link Timestamp} to transform
1027
+ * Alias for relativeDays.
1028
+ * @param {Timestamp} timestamp The Timestamp to transform
863
1029
  * @param {function} [mover=nextDay] The mover function to use (ie: {nextDay} or {prevDay}).
864
1030
  * @param {number} [days=1] The number of days to move.
865
1031
  * @param {number[]} [allowedWeekdays=[ 0, 1, 2, 3, 4, 5, 6 ]] An array of numbers representing the weekdays. ie: [0 = Sun, ..., 6 = Sat].
866
- * @returns A new {@link Timestamp}
1032
+ * @returns A new Timestamp
867
1033
  */
868
1034
  export function moveRelativeDays(timestamp, mover = nextDay, days = 1, allowedWeekdays = [0, 1, 2, 3, 4, 5, 6]) {
869
1035
  return relativeDays(timestamp, mover, days, allowedWeekdays);
870
1036
  }
871
1037
  /**
872
- * Moves the {@link Timestamp} the number of relative days
873
- * @param {Timestamp} timestamp The {@link Timestamp} to transform
1038
+ * Moves the Timestamp the number of relative days
1039
+ * @param {Timestamp} timestamp The Timestamp to transform
874
1040
  * @param {function} [mover=nextDay] The mover function to use (ie: {nextDay} or {prevDay}).
875
1041
  * @param {number} [days=1] The number of days to move.
876
1042
  * @param {number[]} [allowedWeekdays=[ 0, 1, 2, 3, 4, 5, 6 ]] An array of numbers representing the weekdays. ie: [0 = Sun, ..., 6 = Sat].
877
- * @returns A new {@link Timestamp}
1043
+ * @returns A new Timestamp
878
1044
  */
879
1045
  export function relativeDays(timestamp, mover = nextDay, days = 1, allowedWeekdays = [0, 1, 2, 3, 4, 5, 6]) {
880
1046
  let ts = copyTimestamp(timestamp);
@@ -890,12 +1056,12 @@ export function relativeDays(timestamp, mover = nextDay, days = 1, allowedWeekda
890
1056
  return ts;
891
1057
  }
892
1058
  /**
893
- * Finds the specified weekday (forward or back) based on the {@link Timestamp}
894
- * @param {Timestamp} timestamp The {@link Timestamp} to transform
1059
+ * Finds the specified weekday (forward or back) based on the Timestamp
1060
+ * @param {Timestamp} timestamp The Timestamp to transform
895
1061
  * @param {number} weekday The weekday number (Sun = 0, ..., Sat = 6)
896
1062
  * @param {function} [mover=nextDay] The function to use ({prevDay} or {nextDay}).
897
1063
  * @param {number} [maxDays=6] The number of days to look forward or back.
898
- * @returns A new {@link Timestamp}
1064
+ * @returns A new Timestamp
899
1065
  */
900
1066
  export function findWeekday(timestamp, weekday, mover = nextDay, maxDays = 6) {
901
1067
  let ts = copyTimestamp(timestamp);
@@ -904,18 +1070,22 @@ export function findWeekday(timestamp, weekday, mover = nextDay, maxDays = 6) {
904
1070
  return ts;
905
1071
  }
906
1072
  /**
907
- * Creates an array of {@link Timestamp}s based on start and end params
908
- * @param {Timestamp} start The starting {@link Timestamp}
909
- * @param {Timestamp} end The ending {@link Timestamp}
910
- * @param {Timestamp} now The relative day
911
- * @param {number[]} weekdays An array of numbers (representing days of the week) that are 0 (=Sunday) to 6 (=Saturday)
912
- * @param {string} [disabledBefore] Days before this date are disabled (YYYY-MM-DD)
913
- * @param {string} [disabledAfter] Days after this date are disabled (YYYY-MM-DD)
914
- * @param {number[]} [disabledWeekdays] An array representing weekdays that are disabled [0 = Sun, ..., 6 = Sat]
915
- * @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.
916
- * @param {number} [max=42] Max days to do
917
- * @param {number} [min=0] Min days to do
918
- * @returns {Timestamp[]} The requested array of {@link Timestamp}s
1073
+ * Creates an inclusive list of Timestamp days between start and end.
1074
+ *
1075
+ * The returned days are formatted, marked with relative flags against `now`,
1076
+ * and can include disabled metadata when disabled options are supplied.
1077
+ *
1078
+ * @param {Timestamp} start First day in the list.
1079
+ * @param {Timestamp} end Last day boundary for the list.
1080
+ * @param {Timestamp} now Timestamp used to calculate relative flags.
1081
+ * @param {number[]} weekdays Weekday numbers to include, from `0` Sunday to `6` Saturday.
1082
+ * @param {string} [disabledBefore] Disable days before this `YYYY-MM-DD` date.
1083
+ * @param {string} [disabledAfter] Disable days after this `YYYY-MM-DD` date.
1084
+ * @param {number[]} [disabledWeekdays] Weekday numbers to mark disabled.
1085
+ * @param {DisabledDays} [disabledDays] Specific dates or date ranges to mark disabled.
1086
+ * @param {number} [max=42] Maximum number of days to return.
1087
+ * @param {number} [min=0] Minimum number of days to return.
1088
+ * @returns {Timestamp[]} Timestamp days.
919
1089
  */
920
1090
  export function createDayList(start, end, now, weekdays = [0, 1, 2, 3, 4, 5, 6], disabledBefore = undefined, disabledAfter = undefined, disabledWeekdays = [], disabledDays = [], max = 42, min = 0) {
921
1091
  const begin = getDayIdentifier(start);
@@ -947,13 +1117,14 @@ export function createDayList(start, end, now, weekdays = [0, 1, 2, 3, 4, 5, 6],
947
1117
  return days;
948
1118
  }
949
1119
  /**
950
- * Creates an array of interval {@link Timestamp}s based on params
951
- * @param {Timestamp} timestamp The starting {@link Timestamp}
952
- * @param {number} first The starting interval time
953
- * @param {number} minutes How many minutes between intervals (ie: 60, 30, 15 would be common ones)
954
- * @param {number} count The number of intervals needed
955
- * @param {Timestamp} now A relative {@link Timestamp} with time
956
- * @returns {Timestamp[]} The requested array of interval {@link Timestamp}s
1120
+ * Creates an array of interval Timestamp objects for one day.
1121
+ *
1122
+ * @param {Timestamp} timestamp Base date for the intervals.
1123
+ * @param {number} first Starting interval index.
1124
+ * @param {number} minutes Minutes between intervals, such as 60, 30, or 15.
1125
+ * @param {number} count Number of intervals to create.
1126
+ * @param {Timestamp} now Timestamp used to calculate relative flags.
1127
+ * @returns {Timestamp[]} Interval Timestamp objects.
957
1128
  */
958
1129
  export function createIntervalList(timestamp, first, minutes, count, now) {
959
1130
  const intervals = [];
@@ -965,76 +1136,150 @@ export function createIntervalList(timestamp, first, minutes, count, now) {
965
1136
  }
966
1137
  /**
967
1138
  * @callback getOptions
968
- * @param {Timestamp} timestamp A {@link Timestamp} object
1139
+ * @param {Timestamp} timestamp A Timestamp object
969
1140
  * @param {boolean} short True if using short options
970
1141
  * @returns {Object} An Intl object representing options to be used
971
1142
  */
972
1143
  /**
973
1144
  * @callback formatter
974
- * @param {Timestamp} timestamp The {@link Timestamp} being used
1145
+ * @param {Timestamp} timestamp The Timestamp being used
975
1146
  * @param {boolean} short If short format is being requested
976
- * @returns {string} The localized string of the formatted {@link Timestamp}
1147
+ * @returns {string} The localized string of the formatted Timestamp
977
1148
  */
978
- /**
979
- * Returns a locale formatter backed by `Intl.DateTimeFormat`.
980
- *
981
- * The helper is SSR-safe: if `Intl.DateTimeFormat` is unavailable in a target
982
- * runtime, it returns a formatter that produces an empty string instead of
983
- * throwing during module load.
984
- *
985
- * @param {string} locale The locale to use (ie: en-US)
986
- * @param {getOptions} cb The function to call for options. This function should return an Intl formatted object. The function is passed (timestamp, short).
987
- * @returns {formatter} The function has params (timestamp, short). The short is to use the short options.
988
- */
989
- export function createNativeLocaleFormatter(locale, cb) {
1149
+ function createNativeLocaleFormatterByMode(locale, cb, toDate) {
990
1150
  const emptyFormatter = () => "";
991
- /* istanbul ignore next */
992
1151
  if (typeof Intl === "undefined" || typeof Intl.DateTimeFormat === "undefined") {
993
1152
  return emptyFormatter;
994
1153
  }
995
1154
  return (timestamp, short) => {
996
1155
  try {
997
1156
  const intlFormatter = new Intl.DateTimeFormat(locale || undefined, cb(timestamp, short));
998
- return intlFormatter.format(makeDateTime(timestamp));
1157
+ return intlFormatter.format(toDate(timestamp));
999
1158
  }
1000
- catch (e) /* istanbul ignore next */ {
1159
+ catch (e) {
1001
1160
  console.error(`Intl.DateTimeFormat: ${e.message} -> ${getDateTime(timestamp)}`);
1002
1161
  return "";
1003
1162
  }
1004
1163
  };
1005
1164
  }
1006
1165
  /**
1007
- * Makes a JavaScript Date from the passed {@link Timestamp}
1008
- * @param {Timestamp} timestamp The {@link Timestamp} to use
1009
- * @param {boolean} utc True to get Date object using UTC
1010
- * @returns {Date} A JavaScript Date
1166
+ * Returns a host-local locale formatter backed by `Intl.DateTimeFormat`.
1167
+ *
1168
+ * The helper is SSR-safe: if `Intl.DateTimeFormat` is unavailable in a target
1169
+ * runtime, it returns a formatter that produces an empty string instead of
1170
+ * throwing during module load.
1171
+ *
1172
+ * Use `createNativeLocaleFormatterUTC()` when Timestamp values should be read
1173
+ * as UTC fields before formatting.
1174
+ *
1175
+ * @param {string} locale The locale to use (ie: en-US)
1176
+ * @param {getOptions} cb The function to call for options. This function should return an Intl formatted object. The function is passed (timestamp, short).
1177
+ * @returns {formatter} The function has params (timestamp, short). The short is to use the short options.
1178
+ */
1179
+ export function createNativeLocaleFormatter(locale, cb) {
1180
+ return createNativeLocaleFormatterByMode(locale, cb, makeDateTime);
1181
+ }
1182
+ /**
1183
+ * Returns a UTC locale formatter backed by `Intl.DateTimeFormat`.
1184
+ *
1185
+ * This helper constructs the native `Date` with UTC fields before formatting.
1186
+ * Pair it with `timeZone: "UTC"` when calendar labels must remain pinned to
1187
+ * the Timestamp's UTC date instead of the viewer's local timezone.
1188
+ *
1189
+ * @param {string} locale The locale to use (ie: en-US)
1190
+ * @param {getOptions} cb The function to call for options. This function should return an Intl formatted object. The function is passed (timestamp, short).
1191
+ * @returns {formatter} The function has params (timestamp, short). The short is to use the short options.
1192
+ */
1193
+ export function createNativeLocaleFormatterUTC(locale, cb) {
1194
+ return createNativeLocaleFormatterByMode(locale, cb, makeDateTimeUTC);
1195
+ }
1196
+ /**
1197
+ * Converts a Timestamp date into a host-local JavaScript Date.
1198
+ *
1199
+ * @param {Timestamp} timestamp Timestamp object to convert.
1200
+ * @returns {Date} Host-local JavaScript Date object.
1011
1201
  */
1012
- export function makeDate(timestamp, utc = true) {
1013
- if (utc)
1014
- return new Date(Date.UTC(timestamp.year, timestamp.month - 1, timestamp.day, 0, 0));
1202
+ export function makeDate(timestamp) {
1015
1203
  return new Date(timestamp.year, timestamp.month - 1, timestamp.day, 0, 0);
1016
1204
  }
1017
1205
  /**
1018
- * Makes a JavaScript Date from the passed {@link Timestamp} (with time)
1019
- * @param {Timestamp} timestamp The {@link Timestamp} to use
1020
- * @param {boolean} utc True to get Date object using UTC
1021
- * @returns {Date} A JavaScript Date
1206
+ * Converts a Timestamp date into a UTC JavaScript Date.
1207
+ *
1208
+ * @param {Timestamp} timestamp Timestamp object to convert.
1209
+ * @returns {Date} JavaScript Date object built with `Date.UTC()`.
1210
+ */
1211
+ export function makeDateUTC(timestamp) {
1212
+ return new Date(Date.UTC(timestamp.year, timestamp.month - 1, timestamp.day, 0, 0));
1213
+ }
1214
+ /**
1215
+ * Converts a Timestamp date and time into a host-local JavaScript Date.
1216
+ *
1217
+ * @param {Timestamp} timestamp Timestamp object to convert.
1218
+ * @returns {Date} Host-local JavaScript Date object.
1022
1219
  */
1023
- export function makeDateTime(timestamp, utc = true) {
1024
- if (utc)
1025
- return new Date(Date.UTC(timestamp.year, timestamp.month - 1, timestamp.day, timestamp.hour, timestamp.minute, timestamp.second ?? 0, timestamp.millisecond ?? 0));
1220
+ export function makeDateTime(timestamp) {
1026
1221
  return new Date(timestamp.year, timestamp.month - 1, timestamp.day, timestamp.hour, timestamp.minute, timestamp.second ?? 0, timestamp.millisecond ?? 0);
1027
1222
  }
1028
1223
  /**
1029
- * Converts a {@link Timestamp} to a local JavaScript `Date`.
1224
+ * Converts a Timestamp date and time into a UTC JavaScript Date.
1225
+ *
1226
+ * @param {Timestamp} timestamp Timestamp object to convert.
1227
+ * @returns {Date} JavaScript Date object built with `Date.UTC()`.
1228
+ */
1229
+ export function makeDateTimeUTC(timestamp) {
1230
+ return new Date(Date.UTC(timestamp.year, timestamp.month - 1, timestamp.day, timestamp.hour, timestamp.minute, timestamp.second ?? 0, timestamp.millisecond ?? 0));
1231
+ }
1232
+ /**
1233
+ * Converts a Timestamp into Unix milliseconds by reading its fields as UTC.
1234
+ *
1235
+ * This is deterministic across server and client runtimes. It does not read or
1236
+ * convert the optional `timezone` suffix stored on the Timestamp.
1237
+ *
1238
+ * @param {Timestamp} timestamp Timestamp object to convert.
1239
+ * @returns {number} Unix milliseconds.
1240
+ */
1241
+ export function toUnixMilliseconds(timestamp) {
1242
+ return makeDateTimeUTC(timestamp).getTime();
1243
+ }
1244
+ /**
1245
+ * Converts a Timestamp into Unix seconds by reading its fields as UTC.
1246
+ *
1247
+ * Milliseconds are floored because Unix seconds are integer-oriented.
1248
+ *
1249
+ * @param {Timestamp} timestamp Timestamp object to convert.
1250
+ * @returns {number} Unix seconds.
1251
+ */
1252
+ export function toUnixSeconds(timestamp) {
1253
+ return Math.floor(toUnixMilliseconds(timestamp) / MILLISECONDS_IN_SECOND);
1254
+ }
1255
+ /**
1256
+ * Converts Unix milliseconds into an immutable Timestamp using UTC fields.
1257
+ *
1258
+ * @param {number} milliseconds Unix milliseconds.
1259
+ * @returns {Timestamp | null} Timestamp built from UTC fields, or `null` for invalid input.
1260
+ */
1261
+ export function fromUnixMilliseconds(milliseconds) {
1262
+ return parseDateUTC(new Date(milliseconds));
1263
+ }
1264
+ /**
1265
+ * Converts Unix seconds into an immutable Timestamp using UTC fields.
1266
+ *
1267
+ * @param {number} seconds Unix seconds.
1268
+ * @returns {Timestamp | null} Timestamp built from UTC fields, or `null` for invalid input.
1269
+ */
1270
+ export function fromUnixSeconds(seconds) {
1271
+ return fromUnixMilliseconds(seconds * MILLISECONDS_IN_SECOND);
1272
+ }
1273
+ /**
1274
+ * Converts a Timestamp to a local JavaScript Date.
1030
1275
  *
1031
- * This is equivalent to `makeDateTime(timestamp, false)`.
1276
+ * This is equivalent to `makeDateTime(timestamp)`.
1032
1277
  *
1033
- * @param {Timestamp} timestamp The {@link Timestamp} to convert
1034
- * @returns {Date} A local JavaScript Date
1278
+ * @param {Timestamp} timestamp Timestamp object to convert.
1279
+ * @returns {Date} Local JavaScript Date object.
1035
1280
  */
1036
1281
  export function getDateObject(timestamp) {
1037
- return makeDateTime(timestamp, false);
1282
+ return makeDateTime(timestamp);
1038
1283
  }
1039
1284
  /**
1040
1285
  * Validates if the input is a finite number.
@@ -1047,10 +1292,11 @@ export function validateNumber(input) {
1047
1292
  return isFinite(Number(input));
1048
1293
  }
1049
1294
  /**
1050
- * Given an array of {@link Timestamp}s, finds the max date (and possible time)
1051
- * @param {Timestamp[]} timestamps This is an array of {@link Timestamp}s
1052
- * @param {boolean=} useTime Default false; if true, uses time in the comparison as well
1053
- * @returns The {@link Timestamp} with the highest date (and possibly time) value
1295
+ * Finds the latest Timestamp in an array.
1296
+ *
1297
+ * @param {Timestamp[]} timestamps Timestamp objects to compare.
1298
+ * @param {boolean=} useTime Include time-of-day in the comparison when true.
1299
+ * @returns Latest Timestamp object.
1054
1300
  */
1055
1301
  export function maxTimestamp(timestamps, useTime = false) {
1056
1302
  const func = useTime === true ? getDayTimeIdentifier : getDayIdentifier;
@@ -1059,10 +1305,11 @@ export function maxTimestamp(timestamps, useTime = false) {
1059
1305
  });
1060
1306
  }
1061
1307
  /**
1062
- * Given an array of {@link Timestamp}s, finds the min date (and possible time)
1063
- * @param {Timestamp[]} timestamps This is an array of {@link Timestamp}s
1064
- * @param {boolean=} useTime Default false; if true, uses time in the comparison as well
1065
- * @returns The {@link Timestamp} with the lowest date (and possibly time) value
1308
+ * Finds the earliest Timestamp in an array.
1309
+ *
1310
+ * @param {Timestamp[]} timestamps Timestamp objects to compare.
1311
+ * @param {boolean=} useTime Include time-of-day in the comparison when true.
1312
+ * @returns Earliest Timestamp object.
1066
1313
  */
1067
1314
  export function minTimestamp(timestamps, useTime = false) {
1068
1315
  const func = useTime === true ? getDayTimeIdentifier : getDayIdentifier;
@@ -1070,13 +1317,168 @@ export function minTimestamp(timestamps, useTime = false) {
1070
1317
  return Math.min(func(prev), func(cur)) === func(prev) ? prev : cur;
1071
1318
  });
1072
1319
  }
1320
+ function getTimestampSortValue(timestamp, useTime) {
1321
+ if (useTime === true) {
1322
+ return toUnixMilliseconds(timestamp);
1323
+ }
1324
+ return Date.UTC(timestamp.year, timestamp.month - 1, timestamp.day);
1325
+ }
1326
+ function compareTimestampOrder(first, second, useTime) {
1327
+ return getTimestampSortValue(first, useTime) - getTimestampSortValue(second, useTime);
1328
+ }
1329
+ function createFrozenRange(start, end) {
1330
+ return Object.freeze({ start: copyTimestamp(start), end: copyTimestamp(end) });
1331
+ }
1332
+ function isRangeTouchingOrOverlapping(first, second, useTime) {
1333
+ const step = useTime ? 1 : MILLISECONDS_IN_DAY;
1334
+ return (getTimestampSortValue(second.start, useTime) <= getTimestampSortValue(first.end, useTime) + step);
1335
+ }
1336
+ function moveBoundary(timestamp, amount, useTime) {
1337
+ if (useTime === true) {
1338
+ return fromUnixMilliseconds(toUnixMilliseconds(timestamp) + amount);
1339
+ }
1340
+ return addToDate(timestamp, { day: amount });
1341
+ }
1342
+ /**
1343
+ * Creates an inclusive Timestamp range and normalizes start/end order.
1344
+ *
1345
+ * @param {Timestamp} start First boundary.
1346
+ * @param {Timestamp} end Second boundary.
1347
+ * @param {boolean=} useTime Include time-of-day when ordering boundaries.
1348
+ * @returns {TimestampRange} Frozen inclusive Timestamp range.
1349
+ */
1350
+ export function createTimestampRange(start, end, useTime = false) {
1351
+ if (compareTimestampOrder(start, end, useTime) <= 0) {
1352
+ return createFrozenRange(start, end);
1353
+ }
1354
+ return createFrozenRange(end, start);
1355
+ }
1356
+ /**
1357
+ * Checks whether a Timestamp falls inside an inclusive TimestampRange.
1358
+ *
1359
+ * @param {Timestamp} timestamp Timestamp object to test.
1360
+ * @param {TimestampRange} range Inclusive range to test against.
1361
+ * @param {boolean=} useTime Include time-of-day in the comparison.
1362
+ * @returns {boolean} True when the timestamp is inside the range.
1363
+ */
1364
+ export function isTimestampInRange(timestamp, range, useTime = false) {
1365
+ return isBetweenDates(timestamp, range.start, range.end, useTime);
1366
+ }
1367
+ /**
1368
+ * Checks whether two inclusive TimestampRange values overlap.
1369
+ *
1370
+ * @param {TimestampRange} first First range.
1371
+ * @param {TimestampRange} second Second range.
1372
+ * @param {boolean=} useTime Include time-of-day in the comparison.
1373
+ * @returns {boolean} True when the ranges overlap.
1374
+ */
1375
+ export function isRangeOverlapping(first, second, useTime = false) {
1376
+ const firstRange = createTimestampRange(first.start, first.end, useTime);
1377
+ const secondRange = createTimestampRange(second.start, second.end, useTime);
1378
+ return (getTimestampSortValue(firstRange.start, useTime) <=
1379
+ getTimestampSortValue(secondRange.end, useTime) &&
1380
+ getTimestampSortValue(secondRange.start, useTime) <=
1381
+ getTimestampSortValue(firstRange.end, useTime));
1382
+ }
1073
1383
  /**
1074
- * Determines if the passed {@link Timestamp} is between (or equal) to two {@link Timestamp}s (range)
1075
- * @param {Timestamp} timestamp The {@link Timestamp} for testing
1076
- * @param {Timestamp} startTimestamp The starting {@link Timestamp}
1077
- * @param {Timestamp} endTimestamp The ending {@link Timestamp}
1078
- * @param {boolean=} useTime If true, use time from the {@link Timestamp}s
1079
- * @returns {boolean} True if {@link Timestamp} is between (or equal) to two {@link Timestamp}s (range)
1384
+ * Returns the inclusive intersection of two TimestampRange values.
1385
+ *
1386
+ * @param {TimestampRange} first First range.
1387
+ * @param {TimestampRange} second Second range.
1388
+ * @param {boolean=} useTime Include time-of-day in the comparison.
1389
+ * @returns {TimestampRange | null} Intersected range, or `null` when the ranges do not overlap.
1390
+ */
1391
+ export function intersectRanges(first, second, useTime = false) {
1392
+ if (isRangeOverlapping(first, second, useTime) === false) {
1393
+ return null;
1394
+ }
1395
+ const start = compareTimestampOrder(first.start, second.start, useTime) >= 0 ? first.start : second.start;
1396
+ const end = compareTimestampOrder(first.end, second.end, useTime) <= 0 ? first.end : second.end;
1397
+ return createTimestampRange(start, end, useTime);
1398
+ }
1399
+ /**
1400
+ * Merges overlapping or touching TimestampRange values.
1401
+ *
1402
+ * Date-only ranges touch when the next range starts on the next calendar day.
1403
+ * Time-aware ranges touch when the next range starts one millisecond after the
1404
+ * previous range ends.
1405
+ *
1406
+ * @param {TimestampRange[]} ranges Ranges to merge.
1407
+ * @param {boolean=} useTime Include time-of-day in the comparison.
1408
+ * @returns {TimestampRange[]} Merged ranges sorted by start boundary.
1409
+ */
1410
+ export function mergeRanges(ranges, useTime = false) {
1411
+ const sorted = ranges
1412
+ .map((range) => createTimestampRange(range.start, range.end, useTime))
1413
+ .sort((a, b) => compareTimestampOrder(a.start, b.start, useTime));
1414
+ const merged = [];
1415
+ for (const range of sorted) {
1416
+ const last = merged[merged.length - 1];
1417
+ if (last === undefined || isRangeTouchingOrOverlapping(last, range, useTime) === false) {
1418
+ merged.push(range);
1419
+ continue;
1420
+ }
1421
+ const end = compareTimestampOrder(last.end, range.end, useTime) >= 0 ? last.end : range.end;
1422
+ merged[merged.length - 1] = createFrozenRange(last.start, end);
1423
+ }
1424
+ return merged;
1425
+ }
1426
+ /**
1427
+ * Subtracts blocked ranges from a source range.
1428
+ *
1429
+ * The result is useful for availability windows because it returns the pieces
1430
+ * of the source range that remain after each blocked range is removed.
1431
+ *
1432
+ * @param {TimestampRange} source Source range.
1433
+ * @param {TimestampRange[]} blocked Ranges to remove from the source.
1434
+ * @param {boolean=} useTime Include time-of-day in the comparison.
1435
+ * @returns {TimestampRange[]} Remaining ranges.
1436
+ */
1437
+ export function subtractRanges(source, blocked, useTime = false) {
1438
+ const normalizedSource = createTimestampRange(source.start, source.end, useTime);
1439
+ const blockers = mergeRanges(blocked, useTime);
1440
+ let available = [normalizedSource];
1441
+ for (const blocker of blockers) {
1442
+ const nextAvailable = [];
1443
+ for (const range of available) {
1444
+ const overlap = intersectRanges(range, blocker, useTime);
1445
+ if (overlap === null) {
1446
+ nextAvailable.push(range);
1447
+ continue;
1448
+ }
1449
+ if (compareTimestampOrder(range.start, overlap.start, useTime) < 0) {
1450
+ nextAvailable.push(createTimestampRange(range.start, moveBoundary(overlap.start, -1, useTime), useTime));
1451
+ }
1452
+ if (compareTimestampOrder(overlap.end, range.end, useTime) < 0) {
1453
+ nextAvailable.push(createTimestampRange(moveBoundary(overlap.end, 1, useTime), range.end, useTime));
1454
+ }
1455
+ }
1456
+ available = nextAvailable;
1457
+ }
1458
+ return available;
1459
+ }
1460
+ /**
1461
+ * Finds open gaps inside a source range after occupied ranges are removed.
1462
+ *
1463
+ * This is an alias for subtractRanges() with naming that reads naturally in
1464
+ * booking, resource, and availability workflows.
1465
+ *
1466
+ * @param {TimestampRange} source Source range.
1467
+ * @param {TimestampRange[]} occupied Ranges that are not available.
1468
+ * @param {boolean=} useTime Include time-of-day in the comparison.
1469
+ * @returns {TimestampRange[]} Gap ranges.
1470
+ */
1471
+ export function findRangeGaps(source, occupied, useTime = false) {
1472
+ return subtractRanges(source, occupied, useTime);
1473
+ }
1474
+ /**
1475
+ * Checks whether a Timestamp falls inside an inclusive range.
1476
+ *
1477
+ * @param {Timestamp} timestamp Timestamp object to test.
1478
+ * @param {Timestamp} startTimestamp Inclusive start boundary.
1479
+ * @param {Timestamp} endTimestamp Inclusive end boundary.
1480
+ * @param {boolean=} useTime Include time-of-day in the comparison when true.
1481
+ * @returns {boolean} True when the timestamp is inside the range.
1080
1482
  */
1081
1483
  export function isBetweenDates(timestamp, startTimestamp, endTimestamp, useTime = false) {
1082
1484
  const cd = getDayIdentifier(timestamp) + (useTime === true ? getTimeIdentifier(timestamp) : 0);
@@ -1085,12 +1487,13 @@ export function isBetweenDates(timestamp, startTimestamp, endTimestamp, useTime
1085
1487
  return cd >= sd && cd <= ed;
1086
1488
  }
1087
1489
  /**
1088
- * Determine if two ranges of {@link Timestamp}s overlap each other
1089
- * @param {Timestamp} startTimestamp The starting {@link Timestamp} of first range
1090
- * @param {Timestamp} endTimestamp The endinging {@link Timestamp} of first range
1091
- * @param {Timestamp} firstTimestamp The starting {@link Timestamp} of second range
1092
- * @param {Timestamp} lastTimestamp The ending {@link Timestamp} of second range
1093
- * @returns {boolean} True if the two ranges overlap each other
1490
+ * Checks whether two inclusive Timestamp ranges overlap.
1491
+ *
1492
+ * @param {Timestamp} startTimestamp Start of the first range.
1493
+ * @param {Timestamp} endTimestamp End of the first range.
1494
+ * @param {Timestamp} firstTimestamp Start of the second range.
1495
+ * @param {Timestamp} lastTimestamp End of the second range.
1496
+ * @returns {boolean} True when the ranges overlap.
1094
1497
  */
1095
1498
  export function isOverlappingDates(startTimestamp, endTimestamp, firstTimestamp, lastTimestamp) {
1096
1499
  const start = getDayIdentifier(startTimestamp);
@@ -1105,11 +1508,12 @@ export function isOverlappingDates(startTimestamp, endTimestamp, firstTimestamp,
1105
1508
  /**
1106
1509
  * Adds or subtracts date/time units from a timestamp.
1107
1510
  *
1108
- * This function returns a new frozen {@link Timestamp}; it does not mutate the
1109
- * timestamp passed in.
1511
+ * This function returns a new frozen Timestamp; it does not mutate the
1512
+ * timestamp passed in. Invalid target dates are normalized through JavaScript
1513
+ * Date rules, so month overflow can roll into the following month.
1110
1514
  *
1111
- * @param {Timestamp} timestamp The {@link Timestamp} object
1112
- * @param {Object} options configuration data
1515
+ * @param {Timestamp} timestamp Timestamp object to offset.
1516
+ * @param {Object} options Date/time units to add or subtract.
1113
1517
  * @param {number=} options.year If positive, adds years. If negative, removes years.
1114
1518
  * @param {number=} options.month If positive, adds months. If negative, removes month.
1115
1519
  * @param {number=} options.day If positive, adds days. If negative, removes days.
@@ -1117,7 +1521,7 @@ export function isOverlappingDates(startTimestamp, endTimestamp, firstTimestamp,
1117
1521
  * @param {number=} options.minute If positive, adds minutes. If negative, removes minutes.
1118
1522
  * @param {number=} options.second If positive, adds seconds. If negative, removes seconds.
1119
1523
  * @param {number=} options.millisecond If positive, adds milliseconds. If negative, removes milliseconds.
1120
- * @returns {Timestamp} A new normalized {@link Timestamp}
1524
+ * @returns {Timestamp} New normalized Timestamp object.
1121
1525
  */
1122
1526
  export function addToDate(timestamp, options) {
1123
1527
  const ts = cloneTimestamp(timestamp);
@@ -1146,11 +1550,11 @@ export function addToDate(timestamp, options) {
1146
1550
  * March. Day and time offsets still use normal JavaScript Date normalization
1147
1551
  * after the year/month clamp is applied.
1148
1552
  *
1149
- * This function returns a new frozen {@link Timestamp}; it does not mutate the
1553
+ * This function returns a new frozen Timestamp; it does not mutate the
1150
1554
  * timestamp passed in.
1151
1555
  *
1152
- * @param {Timestamp} timestamp The {@link Timestamp} object
1153
- * @param {Object} options configuration data
1556
+ * @param {Timestamp} timestamp Timestamp object to offset.
1557
+ * @param {Object} options Date/time units to add or subtract.
1154
1558
  * @param {number=} options.year If positive, adds years. If negative, removes years.
1155
1559
  * @param {number=} options.month If positive, adds months. If negative, removes month.
1156
1560
  * @param {number=} options.day If positive, adds days. If negative, removes days.
@@ -1158,7 +1562,7 @@ export function addToDate(timestamp, options) {
1158
1562
  * @param {number=} options.minute If positive, adds minutes. If negative, removes minutes.
1159
1563
  * @param {number=} options.second If positive, adds seconds. If negative, removes seconds.
1160
1564
  * @param {number=} options.millisecond If positive, adds milliseconds. If negative, removes milliseconds.
1161
- * @returns {Timestamp} A new normalized {@link Timestamp}
1565
+ * @returns {Timestamp} New normalized Timestamp object.
1162
1566
  */
1163
1567
  export function addToDateClamped(timestamp, options) {
1164
1568
  const ts = cloneTimestamp(timestamp);
@@ -1225,9 +1629,9 @@ function normalizeTimestamp(ts) {
1225
1629
  return freezeTimestamp(timestamp);
1226
1630
  }
1227
1631
  /**
1228
- * Returns number of days between two {@link Timestamp}s
1229
- * @param {Timestamp} ts1 The first {@link Timestamp}
1230
- * @param {Timestamp} ts2 The second {@link Timestamp}
1632
+ * Returns number of days between two Timestamps
1633
+ * @param {Timestamp} ts1 The first Timestamp
1634
+ * @param {Timestamp} ts2 The second Timestamp
1231
1635
  * @returns Number of days
1232
1636
  */
1233
1637
  export function daysBetween(ts1, ts2) {
@@ -1235,9 +1639,9 @@ export function daysBetween(ts1, ts2) {
1235
1639
  return Math.floor(diff / TIME_CONSTANTS.MILLISECONDS_IN.DAY);
1236
1640
  }
1237
1641
  /**
1238
- * Returns number of weeks between two {@link Timestamp}s
1239
- * @param {Timestamp} ts1 The first {@link Timestamp}
1240
- * @param {Timestamp} ts2 The second {@link Timestamp}
1642
+ * Returns number of weeks between two Timestamps
1643
+ * @param {Timestamp} ts1 The first Timestamp
1644
+ * @param {Timestamp} ts2 The second Timestamp
1241
1645
  */
1242
1646
  export function weeksBetween(ts1, ts2) {
1243
1647
  let t1 = copyTimestamp(ts1);
@@ -1246,6 +1650,130 @@ export function weeksBetween(ts1, ts2) {
1246
1650
  t2 = findWeekday(t2, 6);
1247
1651
  return Math.ceil(daysBetween(t1, t2) / TIME_CONSTANTS.DAYS_IN.WEEK);
1248
1652
  }
1653
+ /**
1654
+ * Creates a TimestampDuration from signed milliseconds.
1655
+ *
1656
+ * @param {number} milliseconds Signed elapsed milliseconds.
1657
+ * @returns {TimestampDuration} Frozen duration object.
1658
+ */
1659
+ export function createDuration(milliseconds) {
1660
+ const sign = milliseconds === 0 ? 0 : milliseconds < 0 ? -1 : 1;
1661
+ let remaining = Math.abs(milliseconds);
1662
+ const days = Math.floor(remaining / MILLISECONDS_IN_DAY);
1663
+ remaining -= days * MILLISECONDS_IN_DAY;
1664
+ const hours = Math.floor(remaining / MILLISECONDS_IN_HOUR);
1665
+ remaining -= hours * MILLISECONDS_IN_HOUR;
1666
+ const minutes = Math.floor(remaining / MILLISECONDS_IN_MINUTE);
1667
+ remaining -= minutes * MILLISECONDS_IN_MINUTE;
1668
+ const seconds = Math.floor(remaining / MILLISECONDS_IN_SECOND);
1669
+ remaining -= seconds * MILLISECONDS_IN_SECOND;
1670
+ return Object.freeze({
1671
+ totalMilliseconds: milliseconds,
1672
+ absoluteMilliseconds: Math.abs(milliseconds),
1673
+ sign,
1674
+ days,
1675
+ hours,
1676
+ minutes,
1677
+ seconds,
1678
+ milliseconds: remaining,
1679
+ });
1680
+ }
1681
+ /**
1682
+ * Measures the elapsed duration between two Timestamp values.
1683
+ *
1684
+ * Timestamp fields are read as UTC so the result is deterministic across
1685
+ * server and client runtimes.
1686
+ *
1687
+ * @param {Timestamp} start Start timestamp.
1688
+ * @param {Timestamp} end End timestamp.
1689
+ * @returns {TimestampDuration} Frozen duration object.
1690
+ */
1691
+ export function durationBetween(start, end) {
1692
+ return createDuration(toUnixMilliseconds(end) - toUnixMilliseconds(start));
1693
+ }
1694
+ /**
1695
+ * Adds an elapsed duration to a Timestamp.
1696
+ *
1697
+ * This helper treats the Timestamp fields as UTC and returns a Timestamp built
1698
+ * from UTC fields. Use addToDate() for calendar-unit arithmetic such as
1699
+ * "one month from now".
1700
+ *
1701
+ * @param {Timestamp} timestamp Timestamp object to offset.
1702
+ * @param {TimestampDuration | number} duration Duration object or signed milliseconds.
1703
+ * @returns {Timestamp} Offset Timestamp.
1704
+ */
1705
+ export function addDuration(timestamp, duration) {
1706
+ const milliseconds = typeof duration === "number" ? duration : duration.totalMilliseconds;
1707
+ return fromUnixMilliseconds(toUnixMilliseconds(timestamp) + milliseconds);
1708
+ }
1709
+ /**
1710
+ * Subtracts an elapsed duration from a Timestamp.
1711
+ *
1712
+ * @param {Timestamp} timestamp Timestamp object to offset.
1713
+ * @param {TimestampDuration | number} duration Duration object or signed milliseconds.
1714
+ * @returns {Timestamp} Offset Timestamp.
1715
+ */
1716
+ export function subtractDuration(timestamp, duration) {
1717
+ const milliseconds = typeof duration === "number" ? duration : duration.totalMilliseconds;
1718
+ return addDuration(timestamp, -milliseconds);
1719
+ }
1720
+ /**
1721
+ * Formats a duration as `HH:mm:ss` or `HH:mm:ss.SSS`.
1722
+ *
1723
+ * Hours include full days, so a two-day duration formats as `48:00:00`.
1724
+ *
1725
+ * @param {TimestampDuration | number} duration Duration object or signed milliseconds.
1726
+ * @param {FormatDurationOptions=} options Formatting options.
1727
+ * @returns {string} Formatted duration.
1728
+ */
1729
+ export function formatDuration(duration, options = {}) {
1730
+ const value = typeof duration === "number" ? createDuration(duration) : duration;
1731
+ const hours = value.days * HOURS_IN_DAY + value.hours;
1732
+ const sign = options.signed === true && value.sign < 0 ? "-" : "";
1733
+ let formatted = `${sign}${padNumber(hours, 2)}:${padNumber(value.minutes, 2)}:${padNumber(value.seconds, 2)}`;
1734
+ if (options.milliseconds === true) {
1735
+ formatted += `.${padNumber(value.milliseconds, 3)}`;
1736
+ }
1737
+ return formatted;
1738
+ }
1739
+ function roundTimestampToInterval(timestamp, minutes, rounder) {
1740
+ if (minutes <= 0 || Number.isFinite(minutes) === false) {
1741
+ return copyTimestamp(timestamp);
1742
+ }
1743
+ const interval = minutes * MILLISECONDS_IN_MINUTE;
1744
+ const value = toUnixMilliseconds(timestamp);
1745
+ return fromUnixMilliseconds(rounder(value / interval) * interval);
1746
+ }
1747
+ /**
1748
+ * Floors a Timestamp down to the nearest interval.
1749
+ *
1750
+ * @param {Timestamp} timestamp Timestamp object to round.
1751
+ * @param {number} minutes Interval size in minutes.
1752
+ * @returns {Timestamp} Rounded Timestamp.
1753
+ */
1754
+ export function floorToInterval(timestamp, minutes) {
1755
+ return roundTimestampToInterval(timestamp, minutes, Math.floor);
1756
+ }
1757
+ /**
1758
+ * Ceils a Timestamp up to the nearest interval.
1759
+ *
1760
+ * @param {Timestamp} timestamp Timestamp object to round.
1761
+ * @param {number} minutes Interval size in minutes.
1762
+ * @returns {Timestamp} Rounded Timestamp.
1763
+ */
1764
+ export function ceilToInterval(timestamp, minutes) {
1765
+ return roundTimestampToInterval(timestamp, minutes, Math.ceil);
1766
+ }
1767
+ /**
1768
+ * Rounds a Timestamp to the nearest interval.
1769
+ *
1770
+ * @param {Timestamp} timestamp Timestamp object to round.
1771
+ * @param {number} minutes Interval size in minutes.
1772
+ * @returns {Timestamp} Rounded Timestamp.
1773
+ */
1774
+ export function roundToInterval(timestamp, minutes) {
1775
+ return roundTimestampToInterval(timestamp, minutes, Math.round);
1776
+ }
1249
1777
  // Known dates
1250
1778
  const weekdayDateMap = {
1251
1779
  Sun: new Date("2020-01-05T00:00:00.000Z"),
@@ -1286,7 +1814,6 @@ export function getWeekdayFormatter() {
1286
1814
  short: { timeZone: "UTC", weekday: "short" },
1287
1815
  narrow: { timeZone: "UTC", weekday: "narrow" },
1288
1816
  };
1289
- /* istanbul ignore next */
1290
1817
  if (typeof Intl === "undefined" || typeof Intl.DateTimeFormat === "undefined") {
1291
1818
  return emptyFormatter;
1292
1819
  }
@@ -1303,7 +1830,7 @@ export function getWeekdayFormatter() {
1303
1830
  const intlFormatter = new Intl.DateTimeFormat(locale || undefined, resolveIntlNameFormat(options, type));
1304
1831
  return intlFormatter.format(weekdayDateMap[weekday]);
1305
1832
  }
1306
- catch (e) /* istanbul ignore next */ {
1833
+ catch (e) {
1307
1834
  if (e instanceof Error) {
1308
1835
  console.error(`Intl.DateTimeFormat: ${e.message} -> day of week: ${weekday}`);
1309
1836
  }
@@ -1313,11 +1840,11 @@ export function getWeekdayFormatter() {
1313
1840
  return weekdayFormatter;
1314
1841
  }
1315
1842
  /**
1316
- * Retrieves an array of localized weekday names.
1843
+ * Retrieves localized weekday names.
1317
1844
  *
1318
- * @param {string} type - The format type for the weekday names. Can be 'narrow', 'short', or 'long'.
1319
- * @param {string} [locale] - The locale to use for formatting. If not provided, the default locale is used.
1320
- * @returns {string[]} An array of localized weekday names in the specified format.
1845
+ * @param {string} type Format type: `narrow`, `short`, or `long`.
1846
+ * @param {string} locale Locale to use for formatting, such as `en-US`.
1847
+ * @returns {string[]} Localized weekday names in Sunday-first order.
1321
1848
  */
1322
1849
  export function getWeekdayNames(type, locale) {
1323
1850
  const shortWeekdays = Object.keys(weekdayDateMap);
@@ -1343,7 +1870,6 @@ export function getMonthFormatter() {
1343
1870
  short: { timeZone: "UTC", month: "short" },
1344
1871
  narrow: { timeZone: "UTC", month: "narrow" },
1345
1872
  };
1346
- /* istanbul ignore next */
1347
1873
  if (typeof Intl === "undefined" || typeof Intl.DateTimeFormat === "undefined") {
1348
1874
  return emptyFormatter;
1349
1875
  }
@@ -1363,7 +1889,7 @@ export function getMonthFormatter() {
1363
1889
  date.setMonth(month);
1364
1890
  return intlFormatter.format(date);
1365
1891
  }
1366
- catch (e) /* istanbul ignore next */ {
1892
+ catch (e) {
1367
1893
  if (e instanceof Error) {
1368
1894
  console.error(`Intl.DateTimeFormat: ${e.message} -> month: ${month}`);
1369
1895
  }
@@ -1373,94 +1899,14 @@ export function getMonthFormatter() {
1373
1899
  return monthFormatter;
1374
1900
  }
1375
1901
  /**
1376
- * Retrieves an array of localized month names.
1902
+ * Retrieves localized month names.
1377
1903
  *
1378
- * @param {string} type - The format type for the month names. Can be 'narrow', 'short', or 'long'.
1379
- * @param {string} [locale] - The locale to use for formatting. If not provided, the default locale is used.
1380
- * @returns {string[]} An array of localized month names in the specified format.
1904
+ * @param {string} type Format type: `narrow`, `short`, or `long`.
1905
+ * @param {string} locale Locale to use for formatting, such as `en-US`.
1906
+ * @returns {string[]} Localized month names in January-first order.
1381
1907
  */
1382
1908
  export function getMonthNames(type, locale) {
1383
1909
  const monthFormatter = getMonthFormatter();
1384
1910
  return [...Array(12).keys()].map((month) => monthFormatter(month, type, locale));
1385
1911
  }
1386
- // the exports...
1387
- export default {
1388
- PARSE_DATETIME,
1389
- PARSE_DATE,
1390
- PARSE_TIME,
1391
- DAYS_IN_MONTH,
1392
- DAYS_IN_MONTH_LEAP,
1393
- DAYS_IN_MONTH_MIN,
1394
- DAYS_IN_MONTH_MAX,
1395
- MONTH_MAX,
1396
- MONTH_MIN,
1397
- DAY_MIN,
1398
- TIME_CONSTANTS,
1399
- DAYS_IN_WEEK,
1400
- MINUTES_IN_HOUR,
1401
- HOURS_IN_DAY,
1402
- FIRST_HOUR,
1403
- MILLISECONDS_IN_MINUTE,
1404
- MILLISECONDS_IN_HOUR,
1405
- MILLISECONDS_IN_DAY,
1406
- MILLISECONDS_IN_WEEK,
1407
- Timestamp,
1408
- TimeObject,
1409
- today,
1410
- getStartOfWeek,
1411
- getEndOfWeek,
1412
- getStartOfMonth,
1413
- getEndOfMonth,
1414
- parseTime,
1415
- validateTimestamp,
1416
- parsed,
1417
- parseTimestamp,
1418
- parseDate,
1419
- getDayIdentifier,
1420
- getTimeIdentifier,
1421
- getDayTimeIdentifier,
1422
- diffTimestamp,
1423
- updateRelative,
1424
- updateMinutes,
1425
- updateWeekday,
1426
- updateDayOfYear,
1427
- updateWorkWeek,
1428
- updateDisabled,
1429
- updateFormatted,
1430
- getDayOfYear,
1431
- getWorkWeek,
1432
- getWeekday,
1433
- isLeapYear,
1434
- daysInMonth,
1435
- copyTimestamp,
1436
- padNumber,
1437
- getDate,
1438
- getTime,
1439
- getDateTime,
1440
- nextDay,
1441
- prevDay,
1442
- relativeDays,
1443
- findWeekday,
1444
- createDayList,
1445
- createIntervalList,
1446
- createNativeLocaleFormatter,
1447
- makeDate,
1448
- makeDateTime,
1449
- getDateObject,
1450
- validateNumber,
1451
- isBetweenDates,
1452
- isOverlappingDates,
1453
- daysBetween,
1454
- weeksBetween,
1455
- addToDate,
1456
- addToDateClamped,
1457
- compareTimestamps,
1458
- compareDate,
1459
- compareTime,
1460
- compareDateTime,
1461
- getWeekdayFormatter,
1462
- getWeekdayNames,
1463
- getMonthFormatter,
1464
- getMonthNames,
1465
- };
1466
1912
  //# sourceMappingURL=index.js.map