ts-time-utils 4.0.1 → 4.4.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.
Files changed (81) hide show
  1. package/README.md +175 -30
  2. package/dist/{age.js → age.cjs} +14 -6
  3. package/dist/{calculate.js → calculate.cjs} +30 -18
  4. package/dist/{calendar.js → calendar.cjs} +80 -39
  5. package/dist/{calendars.js → calendars.cjs} +48 -23
  6. package/dist/{chain.js → chain.cjs} +41 -40
  7. package/dist/{compare.js → compare.cjs} +58 -28
  8. package/dist/constants.cjs +19 -0
  9. package/dist/{countdown.js → countdown.cjs} +16 -7
  10. package/dist/{cron.js → cron.cjs} +20 -9
  11. package/dist/{dateRange.js → dateRange.cjs} +42 -26
  12. package/dist/{duration.js → duration.cjs} +56 -44
  13. package/dist/esm/chain.js +0 -5
  14. package/dist/esm/finance.d.ts +236 -0
  15. package/dist/esm/finance.d.ts.map +1 -0
  16. package/dist/esm/finance.js +495 -0
  17. package/dist/esm/healthcare.d.ts +260 -0
  18. package/dist/esm/healthcare.d.ts.map +1 -0
  19. package/dist/esm/healthcare.js +447 -0
  20. package/dist/esm/index.d.ts +6 -0
  21. package/dist/esm/index.d.ts.map +1 -1
  22. package/dist/esm/index.js +6 -0
  23. package/dist/esm/naturalLanguage.d.ts +1 -3
  24. package/dist/esm/naturalLanguage.d.ts.map +1 -1
  25. package/dist/esm/naturalLanguage.js +9 -2
  26. package/dist/esm/plugins.d.ts +0 -6
  27. package/dist/esm/plugins.d.ts.map +1 -1
  28. package/dist/esm/plugins.js +36 -42
  29. package/dist/esm/recurrence.d.ts.map +1 -1
  30. package/dist/esm/recurrence.js +3 -5
  31. package/dist/esm/scheduling.d.ts +206 -0
  32. package/dist/esm/scheduling.d.ts.map +1 -0
  33. package/dist/esm/scheduling.js +329 -0
  34. package/dist/esm/timezone.d.ts +6 -1
  35. package/dist/esm/timezone.d.ts.map +1 -1
  36. package/dist/esm/timezone.js +106 -66
  37. package/dist/esm/types.d.ts +0 -4
  38. package/dist/esm/types.d.ts.map +1 -1
  39. package/dist/finance.cjs +512 -0
  40. package/dist/finance.d.ts +236 -0
  41. package/dist/finance.d.ts.map +1 -0
  42. package/dist/{fiscal.js → fiscal.cjs} +36 -17
  43. package/dist/{format.js → format.cjs} +83 -70
  44. package/dist/healthcare.cjs +462 -0
  45. package/dist/healthcare.d.ts +260 -0
  46. package/dist/healthcare.d.ts.map +1 -0
  47. package/dist/{holidays.js → holidays.cjs} +52 -25
  48. package/dist/index.cjs +595 -0
  49. package/dist/index.d.ts +6 -0
  50. package/dist/index.d.ts.map +1 -1
  51. package/dist/{interval.js → interval.cjs} +24 -11
  52. package/dist/{iterate.js → iterate.cjs} +84 -41
  53. package/dist/{locale.js → locale.cjs} +54 -26
  54. package/dist/{naturalLanguage.js → naturalLanguage.cjs} +36 -23
  55. package/dist/naturalLanguage.d.ts +1 -3
  56. package/dist/naturalLanguage.d.ts.map +1 -1
  57. package/dist/{parse.js → parse.cjs} +24 -11
  58. package/dist/{performance.js → performance.cjs} +23 -10
  59. package/dist/{plugins.js → plugins.cjs} +48 -47
  60. package/dist/plugins.d.ts +0 -6
  61. package/dist/plugins.d.ts.map +1 -1
  62. package/dist/{precision.js → precision.cjs} +74 -37
  63. package/dist/{rangePresets.js → rangePresets.cjs} +40 -19
  64. package/dist/{recurrence.js → recurrence.cjs} +27 -21
  65. package/dist/recurrence.d.ts.map +1 -1
  66. package/dist/scheduling.cjs +344 -0
  67. package/dist/scheduling.d.ts +206 -0
  68. package/dist/scheduling.d.ts.map +1 -0
  69. package/dist/{serialize.js → serialize.cjs} +36 -17
  70. package/dist/{temporal.js → temporal.cjs} +28 -13
  71. package/dist/{timezone.js → timezone.cjs} +140 -82
  72. package/dist/timezone.d.ts +6 -1
  73. package/dist/timezone.d.ts.map +1 -1
  74. package/dist/{types.js → types.cjs} +9 -3
  75. package/dist/types.d.ts +0 -4
  76. package/dist/types.d.ts.map +1 -1
  77. package/dist/{validate.js → validate.cjs} +54 -26
  78. package/dist/{workingHours.js → workingHours.cjs} +36 -17
  79. package/package.json +52 -34
  80. package/dist/constants.js +0 -16
  81. package/dist/index.js +0 -66
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scheduling.d.ts","sourceRoot":"","sources":["../src/scheduling.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAK3F,8CAA8C;AAC9C,MAAM,WAAW,gBAAgB;IAC/B,kCAAkC;IAClC,YAAY,CAAC,EAAE,kBAAkB,CAAC;IAClC,kDAAkD;IAClD,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,uCAAuC;IACvC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,0BAA0B;IAC1B,QAAQ,CAAC,EAAE,IAAI,EAAE,CAAC;CACnB;AAED,2CAA2C;AAC3C,MAAM,WAAW,IAAI;IACnB,KAAK,EAAE,IAAI,CAAC;IACZ,GAAG,EAAE,IAAI,CAAC;IACV,SAAS,EAAE,OAAO,CAAC;CACpB;AAED,uCAAuC;AACvC,MAAM,WAAW,OAAQ,SAAQ,SAAS;IACxC,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC;AAED,uCAAuC;AACvC,eAAO,MAAM,yBAAyB,EAAE,gBAKvC,CAAC;AAkBF;;;;;;;;;;;GAWG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,SAAS,EAAE,MAAM,GAAE,gBAAqB,GAAG,IAAI,EAAE,CAqCpF;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,SAAS,EAAE,MAAM,GAAE,gBAAqB,GAAG,IAAI,EAAE,CAe7F;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,SAAS,EACf,QAAQ,EAAE,OAAO,EAAE,EACnB,MAAM,GAAE,gBAAqB,GAC5B,IAAI,EAAE,CAmBR;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,SAAS,EAChB,QAAQ,EAAE,OAAO,EAAE,EACnB,QAAQ,EAAE,MAAM,EAChB,MAAM,GAAE,gBAAqB,GAC5B,IAAI,GAAG,IAAI,CAmBb;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,OAAO,EAAE,GAAG,OAAO,CAE7E;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,OAAO,EAAE,EAAE,QAAQ,EAAE,SAAS,GAAG,OAAO,EAAE,CAEjF;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE,OAAO,EAAE,EAAE,QAAQ,EAAE,SAAS,GAAG,OAAO,CAE7E;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,GAAG,SAAS,CAM3E;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,GAAG,SAAS,CAM9E;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,2BAA2B,CACzC,OAAO,EAAE,cAAc,EACvB,KAAK,EAAE,SAAS,EAChB,MAAM,GAAE,gBAAqB,GAC5B,IAAI,EAAE,CAUR;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,OAAO,EAAE,GAAG,OAAO,EAAE,CAS5D;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE,EAAE,SAAS,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,GAAG,IAAI,CAmBxE"}
@@ -1,10 +1,29 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.serializeDate = serializeDate;
4
+ exports.deserializeDate = deserializeDate;
5
+ exports.createDateReviver = createDateReviver;
6
+ exports.createDateReplacer = createDateReplacer;
7
+ exports.parseISOString = parseISOString;
8
+ exports.toEpochTimestamp = toEpochTimestamp;
9
+ exports.fromEpochTimestamp = fromEpochTimestamp;
10
+ exports.createEpochTimestamp = createEpochTimestamp;
11
+ exports.toDateObject = toDateObject;
12
+ exports.fromDateObject = fromDateObject;
13
+ exports.isValidISODateString = isValidISODateString;
14
+ exports.isValidEpochTimestamp = isValidEpochTimestamp;
15
+ exports.cloneDate = cloneDate;
16
+ exports.datesEqual = datesEqual;
17
+ exports.now = now;
18
+ exports.parseJSONWithDates = parseJSONWithDates;
19
+ exports.stringifyWithDates = stringifyWithDates;
1
20
  /**
2
21
  * Safe JSON date serialization and deserialization utilities
3
22
  */
