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.
- package/README.md +175 -30
- package/dist/{age.js → age.cjs} +14 -6
- package/dist/{calculate.js → calculate.cjs} +30 -18
- package/dist/{calendar.js → calendar.cjs} +80 -39
- package/dist/{calendars.js → calendars.cjs} +48 -23
- package/dist/{chain.js → chain.cjs} +41 -40
- package/dist/{compare.js → compare.cjs} +58 -28
- package/dist/constants.cjs +19 -0
- package/dist/{countdown.js → countdown.cjs} +16 -7
- package/dist/{cron.js → cron.cjs} +20 -9
- package/dist/{dateRange.js → dateRange.cjs} +42 -26
- package/dist/{duration.js → duration.cjs} +56 -44
- package/dist/esm/chain.js +0 -5
- package/dist/esm/finance.d.ts +236 -0
- package/dist/esm/finance.d.ts.map +1 -0
- package/dist/esm/finance.js +495 -0
- package/dist/esm/healthcare.d.ts +260 -0
- package/dist/esm/healthcare.d.ts.map +1 -0
- package/dist/esm/healthcare.js +447 -0
- package/dist/esm/index.d.ts +6 -0
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +6 -0
- package/dist/esm/naturalLanguage.d.ts +1 -3
- package/dist/esm/naturalLanguage.d.ts.map +1 -1
- package/dist/esm/naturalLanguage.js +9 -2
- package/dist/esm/plugins.d.ts +0 -6
- package/dist/esm/plugins.d.ts.map +1 -1
- package/dist/esm/plugins.js +36 -42
- package/dist/esm/recurrence.d.ts.map +1 -1
- package/dist/esm/recurrence.js +3 -5
- package/dist/esm/scheduling.d.ts +206 -0
- package/dist/esm/scheduling.d.ts.map +1 -0
- package/dist/esm/scheduling.js +329 -0
- package/dist/esm/timezone.d.ts +6 -1
- package/dist/esm/timezone.d.ts.map +1 -1
- package/dist/esm/timezone.js +106 -66
- package/dist/esm/types.d.ts +0 -4
- package/dist/esm/types.d.ts.map +1 -1
- package/dist/finance.cjs +512 -0
- package/dist/finance.d.ts +236 -0
- package/dist/finance.d.ts.map +1 -0
- package/dist/{fiscal.js → fiscal.cjs} +36 -17
- package/dist/{format.js → format.cjs} +83 -70
- package/dist/healthcare.cjs +462 -0
- package/dist/healthcare.d.ts +260 -0
- package/dist/healthcare.d.ts.map +1 -0
- package/dist/{holidays.js → holidays.cjs} +52 -25
- package/dist/index.cjs +595 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/{interval.js → interval.cjs} +24 -11
- package/dist/{iterate.js → iterate.cjs} +84 -41
- package/dist/{locale.js → locale.cjs} +54 -26
- package/dist/{naturalLanguage.js → naturalLanguage.cjs} +36 -23
- package/dist/naturalLanguage.d.ts +1 -3
- package/dist/naturalLanguage.d.ts.map +1 -1
- package/dist/{parse.js → parse.cjs} +24 -11
- package/dist/{performance.js → performance.cjs} +23 -10
- package/dist/{plugins.js → plugins.cjs} +48 -47
- package/dist/plugins.d.ts +0 -6
- package/dist/plugins.d.ts.map +1 -1
- package/dist/{precision.js → precision.cjs} +74 -37
- package/dist/{rangePresets.js → rangePresets.cjs} +40 -19
- package/dist/{recurrence.js → recurrence.cjs} +27 -21
- package/dist/recurrence.d.ts.map +1 -1
- package/dist/scheduling.cjs +344 -0
- package/dist/scheduling.d.ts +206 -0
- package/dist/scheduling.d.ts.map +1 -0
- package/dist/{serialize.js → serialize.cjs} +36 -17
- package/dist/{temporal.js → temporal.cjs} +28 -13
- package/dist/{timezone.js → timezone.cjs} +140 -82
- package/dist/timezone.d.ts +6 -1
- package/dist/timezone.d.ts.map +1 -1
- package/dist/{types.js → types.cjs} +9 -3
- package/dist/types.d.ts +0 -4
- package/dist/types.d.ts.map +1 -1
- package/dist/{validate.js → validate.cjs} +54 -26
- package/dist/{workingHours.js → workingHours.cjs} +36 -17
- package/package.json +52 -34
- package/dist/constants.js +0 -16
- 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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
600
|
+
function toZonedDateTime(date, timeZone) {
|
|
586
601
|
return new ZonedDateTimeImpl(date.getTime(), timeZone);
|
|
587
602
|
}
|
|
588
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
649
|
+
function nowPlainDateTime() {
|
|
635
650
|
return toPlainDateTime(new Date());
|
|
636
651
|
}
|
|
637
652
|
/**
|
|
638
653
|
* Get current PlainDate in local timezone
|
|
639
654
|
*/
|
|
640
|
-
|
|
655
|
+
function nowPlainDate() {
|
|
641
656
|
return toPlainDate(new Date());
|
|
642
657
|
}
|
|
643
658
|
/**
|
|
644
659
|
* Get current PlainTime in local timezone
|
|
645
660
|
*/
|
|
646
|
-
|
|
661
|
+
function nowPlainTime() {
|
|
647
662
|
return toPlainTime(new Date());
|
|
648
663
|
}
|
|
649
664
|
/**
|
|
650
665
|
* Get current ZonedDateTime in specified timezone
|
|
651
666
|
*/
|
|
652
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
110
|
+
function getLocalOffset() {
|
|
79
111
|
return -new Date().getTimezoneOffset();
|
|
80
112
|
}
|
|
81
113
|
/** Compare two timezones offset at given date */
|
|
82
|
-
|
|
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
|
-
|
|
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
|
-
|
|
139
|
+
function isDST(date, zone) {
|
|
106
140
|
if (!isValidTimeZone(zone))
|
|
107
141
|
return null;
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
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 (
|
|
147
|
+
if (currentOffset === null) {
|
|
115
148
|
return null;
|
|
116
149
|
}
|
|
117
|
-
|
|
118
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
150
|
-
const
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
214
|
+
function findCommonWorkingHours(zones, workHoursStart = 9, workHoursEnd = 17, date = new Date()) {
|
|
186
215
|
if (zones.length === 0)
|
|
187
216
|
return null;
|
|
188
|
-
|
|
189
|
-
const
|
|
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
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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;
|
package/dist/timezone.d.ts
CHANGED
|
@@ -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
|
|
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;
|