ts-time-utils 4.1.0 → 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 +81 -31
- 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/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/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.js → finance.cjs} +39 -22
- package/dist/{fiscal.js → fiscal.cjs} +36 -17
- package/dist/{format.js → format.cjs} +83 -70
- package/dist/{healthcare.js → healthcare.cjs} +37 -22
- package/dist/{holidays.js → holidays.cjs} +52 -25
- package/dist/index.cjs +595 -0
- 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.js → scheduling.cjs} +46 -31
- 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 +40 -37
- package/dist/constants.js +0 -16
- package/dist/index.js +0 -72
package/dist/esm/plugins.js
CHANGED
|
@@ -19,38 +19,20 @@
|
|
|
19
19
|
* chain(new Date()).nextMonday().format('YYYY-MM-DD');
|
|
20
20
|
* ```
|
|
21
21
|
*/
|
|
22
|
-
|
|
23
|
-
// We need the actual class (not just the type) to modify its prototype
|
|
24
|
-
let ChainedDateConstructor = null;
|
|
22
|
+
import { ChainedDate } from './chain.js';
|
|
25
23
|
/**
|
|
26
|
-
* Get ChainedDate class
|
|
24
|
+
* Get ChainedDate class for prototype mutation.
|
|
25
|
+
* Importing `plugins` now brings in `chain` directly, so the class is available
|
|
26
|
+
* without a hidden global handshake.
|
|
27
27
|
*/
|
|
28
28
|
function getChainedDate() {
|
|
29
|
-
if (!
|
|
30
|
-
|
|
31
|
-
try {
|
|
32
|
-
// Try ESM import path first
|
|
33
|
-
ChainedDateConstructor = globalThis.__chainedDateClass;
|
|
34
|
-
if (!ChainedDateConstructor) {
|
|
35
|
-
// Fallback: The class will be set by chain.js when it loads
|
|
36
|
-
throw new Error('ChainedDate not yet loaded. Import chain.js before using plugins.');
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
catch (e) {
|
|
40
|
-
throw new Error('ChainedDate class not available. Ensure chain.js is imported before registering plugins.');
|
|
41
|
-
}
|
|
29
|
+
if (!ChainedDate) {
|
|
30
|
+
throw new Error('ChainedDate export is not available yet. Import chain.js before registering plugins.');
|
|
42
31
|
}
|
|
43
|
-
return
|
|
44
|
-
}
|
|
45
|
-
/**
|
|
46
|
-
* Initialize the plugin system with ChainedDate class
|
|
47
|
-
* This is called automatically when chain.js is imported
|
|
48
|
-
* @internal
|
|
49
|
-
*/
|
|
50
|
-
export function __initPluginSystem(ChainedDateClass) {
|
|
51
|
-
ChainedDateConstructor = ChainedDateClass;
|
|
32
|
+
return ChainedDate;
|
|
52
33
|
}
|
|
53
34
|
const registry = {};
|
|
35
|
+
const methodStates = {};
|
|
54
36
|
/**
|
|
55
37
|
* Extend ChainedDate with custom methods
|
|
56
38
|
*
|
|
@@ -80,23 +62,23 @@ export function extend(pluginName, methods) {
|
|
|
80
62
|
if (registry[pluginName]) {
|
|
81
63
|
throw new Error(`Plugin "${pluginName}" is already registered. Use a different name or uninstall first.`);
|
|
82
64
|
}
|
|
83
|
-
// Get ChainedDate class and save original methods before overwriting
|
|
84
65
|
const ChainedDateClass = getChainedDate();
|
|
85
|
-
const originalMethods = new Map();
|
|
86
66
|
Object.entries(methods).forEach(([methodName, fn]) => {
|
|
87
|
-
|
|
67
|
+
const current = ChainedDateClass.prototype[methodName];
|
|
68
|
+
if (!methodStates[methodName]) {
|
|
69
|
+
methodStates[methodName] = {
|
|
70
|
+
original: typeof current === 'function' ? current : undefined,
|
|
71
|
+
owners: []
|
|
72
|
+
};
|
|
73
|
+
}
|
|
88
74
|
if (methodName in ChainedDateClass.prototype) {
|
|
89
|
-
const original = ChainedDateClass.prototype[methodName];
|
|
90
|
-
if (typeof original === 'function') {
|
|
91
|
-
originalMethods.set(methodName, original);
|
|
92
|
-
}
|
|
93
75
|
console.warn(`Method "${methodName}" already exists on ChainedDate and will be overwritten`);
|
|
94
76
|
}
|
|
77
|
+
methodStates[methodName].owners.push(pluginName);
|
|
95
78
|
// Add the plugin method
|
|
96
79
|
ChainedDateClass.prototype[methodName] = fn;
|
|
97
80
|
});
|
|
98
|
-
|
|
99
|
-
registry[pluginName] = { plugin: methods, originalMethods };
|
|
81
|
+
registry[pluginName] = { plugin: methods };
|
|
100
82
|
}
|
|
101
83
|
/**
|
|
102
84
|
* Remove a plugin and its methods from ChainedDate
|
|
@@ -113,20 +95,32 @@ export function uninstall(pluginName) {
|
|
|
113
95
|
if (!entry) {
|
|
114
96
|
throw new Error(`Plugin "${pluginName}" is not registered`);
|
|
115
97
|
}
|
|
116
|
-
// Get ChainedDate class and restore/remove methods
|
|
117
98
|
const ChainedDateClass = getChainedDate();
|
|
118
99
|
Object.keys(entry.plugin).forEach((methodName) => {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
100
|
+
const state = methodStates[methodName];
|
|
101
|
+
if (!state) {
|
|
102
|
+
delete ChainedDateClass.prototype[methodName];
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
state.owners = state.owners.filter((owner) => owner !== pluginName);
|
|
106
|
+
const nextOwner = state.owners[state.owners.length - 1];
|
|
107
|
+
if (nextOwner) {
|
|
108
|
+
const nextPlugin = registry[nextOwner];
|
|
109
|
+
if (nextPlugin && methodName in nextPlugin.plugin) {
|
|
110
|
+
ChainedDateClass.prototype[methodName] = nextPlugin.plugin[methodName];
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
delete ChainedDateClass.prototype[methodName];
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
if (state.original) {
|
|
117
|
+
ChainedDateClass.prototype[methodName] = state.original;
|
|
123
118
|
}
|
|
124
119
|
else {
|
|
125
|
-
// Otherwise, delete the method entirely
|
|
126
120
|
delete ChainedDateClass.prototype[methodName];
|
|
127
121
|
}
|
|
122
|
+
delete methodStates[methodName];
|
|
128
123
|
});
|
|
129
|
-
// Remove from registry
|
|
130
124
|
delete registry[pluginName];
|
|
131
125
|
}
|
|
132
126
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"recurrence.d.ts","sourceRoot":"","sources":["../../src/recurrence.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,cAAc,EAAuB,MAAM,YAAY,CAAC;AAGjF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,cAAc;;oCAKjB,SAAS;mCACV,SAAS,OAAO,SAAS,UAAU,MAAM;6BAE/C,SAAS;;
|
|
1
|
+
{"version":3,"file":"recurrence.d.ts","sourceRoot":"","sources":["../../src/recurrence.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,cAAc,EAAuB,MAAM,YAAY,CAAC;AAGjF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,cAAc;;oCAKjB,SAAS;mCACV,SAAS,OAAO,SAAS,UAAU,MAAM;6BAE/C,SAAS;;EAmBrC;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,cAAc,EAAE,SAAS,CAAC,EAAE,SAAS,GAAG,IAAI,GAAG,IAAI,CAyC1F;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,cAAc,EACpB,KAAK,EAAE,SAAS,EAChB,GAAG,EAAE,SAAS,EACd,KAAK,SAAO,GACX,IAAI,EAAE,CA6BR;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,cAAc,GAAG,OAAO,CAW/E;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,OAAO,CAAC,cAAc,CAAC,GAAG,OAAO,CAgC5E;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,cAAc,GAAG,MAAM,CA6C/D"}
|
package/dist/esm/recurrence.js
CHANGED
|
@@ -43,19 +43,17 @@ export function createRecurrence(rule) {
|
|
|
43
43
|
isRecurrenceDate: (date) => isRecurrenceDate(date, rule),
|
|
44
44
|
getAllOccurrences: (limit = 100) => {
|
|
45
45
|
const occurrences = [];
|
|
46
|
-
let current = new Date(startDate);
|
|
47
|
-
|
|
48
|
-
while (count < limit) {
|
|
46
|
+
let current = new Date(startDate.getTime() - 1);
|
|
47
|
+
while (occurrences.length < limit) {
|
|
49
48
|
if (rule.until && current > new Date(rule.until))
|
|
50
49
|
break;
|
|
51
|
-
if (rule.count &&
|
|
50
|
+
if (rule.count && occurrences.length >= rule.count)
|
|
52
51
|
break;
|
|
53
52
|
const next = getNextOccurrence(rule, current);
|
|
54
53
|
if (!next)
|
|
55
54
|
break;
|
|
56
55
|
occurrences.push(next);
|
|
57
56
|
current = new Date(next.getTime() + 1);
|
|
58
|
-
count++;
|
|
59
57
|
}
|
|
60
58
|
return occurrences;
|
|
61
59
|
}
|
package/dist/esm/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;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"timezone.d.ts","sourceRoot":"","sources":["../../src/timezone.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"timezone.d.ts","sourceRoot":"","sources":["../../src/timezone.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AA6B5C,sDAAsD;AACtD,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,GAAE,IAAiB,GAAG,MAAM,GAAG,IAAI,CAgBtF;AAED,iCAAiC;AACjC,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,IAAI,CAAC,qBAA0B,GAAG,MAAM,CAG3G;AAED,yCAAyC;AACzC,wBAAgB,YAAY,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAIvE;AAED,qFAAqF;AACrF,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;CAAE,GAAG,IAAI,CAoB9J;AAED,yDAAyD;AACzD,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAOrD;AAED,uEAAuE;AACvE,eAAO,MAAM,gBAAgB,UAK5B,CAAC;AAEF,0CAA0C;AAC1C,wBAAgB,cAAc,IAAI,MAAM,CAEvC;AAED,iDAAiD;AACjD,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,GAAE,IAAiB,GAAG,MAAM,GAAG,IAAI,CAKvG;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAI7E;AAED;;;;;;GAMG;AACH,wBAAgB,KAAK,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,GAAG,IAAI,CA4B9D;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAmC1E;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,MAAM,EAAE,EACf,cAAc,GAAE,MAAU,EAC1B,YAAY,GAAE,MAAW,EACzB,IAAI,GAAE,IAAiB,GACtB;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAqE7C;AAED;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,GAAE,IAAiB,GAAG,MAAM,GAAG,IAAI,CAY5F;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAa7F;AAED;;;;;GAKG;AACH,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,GAAE,IAAiB,GAAG,MAAM,GAAG,IAAI,CAI/G;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,GAAE,IAAiB,GAAG,OAAO,GAAG,IAAI,CAIpG"}
|
package/dist/esm/timezone.js
CHANGED
|
@@ -1,6 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Timezone utilities using Intl API with fallbacks
|
|
3
3
|
*/
|
|
4
|
+
const DAY_MS = 24 * 60 * 60 * 1000;
|
|
5
|
+
const DAY_HOURS = 24;
|
|
6
|
+
const YEAR_MONTHS = Array.from({ length: 12 }, (_, month) => month);
|
|
7
|
+
function normalizeHourValue(hour) {
|
|
8
|
+
return ((hour % DAY_HOURS) + DAY_HOURS) % DAY_HOURS;
|
|
9
|
+
}
|
|
10
|
+
function pushSweepInterval(events, start, end) {
|
|
11
|
+
const clampedStart = Math.max(0, start);
|
|
12
|
+
const clampedEnd = Math.min(DAY_HOURS * 2, end);
|
|
13
|
+
if (clampedStart >= clampedEnd)
|
|
14
|
+
return;
|
|
15
|
+
events.push({ time: clampedStart, delta: 1 });
|
|
16
|
+
events.push({ time: clampedEnd, delta: -1 });
|
|
17
|
+
}
|
|
4
18
|
/** Get offset (minutes) for a zone at a given date */
|
|
5
19
|
export function getTimezoneOffset(zone, date = new Date()) {
|
|
6
20
|
try {
|
|
@@ -99,30 +113,35 @@ export function reinterpretAsZone(date, targetZone) {
|
|
|
99
113
|
}
|
|
100
114
|
/**
|
|
101
115
|
* Check if a date is in Daylight Saving Time for a given timezone
|
|
116
|
+
* Uses a yearly-offset heuristic: sample the zone's local year and treat the
|
|
117
|
+
* maximum observed UTC offset as the DST offset for that year.
|
|
102
118
|
* @param date - date to check
|
|
103
119
|
* @param zone - IANA timezone string
|
|
104
120
|
*/
|
|
105
121
|
export function isDST(date, zone) {
|
|
106
122
|
if (!isValidTimeZone(zone))
|
|
107
123
|
return null;
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
const julOffset = getTimezoneOffset(zone, july);
|
|
124
|
+
const zonedDate = convertDateToZone(date, zone);
|
|
125
|
+
if (!zonedDate) {
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
113
128
|
const currentOffset = getTimezoneOffset(zone, date);
|
|
114
|
-
if (
|
|
129
|
+
if (currentOffset === null) {
|
|
115
130
|
return null;
|
|
116
131
|
}
|
|
117
|
-
|
|
118
|
-
|
|
132
|
+
const yearlyOffsets = new Set();
|
|
133
|
+
for (const month of YEAR_MONTHS) {
|
|
134
|
+
const sample = new Date(Date.UTC(zonedDate.year, month, 1, 12, 0, 0));
|
|
135
|
+
const offset = getTimezoneOffset(zone, sample);
|
|
136
|
+
if (offset === null) {
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
yearlyOffsets.add(offset);
|
|
140
|
+
}
|
|
141
|
+
if (yearlyOffsets.size <= 1) {
|
|
119
142
|
return false;
|
|
120
143
|
}
|
|
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;
|
|
144
|
+
return currentOffset === Math.max(...yearlyOffsets);
|
|
126
145
|
}
|
|
127
146
|
/**
|
|
128
147
|
* Get the next DST transition (if any) for a timezone
|
|
@@ -133,46 +152,35 @@ export function isDST(date, zone) {
|
|
|
133
152
|
export function getNextDSTTransition(date, zone) {
|
|
134
153
|
if (!isValidTimeZone(zone))
|
|
135
154
|
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
155
|
const currentOffset = getTimezoneOffset(zone, date);
|
|
147
156
|
if (currentOffset === null)
|
|
148
157
|
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);
|
|
158
|
+
const startTime = date.getTime() + 1;
|
|
159
|
+
const searchLimit = startTime + 366 * DAY_MS;
|
|
160
|
+
let low = startTime;
|
|
161
|
+
let high = null;
|
|
162
|
+
for (let probeTime = startTime + DAY_MS; probeTime <= searchLimit; probeTime += DAY_MS) {
|
|
163
|
+
const probeOffset = getTimezoneOffset(zone, new Date(probeTime));
|
|
164
|
+
if (probeOffset !== null && probeOffset !== currentOffset) {
|
|
165
|
+
low = probeTime - DAY_MS;
|
|
166
|
+
high = probeTime;
|
|
167
|
+
break;
|
|
173
168
|
}
|
|
174
169
|
}
|
|
175
|
-
|
|
170
|
+
if (high === null) {
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
while (high - low > 1) {
|
|
174
|
+
const mid = Math.floor((low + high) / 2);
|
|
175
|
+
const midOffset = getTimezoneOffset(zone, new Date(mid));
|
|
176
|
+
if (midOffset === currentOffset) {
|
|
177
|
+
low = mid;
|
|
178
|
+
}
|
|
179
|
+
else {
|
|
180
|
+
high = mid;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
return new Date(high);
|
|
176
184
|
}
|
|
177
185
|
/**
|
|
178
186
|
* Find overlapping working hours between multiple timezones
|
|
@@ -180,32 +188,64 @@ export function getNextDSTTransition(date, zone) {
|
|
|
180
188
|
* @param workHoursStart - work hours start (0-24)
|
|
181
189
|
* @param workHoursEnd - work hours end (0-24)
|
|
182
190
|
* @param date - reference date (default: today)
|
|
183
|
-
* @returns
|
|
191
|
+
* @returns one contiguous UTC overlap window, specifically the longest
|
|
192
|
+
* contiguous overlap slice. A full-day overlap is returned as
|
|
193
|
+
* `{ startUTC: 0, endUTC: 24 }`, and wrapped overlaps are returned with
|
|
194
|
+
* `endUTC` normalized back into the 0-24 range and may be less than `startUTC`
|
|
184
195
|
*/
|
|
185
196
|
export function findCommonWorkingHours(zones, workHoursStart = 9, workHoursEnd = 17, date = new Date()) {
|
|
186
197
|
if (zones.length === 0)
|
|
187
198
|
return null;
|
|
188
|
-
|
|
189
|
-
const
|
|
199
|
+
const endHour = workHoursEnd < workHoursStart ? workHoursEnd + DAY_HOURS : workHoursEnd;
|
|
200
|
+
const duration = endHour - workHoursStart;
|
|
201
|
+
if (duration <= 0) {
|
|
202
|
+
return null;
|
|
203
|
+
}
|
|
204
|
+
const sweepEvents = [];
|
|
205
|
+
for (const zone of zones) {
|
|
190
206
|
const offset = getTimezoneOffset(zone, date);
|
|
191
|
-
if (offset === null)
|
|
207
|
+
if (offset === null) {
|
|
192
208
|
return null;
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
209
|
+
}
|
|
210
|
+
if (duration >= DAY_HOURS) {
|
|
211
|
+
pushSweepInterval(sweepEvents, 0, DAY_HOURS * 2);
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
const startUTC = normalizeHourValue(workHoursStart - (offset / 60));
|
|
215
|
+
pushSweepInterval(sweepEvents, startUTC, startUTC + duration);
|
|
216
|
+
pushSweepInterval(sweepEvents, startUTC + DAY_HOURS, startUTC + DAY_HOURS + duration);
|
|
217
|
+
}
|
|
218
|
+
sweepEvents.sort((a, b) => a.time - b.time || b.delta - a.delta);
|
|
219
|
+
let activeWindows = 0;
|
|
220
|
+
let previousTime = 0;
|
|
221
|
+
let bestOverlap = null;
|
|
222
|
+
for (let i = 0; i < sweepEvents.length;) {
|
|
223
|
+
const currentTime = sweepEvents[i].time;
|
|
224
|
+
if (currentTime > previousTime && activeWindows === zones.length) {
|
|
225
|
+
const candidate = { start: previousTime, end: currentTime };
|
|
226
|
+
if (bestOverlap === null ||
|
|
227
|
+
candidate.end - candidate.start > bestOverlap.end - bestOverlap.start ||
|
|
228
|
+
(candidate.end - candidate.start === bestOverlap.end - bestOverlap.start &&
|
|
229
|
+
candidate.start < bestOverlap.start)) {
|
|
230
|
+
bestOverlap = candidate;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
while (i < sweepEvents.length && sweepEvents[i].time === currentTime) {
|
|
234
|
+
activeWindows += sweepEvents[i].delta;
|
|
235
|
+
i++;
|
|
236
|
+
}
|
|
237
|
+
previousTime = currentTime;
|
|
238
|
+
}
|
|
239
|
+
if (bestOverlap === null) {
|
|
200
240
|
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
241
|
}
|
|
208
|
-
|
|
242
|
+
const overlapDuration = bestOverlap.end - bestOverlap.start;
|
|
243
|
+
if (overlapDuration >= DAY_HOURS) {
|
|
244
|
+
return { startUTC: 0, endUTC: DAY_HOURS };
|
|
245
|
+
}
|
|
246
|
+
const startUTC = normalizeHourValue(bestOverlap.start);
|
|
247
|
+
const endUTC = normalizeHourValue(bestOverlap.start + overlapDuration);
|
|
248
|
+
return { startUTC, endUTC };
|
|
209
249
|
}
|
|
210
250
|
/**
|
|
211
251
|
* Get all timezone abbreviations for a zone on a given date
|
package/dist/esm/types.d.ts
CHANGED
|
@@ -14,8 +14,6 @@ export interface ParseOptions {
|
|
|
14
14
|
strict?: boolean;
|
|
15
15
|
/** Timezone to use for parsing */
|
|
16
16
|
timezone?: string;
|
|
17
|
-
/** Default locale for parsing */
|
|
18
|
-
locale?: string;
|
|
19
17
|
}
|
|
20
18
|
/** Options for formatting operations */
|
|
21
19
|
export interface FormatOptions {
|
|
@@ -63,8 +61,6 @@ export interface WorkingHoursConfig {
|
|
|
63
61
|
start: number;
|
|
64
62
|
end: number;
|
|
65
63
|
}[];
|
|
66
|
-
/** Timezone for working hours calculation */
|
|
67
|
-
timezone?: string;
|
|
68
64
|
}
|
|
69
65
|
/** Result of age calculation */
|
|
70
66
|
export interface AgeResult {
|
package/dist/esm/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,4CAA4C;AAC5C,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;AAE/C,4CAA4C;AAC5C,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,IAAI,CAAC;IACZ,GAAG,EAAE,IAAI,CAAC;CACX;AAED,qCAAqC;AACrC,MAAM,WAAW,YAAY;IAC3B,+DAA+D;IAC/D,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,kCAAkC;IAClC,QAAQ,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,9 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
1
2
|
/**
|
|
2
3
|
* @fileoverview Finance utilities for market-aware date calculations
|
|
3
4
|
* Provides US market hours, trading days, settlement dates, and options expiration
|
|
4
5
|
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.US_MARKET_HOLIDAYS = exports.MARKET_HOURS = void 0;
|
|
8
|
+
exports.isMarketHoliday = isMarketHoliday;
|
|
9
|
+
exports.isTradingDay = isTradingDay;
|
|
10
|
+
exports.isMarketOpen = isMarketOpen;
|
|
11
|
+
exports.getMarketHours = getMarketHours;
|
|
12
|
+
exports.getMarketOpen = getMarketOpen;
|
|
13
|
+
exports.getMarketClose = getMarketClose;
|
|
14
|
+
exports.getNextMarketOpen = getNextMarketOpen;
|
|
15
|
+
exports.getNextMarketClose = getNextMarketClose;
|
|
16
|
+
exports.getSettlementDate = getSettlementDate;
|
|
17
|
+
exports.getTradeDateFromSettlement = getTradeDateFromSettlement;
|
|
18
|
+
exports.eachTradingDay = eachTradingDay;
|
|
19
|
+
exports.countTradingDays = countTradingDays;
|
|
20
|
+
exports.addTradingDays = addTradingDays;
|
|
21
|
+
exports.getOptionsExpiration = getOptionsExpiration;
|
|
5
22
|
/** Market hours for US exchanges */
|
|
6
|
-
|
|
23
|
+
exports.MARKET_HOURS = {
|
|
7
24
|
NYSE: {
|
|
8
25
|
open: { hour: 9, minute: 30 },
|
|
9
26
|
close: { hour: 16, minute: 0 },
|
|
@@ -20,7 +37,7 @@ export const MARKET_HOURS = {
|
|
|
20
37
|
}
|
|
21
38
|
};
|
|
22
39
|
/** US market holidays (NYSE/NASDAQ follow same schedule) */
|
|
23
|
-
|
|
40
|
+
exports.US_MARKET_HOLIDAYS = [
|
|
24
41
|
"New Year's Day",
|
|
25
42
|
'Martin Luther King Jr. Day',
|
|
26
43
|
"Presidents' Day",
|
|
@@ -147,7 +164,7 @@ function isSameDay(date1, date2) {
|
|
|
147
164
|
* isMarketHoliday(new Date('2024-01-02')); // false
|
|
148
165
|
* ```
|
|
149
166
|
*/
|
|
150
|
-
|
|
167
|
+
function isMarketHoliday(date, market = 'NYSE') {
|
|
151
168
|
const d = toDate(date);
|
|
152
169
|
const year = d.getFullYear();
|
|
153
170
|
const holidays = getUSMarketHolidays(year);
|
|
@@ -165,7 +182,7 @@ export function isMarketHoliday(date, market = 'NYSE') {
|
|
|
165
182
|
* isTradingDay(new Date('2024-01-13')); // false (Saturday)
|
|
166
183
|
* ```
|
|
167
184
|
*/
|
|
168
|
-
|
|
185
|
+
function isTradingDay(date, market = 'NYSE') {
|
|
169
186
|
const d = toDate(date);
|
|
170
187
|
const day = d.getDay();
|
|
171
188
|
// Weekend check
|
|
@@ -189,12 +206,12 @@ export function isTradingDay(date, market = 'NYSE') {
|
|
|
189
206
|
* isMarketOpen(new Date('2024-01-15T10:30:00-05:00')); // true
|
|
190
207
|
* ```
|
|
191
208
|
*/
|
|
192
|
-
|
|
209
|
+
function isMarketOpen(date, market = 'NYSE') {
|
|
193
210
|
const d = toDate(date);
|
|
194
211
|
// First check if it's a trading day
|
|
195
212
|
if (!isTradingDay(d, market))
|
|
196
213
|
return false;
|
|
197
|
-
const hours = MARKET_HOURS[market];
|
|
214
|
+
const hours = exports.MARKET_HOURS[market];
|
|
198
215
|
// Convert to market timezone for comparison
|
|
199
216
|
// For simplicity, we assume the input is already in market timezone
|
|
200
217
|
// or we compare hours directly
|
|
@@ -216,8 +233,8 @@ export function isMarketOpen(date, market = 'NYSE') {
|
|
|
216
233
|
* console.log(hours.open); // { hour: 9, minute: 30 }
|
|
217
234
|
* ```
|
|
218
235
|
*/
|
|
219
|
-
|
|
220
|
-
const source = MARKET_HOURS[market];
|
|
236
|
+
function getMarketHours(market = 'NYSE') {
|
|
237
|
+
const source = exports.MARKET_HOURS[market];
|
|
221
238
|
return {
|
|
222
239
|
open: { ...source.open },
|
|
223
240
|
close: { ...source.close },
|
|
@@ -238,9 +255,9 @@ export function getMarketHours(market = 'NYSE') {
|
|
|
238
255
|
* console.log(open); // 2024-01-15T09:30:00
|
|
239
256
|
* ```
|
|
240
257
|
*/
|
|
241
|
-
|
|
258
|
+
function getMarketOpen(date, market = 'NYSE') {
|
|
242
259
|
const d = toDate(date);
|
|
243
|
-
const hours = MARKET_HOURS[market];
|
|
260
|
+
const hours = exports.MARKET_HOURS[market];
|
|
244
261
|
const result = new Date(d);
|
|
245
262
|
result.setHours(hours.open.hour, hours.open.minute, 0, 0);
|
|
246
263
|
return result;
|
|
@@ -257,9 +274,9 @@ export function getMarketOpen(date, market = 'NYSE') {
|
|
|
257
274
|
* console.log(close); // 2024-01-15T16:00:00
|
|
258
275
|
* ```
|
|
259
276
|
*/
|
|
260
|
-
|
|
277
|
+
function getMarketClose(date, market = 'NYSE') {
|
|
261
278
|
const d = toDate(date);
|
|
262
|
-
const hours = MARKET_HOURS[market];
|
|
279
|
+
const hours = exports.MARKET_HOURS[market];
|
|
263
280
|
const result = new Date(d);
|
|
264
281
|
result.setHours(hours.close.hour, hours.close.minute, 0, 0);
|
|
265
282
|
return result;
|
|
@@ -276,9 +293,9 @@ export function getMarketClose(date, market = 'NYSE') {
|
|
|
276
293
|
* const nextOpen = getNextMarketOpen(new Date('2024-01-12T17:00:00'));
|
|
277
294
|
* ```
|
|
278
295
|
*/
|
|
279
|
-
|
|
296
|
+
function getNextMarketOpen(after, market = 'NYSE') {
|
|
280
297
|
const d = toDate(after);
|
|
281
|
-
const hours = MARKET_HOURS[market];
|
|
298
|
+
const hours = exports.MARKET_HOURS[market];
|
|
282
299
|
// Start with current day's open
|
|
283
300
|
let candidate = getMarketOpen(d, market);
|
|
284
301
|
// If we're past today's open, start from tomorrow
|
|
@@ -305,9 +322,9 @@ export function getNextMarketOpen(after, market = 'NYSE') {
|
|
|
305
322
|
* // Returns 2024-01-15T16:00:00 (same day close)
|
|
306
323
|
* ```
|
|
307
324
|
*/
|
|
308
|
-
|
|
325
|
+
function getNextMarketClose(after, market = 'NYSE') {
|
|
309
326
|
const d = toDate(after);
|
|
310
|
-
const hours = MARKET_HOURS[market];
|
|
327
|
+
const hours = exports.MARKET_HOURS[market];
|
|
311
328
|
// Start with current day's close
|
|
312
329
|
let candidate = getMarketClose(d, market);
|
|
313
330
|
// If we're past today's close or not a trading day, go to next trading day
|
|
@@ -335,7 +352,7 @@ export function getNextMarketClose(after, market = 'NYSE') {
|
|
|
335
352
|
* // Returns 2024-01-17 (skipping weekends/holidays)
|
|
336
353
|
* ```
|
|
337
354
|
*/
|
|
338
|
-
|
|
355
|
+
function getSettlementDate(tradeDate, days, market = 'NYSE') {
|
|
339
356
|
const d = toDate(tradeDate);
|
|
340
357
|
const result = new Date(d);
|
|
341
358
|
result.setHours(0, 0, 0, 0);
|
|
@@ -361,7 +378,7 @@ export function getSettlementDate(tradeDate, days, market = 'NYSE') {
|
|
|
361
378
|
* // Returns 2024-01-15
|
|
362
379
|
* ```
|
|
363
380
|
*/
|
|
364
|
-
|
|
381
|
+
function getTradeDateFromSettlement(settlementDate, days, market = 'NYSE') {
|
|
365
382
|
const d = toDate(settlementDate);
|
|
366
383
|
const result = new Date(d);
|
|
367
384
|
result.setHours(0, 0, 0, 0);
|
|
@@ -387,7 +404,7 @@ export function getTradeDateFromSettlement(settlementDate, days, market = 'NYSE'
|
|
|
387
404
|
* // Returns Mon, Tue, Wed, Thu, Fri (if no holidays)
|
|
388
405
|
* ```
|
|
389
406
|
*/
|
|
390
|
-
|
|
407
|
+
function eachTradingDay(start, end, market = 'NYSE') {
|
|
391
408
|
const startDate = toDate(start);
|
|
392
409
|
const endDate = toDate(end);
|
|
393
410
|
startDate.setHours(0, 0, 0, 0);
|
|
@@ -415,7 +432,7 @@ export function eachTradingDay(start, end, market = 'NYSE') {
|
|
|
415
432
|
* // Returns 5 (Mon-Fri if no holidays)
|
|
416
433
|
* ```
|
|
417
434
|
*/
|
|
418
|
-
|
|
435
|
+
function countTradingDays(start, end, market = 'NYSE') {
|
|
419
436
|
return eachTradingDay(start, end, market).length;
|
|
420
437
|
}
|
|
421
438
|
/**
|
|
@@ -431,7 +448,7 @@ export function countTradingDays(start, end, market = 'NYSE') {
|
|
|
431
448
|
* // Returns 5 trading days later
|
|
432
449
|
* ```
|
|
433
450
|
*/
|
|
434
|
-
|
|
451
|
+
function addTradingDays(date, days, market = 'NYSE') {
|
|
435
452
|
const d = toDate(date);
|
|
436
453
|
const result = new Date(d);
|
|
437
454
|
result.setHours(0, 0, 0, 0);
|
|
@@ -466,7 +483,7 @@ export function addTradingDays(date, days, market = 'NYSE') {
|
|
|
466
483
|
* const quarterly = getOptionsExpiration(2024, 3, 'quarterly');
|
|
467
484
|
* ```
|
|
468
485
|
*/
|
|
469
|
-
|
|
486
|
+
function getOptionsExpiration(year, month, type = 'monthly') {
|
|
470
487
|
// Adjust month to 0-indexed
|
|
471
488
|
const monthIndex = month - 1;
|
|
472
489
|
switch (type) {
|