4
23
  /**
5
24
  * Safely serialize a date to JSON with various format options
6
25
  */
7
- export function serializeDate(date, options = {}) {
26
+ function serializeDate(date, options = {}) {
8
27
  const { format = 'iso', includeTimezone = false, useUTC = false, precision = 'milliseconds', customFormat } = options;
9
28
  const dateObj = normalizeDate(date);
10
29
  if (!dateObj) {
@@ -30,7 +49,7 @@ export function serializeDate(date, options = {}) {
30
49
  /**
31
50
  * Safely deserialize a date from various formats
32
51
  */
33
- export function deserializeDate(serializedDate, options = {}) {
52
+ function deserializeDate(serializedDate, options = {}) {
34
53
  const { useUTC = false } = options;
35
54
  try {
36
55
  if (typeof serializedDate === 'string') {
@@ -59,7 +78,7 @@ export function deserializeDate(serializedDate, options = {}) {
59
78
  /**
60
79
  * Create a safe JSON reviver function for automatic date parsing
61
80
  */
62
- export function createDateReviver(dateKeys = ['createdAt', 'updatedAt', 'date', 'timestamp'], options = {}) {
81
+ function createDateReviver(dateKeys = ['createdAt', 'updatedAt', 'date', 'timestamp'], options = {}) {
63
82
  return (key, value) => {
64
83
  if (dateKeys.includes(key) && (typeof value === 'string' || typeof value === 'number')) {
65
84
  const parsed = deserializeDate(value, options);
@@ -71,7 +90,7 @@ export function createDateReviver(dateKeys = ['createdAt', 'updatedAt', 'date',
71
90
  /**
72
91
  * Create a safe JSON replacer function for automatic date serialization
73
92
  */
74
- export function createDateReplacer(dateKeys = ['createdAt', 'updatedAt', 'date', 'timestamp'], options = {}) {
93
+ function createDateReplacer(dateKeys = ['createdAt', 'updatedAt', 'date', 'timestamp'], options = {}) {
75
94
  return (key, value) => {
76
95
  if (dateKeys.includes(key)) {
77
96
  if (value instanceof Date) {
@@ -91,7 +110,7 @@ export function createDateReplacer(dateKeys = ['createdAt', 'updatedAt', 'date',
91
110
  /**
92
111
  * Parse ISO string with better error handling
93
112
  */
94
- export function parseISOString(isoString, useUTC = false) {
113
+ function parseISOString(isoString, useUTC = false) {
95
114
  if (!isoString || typeof isoString !== 'string') {
96
115
  return null;
97
116
  }
@@ -120,7 +139,7 @@ export function parseISOString(isoString, useUTC = false) {
120
139
  /**
121
140
  * Convert date to epoch timestamp with specified precision
122
141
  */
123
- export function toEpochTimestamp(date, precision = 'milliseconds') {
142
+ function toEpochTimestamp(date, precision = 'milliseconds') {
124
143
  const dateObj = normalizeDate(date);
125
144
  if (!dateObj) {
126
145
  throw new Error('Invalid date provided for epoch conversion');
@@ -139,7 +158,7 @@ export function toEpochTimestamp(date, precision = 'milliseconds') {
139
158
  /**
140
159
  * Create date from epoch timestamp with specified precision
141
160
  */
142
- export function fromEpochTimestamp(timestamp, precision = 'milliseconds') {
161
+ function fromEpochTimestamp(timestamp, precision = 'milliseconds') {
143
162
  let ms;
144
163
  switch (precision) {
145
164
  case 'seconds':
@@ -158,7 +177,7 @@ export function fromEpochTimestamp(timestamp, precision = 'milliseconds') {
158
177
  /**
159
178
  * Create epoch timestamp with metadata
160
179
  */
161
- export function createEpochTimestamp(date, precision = 'milliseconds', timezone) {
180
+ function createEpochTimestamp(date, precision = 'milliseconds', timezone) {
162
181
  return {
163
182
  timestamp: toEpochTimestamp(date, precision),
164
183
  precision,
@@ -168,7 +187,7 @@ export function createEpochTimestamp(date, precision = 'milliseconds', timezone)
168
187
  /**
169
188
  * Convert date to safe object representation
170
189
  */
171
- export function toDateObject(date, includeTimezone = false) {
190
+ function toDateObject(date, includeTimezone = false) {
172
191
  const dateObj = normalizeDate(date);
173
192
  if (!dateObj) {
174
193
  throw new Error('Invalid date provided for object conversion');
@@ -195,7 +214,7 @@ export function toDateObject(date, includeTimezone = false) {
195
214
  /**
196
215
  * Create date from object representation
197
216
  */
198
- export function fromDateObject(dateObj) {
217
+ function fromDateObject(dateObj) {
199
218
  // Validate required fields
200
219
  if (!dateObj || typeof dateObj !== 'object') {
201
220
  throw new Error('Invalid date object provided');
@@ -228,7 +247,7 @@ export function fromDateObject(dateObj) {
228
247
  /**
229
248
  * Check if a string is a valid ISO date string for serialization
230
249
  */
231
- export function isValidISODateString(dateString) {
250
+ function isValidISODateString(dateString) {
232
251
  if (!dateString || typeof dateString !== 'string') {
233
252
  return false;
234
253
  }
@@ -238,7 +257,7 @@ export function isValidISODateString(dateString) {
238
257
  /**
239
258
  * Check if a number is a valid epoch timestamp
240
259
  */
241
- export function isValidEpochTimestamp(timestamp, precision = 'milliseconds') {
260
+ function isValidEpochTimestamp(timestamp, precision = 'milliseconds') {
242
261
  if (typeof timestamp !== 'number' || isNaN(timestamp)) {
243
262
  return false;
244
263
  }
@@ -265,14 +284,14 @@ export function isValidEpochTimestamp(timestamp, precision = 'milliseconds') {
265
284
  /**
266
285
  * Clone a date safely (avoids reference issues)
267
286
  */
268
- export function cloneDate(date) {
287
+ function cloneDate(date) {
269
288
  const dateObj = normalizeDate(date);
270
289
  return dateObj ? new Date(dateObj.getTime()) : null;
271
290
  }
272
291
  /**
273
292
  * Compare two dates for equality (ignoring milliseconds if specified)
274
293
  */
275
- export function datesEqual(date1, date2, precision = 'milliseconds') {
294
+ function datesEqual(date1, date2, precision = 'milliseconds') {
276
295
  const d1 = normalizeDate(date1);
277
296
  const d2 = normalizeDate(date2);
278
297
  if (!d1 || !d2) {
@@ -295,7 +314,7 @@ export function datesEqual(date1, date2, precision = 'milliseconds') {
295
314
  /**
296
315
  * Get current timestamp in various formats
297
316
  */
298
- export function now(format = 'date') {
317
+ function now(format = 'date') {
299
318
  const current = new Date();
300
319
  switch (format) {
301
320
  case 'iso':
@@ -312,7 +331,7 @@ export function now(format = 'date') {
312
331
  /**
313
332
  * Safely handle JSON parsing with date conversion
314
333
  */
315
- export function parseJSONWithDates(jsonString, dateKeys, options) {
334
+ function parseJSONWithDates(jsonString, dateKeys, options) {
316
335
  try {
317
336
  return JSON.parse(jsonString, createDateReviver(dateKeys, options));
318
337
  }
@@ -323,7 +342,7 @@ export function parseJSONWithDates(jsonString, dateKeys, options) {
323
342
  /**
324
343
  * Safely handle JSON stringification with date conversion
325
344
  */
326
- export function stringifyWithDates(obj, dateKeys, options, space) {
345
+ function stringifyWithDates(obj, dateKeys, options, space) {
327
346
  try {
328
347
  return JSON.stringify(obj, createDateReplacer(dateKeys, options), space);
329
348
  }
@@ -1,8 +1,23 @@
1
+ "use strict";
1
2
  /**
2
3
  * @fileoverview Temporal API compatibility layer
3
4
  * Provides Temporal-like objects that work with native Date
4
5
  * When Temporal ships natively, these become thin wrappers
5
6
  */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.toPlainDate = toPlainDate;
9
+ exports.toPlainTime = toPlainTime;
10
+ exports.toPlainDateTime = toPlainDateTime;
11
+ exports.toZonedDateTime = toZonedDateTime;
12
+ exports.toInstant = toInstant;
13
+ exports.createDuration = createDuration;
14
+ exports.parseDuration = parseDuration;
15
+ exports.nowInstant = nowInstant;
16
+ exports.nowPlainDateTime = nowPlainDateTime;
17
+ exports.nowPlainDate = nowPlainDate;
18
+ exports.nowPlainTime = nowPlainTime;
19
+ exports.nowZonedDateTime = nowZonedDateTime;
20
+ exports.fromTemporal = fromTemporal;
6
21
  // Helper functions
7
22
  function getWeekNumber(date) {
8
23
  const d = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()));
@@ -552,7 +567,7 @@ class InstantImpl {
552
567
  return new Date(this.epochMilliseconds);
553
568
  }
554
569
  }
555
- export function toPlainDate(dateOrYear, month, day) {
570
+ function toPlainDate(dateOrYear, month, day) {
556
571
  if (dateOrYear instanceof Date) {
557
572
  return new PlainDateImpl(dateOrYear.getFullYear(), dateOrYear.getMonth() + 1, dateOrYear.getDate());
558
573
  }
@@ -561,7 +576,7 @@ export function toPlainDate(dateOrYear, month, day) {
561
576
  }
562
577
  return new PlainDateImpl(dateOrYear, month, day);
563
578
  }
564
- export function toPlainTime(dateOrHour, minute, second, millisecond) {
579
+ function toPlainTime(dateOrHour, minute, second, millisecond) {
565
580
  if (dateOrHour instanceof Date) {
566
581
  return new PlainTimeImpl(dateOrHour.getHours(), dateOrHour.getMinutes(), dateOrHour.getSeconds(), dateOrHour.getMilliseconds());
567
582
  }
@@ -570,7 +585,7 @@ export function toPlainTime(dateOrHour, minute, second, millisecond) {
570
585
  }
571
586
  return new PlainTimeImpl(dateOrHour, minute, second, millisecond);
572
587
  }
573
- export function toPlainDateTime(dateOrYear, month, day, hour, minute, second, millisecond) {
588
+ function toPlainDateTime(dateOrYear, month, day, hour, minute, second, millisecond) {
574
589
  if (dateOrYear instanceof Date) {
575
590
  return new PlainDateTimeImpl(dateOrYear.getFullYear(), dateOrYear.getMonth() + 1, dateOrYear.getDate(), dateOrYear.getHours(), dateOrYear.getMinutes(), dateOrYear.getSeconds(), dateOrYear.getMilliseconds());
576
591
  }
@@ -582,10 +597,10 @@ export function toPlainDateTime(dateOrYear, month, day, hour, minute, second, mi
582
597
  /**
583
598
  * Create a ZonedDateTime from a Date object and timezone
584
599
  */
585
- export function toZonedDateTime(date, timeZone) {
600
+ function toZonedDateTime(date, timeZone) {
586
601
  return new ZonedDateTimeImpl(date.getTime(), timeZone);
587
602
  }
588
- export function toInstant(dateOrEpoch) {
603
+ function toInstant(dateOrEpoch) {
589
604
  if (dateOrEpoch instanceof Date) {
590
605
  return new InstantImpl(dateOrEpoch.getTime());
591
606
  }
@@ -597,13 +612,13 @@ export function toInstant(dateOrEpoch) {
597
612
  /**
598
613
  * Create a Duration from components
599
614
  */
600
- export function createDuration(fields = {}) {
615
+ function createDuration(fields = {}) {
601
616
  return new DurationImpl(fields);
602
617
  }
603
618
  /**
604
619
  * Parse an ISO 8601 duration string
605
620
  */
606
- export function parseDuration(str) {
621
+ function parseDuration(str) {
607
622
  const match = str.match(/^(-)?P(?:(\d+)Y)?(?:(\d+)M)?(?:(\d+)W)?(?:(\d+)D)?(?:T(?:(\d+)H)?(?:(\d+)M)?(?:(\d+(?:\.\d+)?)S)?)?$/);
608
623
  if (!match)
609
624
  throw new Error(`Invalid duration: ${str}`);
@@ -625,36 +640,36 @@ export function parseDuration(str) {
625
640
  /**
626
641
  * Get current instant
627
642
  */
628
- export function nowInstant() {
643
+ function nowInstant() {
629
644
  return new InstantImpl(Date.now());
630
645
  }
631
646
  /**
632
647
  * Get current PlainDateTime in local timezone
633
648
  */
634
- export function nowPlainDateTime() {
649
+ function nowPlainDateTime() {
635
650
  return toPlainDateTime(new Date());
636
651
  }
637
652
  /**
638
653
  * Get current PlainDate in local timezone
639
654
  */
640
- export function nowPlainDate() {
655
+ function nowPlainDate() {
641
656
  return toPlainDate(new Date());
642
657
  }
643
658
  /**
644
659
  * Get current PlainTime in local timezone
645
660
  */
646
- export function nowPlainTime() {
661
+ function nowPlainTime() {
647
662
  return toPlainTime(new Date());
648
663
  }
649
664
  /**
650
665
  * Get current ZonedDateTime in specified timezone
651
666
  */
652
- export function nowZonedDateTime(timeZone) {
667
+ function nowZonedDateTime(timeZone) {
653
668
  return toZonedDateTime(new Date(), timeZone);
654
669
  }
655
670
  /**
656
671
  * Convert Temporal-like object back to Date
657
672
  */
658
- export function fromTemporal(temporal) {
673
+ function fromTemporal(temporal) {
659
674
  return temporal.toDate();
660
675
  }
@@ -1,8 +1,40 @@
1
+ "use strict";
1
2
  /**
2
3
  * Timezone utilities using Intl API with fallbacks
3
4
  */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.COMMON_TIMEZONES = void 0;
7
+ exports.getTimezoneOffset = getTimezoneOffset;
8
+ exports.formatInTimeZone = formatInTimeZone;
9
+ exports.getZonedTime = getZonedTime;
10
+ exports.convertDateToZone = convertDateToZone;
11
+ exports.isValidTimeZone = isValidTimeZone;
12
+ exports.getLocalOffset = getLocalOffset;
13
+ exports.compareZoneOffsets = compareZoneOffsets;
14
+ exports.reinterpretAsZone = reinterpretAsZone;
15
+ exports.isDST = isDST;
16
+ exports.getNextDSTTransition = getNextDSTTransition;
17
+ exports.findCommonWorkingHours = findCommonWorkingHours;
18
+ exports.getTimezoneAbbreviation = getTimezoneAbbreviation;
19
+ exports.convertBetweenZones = convertBetweenZones;
20
+ exports.getTimezoneDifferenceHours = getTimezoneDifferenceHours;
21
+ exports.isSameTimezone = isSameTimezone;
22
+ const DAY_MS = 24 * 60 * 60 * 1000;
23
+ const DAY_HOURS = 24;
24
+ const YEAR_MONTHS = Array.from({ length: 12 }, (_, month) => month);
25
+ function normalizeHourValue(hour) {
26
+ return ((hour % DAY_HOURS) + DAY_HOURS) % DAY_HOURS;
27
+ }
28
+ function pushSweepInterval(events, start, end) {
29
+ const clampedStart = Math.max(0, start);
30
+ const clampedEnd = Math.min(DAY_HOURS * 2, end);
31
+ if (clampedStart >= clampedEnd)
32
+ return;
33
+ events.push({ time: clampedStart, delta: 1 });
34
+ events.push({ time: clampedEnd, delta: -1 });
35
+ }
4
36
  /** Get offset (minutes) for a zone at a given date */
5
- export function getTimezoneOffset(zone, date = new Date()) {
37
+ function getTimezoneOffset(zone, date = new Date()) {
6
38
  try {
7
39
  const dtf = new Intl.DateTimeFormat('en-US', { timeZone: zone, timeZoneName: 'shortOffset', hour: '2-digit' });
8
40
  const parts = dtf.formatToParts(date);
@@ -23,19 +55,19 @@ export function getTimezoneOffset(zone, date = new Date()) {
23
55
  }
24
56
  }
25
57
  /** Format date/time in a zone */
26
- export function formatInTimeZone(date, zone, options = {}) {
58
+ function formatInTimeZone(date, zone, options = {}) {
27
59
  const fmt = new Intl.DateTimeFormat('en-US', { timeZone: zone, ...options });
28
60
  return fmt.format(date);
29
61
  }
30
62
  /** Get a lightweight ZonedTime object */
31
- export function getZonedTime(date, zone) {
63
+ function getZonedTime(date, zone) {
32
64
  const offset = getTimezoneOffset(zone, date);
33
65
  if (offset == null)
34
66
  return null;
35
67
  return { date: new Date(date), zone, offsetMinutes: offset };
36
68
  }
37
69
  /** Convert a date (treated as absolute moment) to another zone's clock components */
38
- export function convertDateToZone(date, zone) {
70
+ function convertDateToZone(date, zone) {
39
71
  try {
40
72
  const fmt = new Intl.DateTimeFormat('en-US', {
41
73
  timeZone: zone,
@@ -58,7 +90,7 @@ export function convertDateToZone(date, zone) {
58
90
  }
59
91
  }
60
92
  /** Check if provided zone string is a valid IANA zone */
61
- export function isValidTimeZone(zone) {
93
+ function isValidTimeZone(zone) {
62
94
  try {
63
95
  new Intl.DateTimeFormat('en-US', { timeZone: zone });
64
96
  return true;
@@ -68,18 +100,18 @@ export function isValidTimeZone(zone) {
68
100
  }
69
101
  }
70
102
  /** List a subset of common timezones (cannot enumerate all via API) */
71
- export const COMMON_TIMEZONES = [
103
+ exports.COMMON_TIMEZONES = [
72
104
  'UTC', 'Etc/UTC', 'Europe/London', 'Europe/Paris', 'Europe/Berlin', 'Europe/Madrid', 'Europe/Rome',
73
105
  'America/New_York', 'America/Chicago', 'America/Denver', 'America/Los_Angeles', 'America/Toronto', 'America/Sao_Paulo',
74
106
  'Asia/Tehran', 'Asia/Dubai', 'Asia/Tokyo', 'Asia/Shanghai', 'Asia/Singapore', 'Asia/Kolkata', 'Asia/Hong_Kong',
75
107
  'Australia/Sydney', 'Pacific/Auckland'
76
108
  ];
77
109
  /** Get current local offset in minutes */
78
- export function getLocalOffset() {
110
+ function getLocalOffset() {
79
111
  return -new Date().getTimezoneOffset();
80
112
  }
81
113
  /** Compare two timezones offset at given date */
82
- export function compareZoneOffsets(zoneA, zoneB, date = new Date()) {
114
+ function compareZoneOffsets(zoneA, zoneB, date = new Date()) {
83
115
  const a = getTimezoneOffset(zoneA, date);
84
116
  const b = getTimezoneOffset(zoneB, date);
85
117
  if (a == null || b == null)
@@ -91,7 +123,7 @@ export function compareZoneOffsets(zoneA, zoneB, date = new Date()) {
91
123
  * the same wall clock time in the target zone interpreted as local.
92
124
  * For example useful for naive scheduling.
93
125
  */
94
- export function reinterpretAsZone(date, targetZone) {
126
+ function reinterpretAsZone(date, targetZone) {
95
127
  const target = convertDateToZone(date, targetZone);
96
128
  if (!target)
97
129
  return null;
@@ -99,30 +131,35 @@ export function reinterpretAsZone(date, targetZone) {
99
131
  }
100
132
  /**
101
133
  * Check if a date is in Daylight Saving Time for a given timezone
134
+ * Uses a yearly-offset heuristic: sample the zone's local year and treat the
135
+ * maximum observed UTC offset as the DST offset for that year.
102
136
  * @param date - date to check
103
137
  * @param zone - IANA timezone string
104
138
  */
105
- export function isDST(date, zone) {
139
+ function isDST(date, zone) {
106
140
  if (!isValidTimeZone(zone))
107
141
  return null;
108
- // Compare offset in January vs July - the one with larger offset is DST
109
- const january = new Date(date.getFullYear(), 0, 1);
110
- const july = new Date(date.getFullYear(), 6, 1);
111
- const janOffset = getTimezoneOffset(zone, january);
112
- const julOffset = getTimezoneOffset(zone, july);
142
+ const zonedDate = convertDateToZone(date, zone);
143
+ if (!zonedDate) {
144
+ return null;
145
+ }
113
146
  const currentOffset = getTimezoneOffset(zone, date);
114
- if (janOffset === null || julOffset === null || currentOffset === null) {
147
+ if (currentOffset === null) {
115
148
  return null;
116
149
  }
117
- // If offsets are the same, no DST in this zone
118
- if (janOffset === julOffset) {
150
+ const yearlyOffsets = new Set();
151
+ for (const month of YEAR_MONTHS) {
152
+ const sample = new Date(Date.UTC(zonedDate.year, month, 1, 12, 0, 0));
153
+ const offset = getTimezoneOffset(zone, sample);
154
+ if (offset === null) {
155
+ return null;
156
+ }
157
+ yearlyOffsets.add(offset);
158
+ }
159
+ if (yearlyOffsets.size <= 1) {
119
160
  return false;
120
161
  }
121
- // In northern hemisphere, summer (July) has larger offset
122
- // In southern hemisphere, summer (January) has larger offset
123
- // DST is whichever is the "larger" offset
124
- const maxOffset = Math.max(janOffset, julOffset);
125
- return currentOffset === maxOffset;
162
+ return currentOffset === Math.max(...yearlyOffsets);
126
163
  }
127
164
  /**
128
165
  * Get the next DST transition (if any) for a timezone
@@ -130,49 +167,38 @@ export function isDST(date, zone) {
130
167
  * @param zone - IANA timezone string
131
168
  * @returns next DST transition date or null if no DST in that zone
132
169
  */
133
- export function getNextDSTTransition(date, zone) {
170
+ function getNextDSTTransition(date, zone) {
134
171
  if (!isValidTimeZone(zone))
135
172
  return null;
136
- const january = new Date(date.getFullYear(), 0, 1);
137
- const july = new Date(date.getFullYear(), 6, 1);
138
- const janOffset = getTimezoneOffset(zone, january);
139
- const julOffset = getTimezoneOffset(zone, july);
140
- if (janOffset === null || julOffset === null)
141
- return null;
142
- // No DST if offsets are the same
143
- if (janOffset === julOffset)
144
- return null;
145
- // Binary search for the transition within the next year
146
173
  const currentOffset = getTimezoneOffset(zone, date);
147
174
  if (currentOffset === null)
148
175
  return null;
149
- // Check day by day for up to 366 days
150
- const searchDate = new Date(date);
151
- for (let i = 1; i <= 366; i++) {
152
- searchDate.setDate(searchDate.getDate() + 1);
153
- const newOffset = getTimezoneOffset(zone, searchDate);
154
- if (newOffset !== null && newOffset !== currentOffset) {
155
- // Found a transition, now narrow it down
156
- const prevDay = new Date(searchDate);
157
- prevDay.setDate(prevDay.getDate() - 1);
158
- // Binary search within the day
159
- let low = prevDay.getTime();
160
- let high = searchDate.getTime();
161
- while (high - low > 60000) { // 1 minute precision
162
- const mid = Math.floor((low + high) / 2);
163
- const midDate = new Date(mid);
164
- const midOffset = getTimezoneOffset(zone, midDate);
165
- if (midOffset === currentOffset) {
166
- low = mid;
167
- }
168
- else {
169
- high = mid;
170
- }
171
- }
172
- return new Date(high);
176
+ const startTime = date.getTime() + 1;
177
+ const searchLimit = startTime + 366 * DAY_MS;
178
+ let low = startTime;
179
+ let high = null;
180
+ for (let probeTime = startTime + DAY_MS; probeTime <= searchLimit; probeTime += DAY_MS) {
181
+ const probeOffset = getTimezoneOffset(zone, new Date(probeTime));
182
+ if (probeOffset !== null && probeOffset !== currentOffset) {
183
+ low = probeTime - DAY_MS;
184
+ high = probeTime;
185
+ break;
173
186
  }
174
187
  }
175
- return null;
188
+ if (high === null) {
189
+ return null;
190
+ }
191
+ while (high - low > 1) {
192
+ const mid = Math.floor((low + high) / 2);
193
+ const midOffset = getTimezoneOffset(zone, new Date(mid));
194
+ if (midOffset === currentOffset) {
195
+ low = mid;
196
+ }
197
+ else {
198
+ high = mid;
199
+ }
200
+ }
201
+ return new Date(high);
176
202
  }
177
203
  /**
178
204
  * Find overlapping working hours between multiple timezones
@@ -180,39 +206,71 @@ export function getNextDSTTransition(date, zone) {
180
206
  * @param workHoursStart - work hours start (0-24)
181
207
  * @param workHoursEnd - work hours end (0-24)
182
208
  * @param date - reference date (default: today)
183
- * @returns array of overlapping hour ranges in UTC, or null if no overlap
209
+ * @returns one contiguous UTC overlap window, specifically the longest
210
+ * contiguous overlap slice. A full-day overlap is returned as
211
+ * `{ startUTC: 0, endUTC: 24 }`, and wrapped overlaps are returned with
212
+ * `endUTC` normalized back into the 0-24 range and may be less than `startUTC`
184
213
  */
185
- export function findCommonWorkingHours(zones, workHoursStart = 9, workHoursEnd = 17, date = new Date()) {
214
+ function findCommonWorkingHours(zones, workHoursStart = 9, workHoursEnd = 17, date = new Date()) {
186
215
  if (zones.length === 0)
187
216
  return null;
188
- // Convert each zone's work hours to UTC
189
- const utcRanges = zones.map(zone => {
217
+ const endHour = workHoursEnd < workHoursStart ? workHoursEnd + DAY_HOURS : workHoursEnd;
218
+ const duration = endHour - workHoursStart;
219
+ if (duration <= 0) {
220
+ return null;
221
+ }
222
+ const sweepEvents = [];
223
+ for (const zone of zones) {
190
224
  const offset = getTimezoneOffset(zone, date);
191
- if (offset === null)
225
+ if (offset === null) {
192
226
  return null;
193
- // Offset is in minutes, positive means ahead of UTC
194
- // So to convert local time to UTC, we subtract the offset
195
- const startUTC = workHoursStart - (offset / 60);
196
- const endUTC = workHoursEnd - (offset / 60);
197
- return { startUTC, endUTC };
198
- });
199
- if (utcRanges.some(r => r === null))
227
+ }
228
+ if (duration >= DAY_HOURS) {
229
+ pushSweepInterval(sweepEvents, 0, DAY_HOURS * 2);
230
+ continue;
231
+ }
232
+ const startUTC = normalizeHourValue(workHoursStart - (offset / 60));
233
+ pushSweepInterval(sweepEvents, startUTC, startUTC + duration);
234
+ pushSweepInterval(sweepEvents, startUTC + DAY_HOURS, startUTC + DAY_HOURS + duration);
235
+ }
236
+ sweepEvents.sort((a, b) => a.time - b.time || b.delta - a.delta);
237
+ let activeWindows = 0;
238
+ let previousTime = 0;
239
+ let bestOverlap = null;
240
+ for (let i = 0; i < sweepEvents.length;) {
241
+ const currentTime = sweepEvents[i].time;
242
+ if (currentTime > previousTime && activeWindows === zones.length) {
243
+ const candidate = { start: previousTime, end: currentTime };
244
+ if (bestOverlap === null ||
245
+ candidate.end - candidate.start > bestOverlap.end - bestOverlap.start ||
246
+ (candidate.end - candidate.start === bestOverlap.end - bestOverlap.start &&
247
+ candidate.start < bestOverlap.start)) {
248
+ bestOverlap = candidate;
249
+ }
250
+ }
251
+ while (i < sweepEvents.length && sweepEvents[i].time === currentTime) {
252
+ activeWindows += sweepEvents[i].delta;
253
+ i++;
254
+ }
255
+ previousTime = currentTime;
256
+ }
257
+ if (bestOverlap === null) {
200
258
  return null;
201
- const validRanges = utcRanges;
202
- // Find intersection of all ranges
203
- let overlapStart = Math.max(...validRanges.map(r => r.startUTC));
204
- let overlapEnd = Math.min(...validRanges.map(r => r.endUTC));
205
- if (overlapStart >= overlapEnd) {
206
- return null; // No overlap
207
259
  }
208
- return { startUTC: overlapStart, endUTC: overlapEnd };
260
+ const overlapDuration = bestOverlap.end - bestOverlap.start;
261
+ if (overlapDuration >= DAY_HOURS) {
262
+ return { startUTC: 0, endUTC: DAY_HOURS };
263
+ }
264
+ const startUTC = normalizeHourValue(bestOverlap.start);
265
+ const endUTC = normalizeHourValue(bestOverlap.start + overlapDuration);
266
+ return { startUTC, endUTC };
209
267
  }
210
268
  /**
211
269
  * Get all timezone abbreviations for a zone on a given date
212
270
  * @param zone - IANA timezone string
213
271
  * @param date - reference date
214
272
  */
215
- export function getTimezoneAbbreviation(zone, date = new Date()) {
273
+ function getTimezoneAbbreviation(zone, date = new Date()) {
216
274
  try {
217
275
  const fmt = new Intl.DateTimeFormat('en-US', {
218
276
  timeZone: zone,
@@ -232,7 +290,7 @@ export function getTimezoneAbbreviation(zone, date = new Date()) {
232
290
  * @param fromZone - source timezone
233
291
  * @param toZone - target timezone
234
292
  */
235
- export function convertBetweenZones(date, fromZone, toZone) {
293
+ function convertBetweenZones(date, fromZone, toZone) {
236
294
  // First, interpret the date as being in fromZone
237
295
  const fromOffset = getTimezoneOffset(fromZone, date);
238
296
  const toOffset = getTimezoneOffset(toZone, date);
@@ -250,7 +308,7 @@ export function convertBetweenZones(date, fromZone, toZone) {
250
308
  * @param zoneB - second timezone
251
309
  * @param date - reference date
252
310
  */
253
- export function getTimezoneDifferenceHours(zoneA, zoneB, date = new Date()) {
311
+ function getTimezoneDifferenceHours(zoneA, zoneB, date = new Date()) {
254
312
  const diff = compareZoneOffsets(zoneA, zoneB, date);
255
313
  if (diff === null)
256
314
  return null;
@@ -262,7 +320,7 @@ export function getTimezoneDifferenceHours(zoneA, zoneB, date = new Date()) {
262
320
  * @param zoneB - second timezone
263
321
  * @param date - reference date
264
322
  */
265
- export function isSameTimezone(zoneA, zoneB, date = new Date()) {
323
+ function isSameTimezone(zoneA, zoneB, date = new Date()) {
266
324
  const diff = compareZoneOffsets(zoneA, zoneB, date);
267
325
  if (diff === null)
268
326
  return null;
@@ -33,6 +33,8 @@ export declare function compareZoneOffsets(zoneA: string, zoneB: string, date?:
33
33
  export declare function reinterpretAsZone(date: Date, targetZone: string): Date | null;
34
34
  /**
35
35
  * Check if a date is in Daylight Saving Time for a given timezone
36
+ * Uses a yearly-offset heuristic: sample the zone's local year and treat the
37
+ * maximum observed UTC offset as the DST offset for that year.
36
38
  * @param date - date to check
37
39
  * @param zone - IANA timezone string
38
40
  */
@@ -50,7 +52,10 @@ export declare function getNextDSTTransition(date: Date, zone: string): Date | n
50
52
  * @param workHoursStart - work hours start (0-24)
51
53
  * @param workHoursEnd - work hours end (0-24)
52
54
  * @param date - reference date (default: today)
53
- * @returns array of overlapping hour ranges in UTC, or null if no overlap
55
+ * @returns one contiguous UTC overlap window, specifically the longest
56
+ * contiguous overlap slice. A full-day overlap is returned as
57
+ * `{ startUTC: 0, endUTC: 24 }`, and wrapped overlaps are returned with
58
+ * `endUTC` normalized back into the 0-24 range and may be less than `startUTC`
54
59
  */
55
60
  export declare function findCommonWorkingHours(zones: string[], workHoursStart?: number, workHoursEnd?: number, date?: Date): {
56
61
  startUTC: number;