ts-time-utils 1.0.0 → 1.1.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 +226 -1
- package/dist/calculate.d.ts.map +1 -1
- package/dist/calculate.js +24 -10
- package/dist/countdown.d.ts +217 -0
- package/dist/countdown.d.ts.map +1 -0
- package/dist/countdown.js +298 -0
- package/dist/dateRange.d.ts +266 -0
- package/dist/dateRange.d.ts.map +1 -0
- package/dist/dateRange.js +433 -0
- package/dist/esm/calculate.d.ts.map +1 -1
- package/dist/esm/calculate.js +24 -10
- package/dist/esm/countdown.d.ts +217 -0
- package/dist/esm/countdown.d.ts.map +1 -0
- package/dist/esm/countdown.js +298 -0
- package/dist/esm/dateRange.d.ts +266 -0
- package/dist/esm/dateRange.d.ts.map +1 -0
- package/dist/esm/dateRange.js +433 -0
- package/dist/esm/index.d.ts +5 -1
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +8 -0
- package/dist/esm/naturalLanguage.d.ts +107 -0
- package/dist/esm/naturalLanguage.d.ts.map +1 -0
- package/dist/esm/naturalLanguage.js +344 -0
- package/dist/esm/recurrence.d.ts +149 -0
- package/dist/esm/recurrence.d.ts.map +1 -0
- package/dist/esm/recurrence.js +404 -0
- package/dist/esm/types.d.ts +21 -0
- package/dist/esm/types.d.ts.map +1 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -0
- package/dist/naturalLanguage.d.ts +107 -0
- package/dist/naturalLanguage.d.ts.map +1 -0
- package/dist/naturalLanguage.js +344 -0
- package/dist/recurrence.d.ts +149 -0
- package/dist/recurrence.d.ts.map +1 -0
- package/dist/recurrence.js +404 -0
- package/dist/types.d.ts +21 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +30 -2
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Extended date range operations and utilities
|
|
3
|
+
* Provides advanced operations for working with date ranges beyond basic intervals
|
|
4
|
+
*/
|
|
5
|
+
import type { DateRange, DateInput } from './types.js';
|
|
6
|
+
/**
|
|
7
|
+
* Checks if two date ranges overlap
|
|
8
|
+
* @param range1 - First date range
|
|
9
|
+
* @param range2 - Second date range
|
|
10
|
+
* @returns True if the ranges overlap
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```ts
|
|
14
|
+
* const range1 = { start: new Date('2024-01-01'), end: new Date('2024-01-10') };
|
|
15
|
+
* const range2 = { start: new Date('2024-01-05'), end: new Date('2024-01-15') };
|
|
16
|
+
*
|
|
17
|
+
* dateRangeOverlap(range1, range2); // true
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
export declare function dateRangeOverlap(range1: DateRange, range2: DateRange): boolean;
|
|
21
|
+
/**
|
|
22
|
+
* Checks if multiple date ranges have any overlaps
|
|
23
|
+
* @param ranges - Array of date ranges
|
|
24
|
+
* @returns True if any two ranges overlap
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* ```ts
|
|
28
|
+
* const ranges = [
|
|
29
|
+
* { start: new Date('2024-01-01'), end: new Date('2024-01-10') },
|
|
30
|
+
* { start: new Date('2024-01-05'), end: new Date('2024-01-15') }
|
|
31
|
+
* ];
|
|
32
|
+
*
|
|
33
|
+
* hasOverlappingRanges(ranges); // true
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
export declare function hasOverlappingRanges(ranges: DateRange[]): boolean;
|
|
37
|
+
/**
|
|
38
|
+
* Merges overlapping or adjacent date ranges
|
|
39
|
+
* @param ranges - Array of date ranges to merge
|
|
40
|
+
* @returns Array of merged, non-overlapping ranges
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* ```ts
|
|
44
|
+
* const ranges = [
|
|
45
|
+
* { start: new Date('2024-01-01'), end: new Date('2024-01-10') },
|
|
46
|
+
* { start: new Date('2024-01-05'), end: new Date('2024-01-15') },
|
|
47
|
+
* { start: new Date('2024-01-20'), end: new Date('2024-01-25') }
|
|
48
|
+
* ];
|
|
49
|
+
*
|
|
50
|
+
* mergeDateRanges(ranges);
|
|
51
|
+
* // [
|
|
52
|
+
* // { start: Date('2024-01-01'), end: Date('2024-01-15') },
|
|
53
|
+
* // { start: Date('2024-01-20'), end: Date('2024-01-25') }
|
|
54
|
+
* // ]
|
|
55
|
+
* ```
|
|
56
|
+
*/
|
|
57
|
+
export declare function mergeDateRanges(ranges: DateRange[]): DateRange[];
|
|
58
|
+
/**
|
|
59
|
+
* Finds gaps between date ranges within specified bounds
|
|
60
|
+
* @param ranges - Array of date ranges
|
|
61
|
+
* @param bounds - Optional bounds to search within
|
|
62
|
+
* @returns Array of date ranges representing gaps
|
|
63
|
+
*
|
|
64
|
+
* @example
|
|
65
|
+
* ```ts
|
|
66
|
+
* const ranges = [
|
|
67
|
+
* { start: new Date('2024-01-01'), end: new Date('2024-01-05') },
|
|
68
|
+
* { start: new Date('2024-01-10'), end: new Date('2024-01-15') }
|
|
69
|
+
* ];
|
|
70
|
+
*
|
|
71
|
+
* findGaps(ranges, {
|
|
72
|
+
* start: new Date('2024-01-01'),
|
|
73
|
+
* end: new Date('2024-01-20')
|
|
74
|
+
* });
|
|
75
|
+
* // [
|
|
76
|
+
* // { start: Date('2024-01-06'), end: Date('2024-01-09') },
|
|
77
|
+
* // { start: Date('2024-01-16'), end: Date('2024-01-20') }
|
|
78
|
+
* // ]
|
|
79
|
+
* ```
|
|
80
|
+
*/
|
|
81
|
+
export declare function findGaps(ranges: DateRange[], bounds?: DateRange): DateRange[];
|
|
82
|
+
/**
|
|
83
|
+
* Splits a date range into smaller chunks
|
|
84
|
+
* @param range - The date range to split
|
|
85
|
+
* @param chunkSize - Size of each chunk
|
|
86
|
+
* @param unit - Unit for chunk size
|
|
87
|
+
* @returns Array of date ranges
|
|
88
|
+
*
|
|
89
|
+
* @example
|
|
90
|
+
* ```ts
|
|
91
|
+
* const range = {
|
|
92
|
+
* start: new Date('2024-01-01'),
|
|
93
|
+
* end: new Date('2024-01-10')
|
|
94
|
+
* };
|
|
95
|
+
*
|
|
96
|
+
* splitRange(range, 3, 'day');
|
|
97
|
+
* // Returns 4 ranges: 3 days, 3 days, 3 days, 1 day
|
|
98
|
+
* ```
|
|
99
|
+
*/
|
|
100
|
+
export declare function splitRange(range: DateRange, chunkSize: number, unit: 'millisecond' | 'second' | 'minute' | 'hour' | 'day' | 'week' | 'month' | 'year'): DateRange[];
|
|
101
|
+
/**
|
|
102
|
+
* Checks if a date falls within a date range
|
|
103
|
+
* @param range - The date range
|
|
104
|
+
* @param date - The date to check
|
|
105
|
+
* @param inclusive - Whether to include boundary dates (default: true)
|
|
106
|
+
* @returns True if date is within range
|
|
107
|
+
*
|
|
108
|
+
* @example
|
|
109
|
+
* ```ts
|
|
110
|
+
* const range = {
|
|
111
|
+
* start: new Date('2024-01-01'),
|
|
112
|
+
* end: new Date('2024-01-31')
|
|
113
|
+
* };
|
|
114
|
+
*
|
|
115
|
+
* containsDate(range, new Date('2024-01-15')); // true
|
|
116
|
+
* containsDate(range, new Date('2024-02-01')); // false
|
|
117
|
+
* ```
|
|
118
|
+
*/
|
|
119
|
+
export declare function containsDate(range: DateRange, date: DateInput, inclusive?: boolean): boolean;
|
|
120
|
+
/**
|
|
121
|
+
* Gets the intersection of two date ranges
|
|
122
|
+
* @param range1 - First date range
|
|
123
|
+
* @param range2 - Second date range
|
|
124
|
+
* @returns The overlapping range, or null if no overlap
|
|
125
|
+
*
|
|
126
|
+
* @example
|
|
127
|
+
* ```ts
|
|
128
|
+
* const range1 = { start: new Date('2024-01-01'), end: new Date('2024-01-15') };
|
|
129
|
+
* const range2 = { start: new Date('2024-01-10'), end: new Date('2024-01-20') };
|
|
130
|
+
*
|
|
131
|
+
* getIntersection(range1, range2);
|
|
132
|
+
* // { start: Date('2024-01-10'), end: Date('2024-01-15') }
|
|
133
|
+
* ```
|
|
134
|
+
*/
|
|
135
|
+
export declare function getIntersection(range1: DateRange, range2: DateRange): DateRange | null;
|
|
136
|
+
/**
|
|
137
|
+
* Gets the union (combined coverage) of two date ranges
|
|
138
|
+
* @param range1 - First date range
|
|
139
|
+
* @param range2 - Second date range
|
|
140
|
+
* @returns The combined range covering both inputs
|
|
141
|
+
*
|
|
142
|
+
* @example
|
|
143
|
+
* ```ts
|
|
144
|
+
* const range1 = { start: new Date('2024-01-01'), end: new Date('2024-01-15') };
|
|
145
|
+
* const range2 = { start: new Date('2024-01-10'), end: new Date('2024-01-20') };
|
|
146
|
+
*
|
|
147
|
+
* getUnion(range1, range2);
|
|
148
|
+
* // { start: Date('2024-01-01'), end: Date('2024-01-20') }
|
|
149
|
+
* ```
|
|
150
|
+
*/
|
|
151
|
+
export declare function getUnion(range1: DateRange, range2: DateRange): DateRange;
|
|
152
|
+
/**
|
|
153
|
+
* Subtracts one date range from another
|
|
154
|
+
* @param range - The range to subtract from
|
|
155
|
+
* @param subtract - The range to subtract
|
|
156
|
+
* @returns Array of remaining date ranges (0-2 ranges)
|
|
157
|
+
*
|
|
158
|
+
* @example
|
|
159
|
+
* ```ts
|
|
160
|
+
* const range = { start: new Date('2024-01-01'), end: new Date('2024-01-31') };
|
|
161
|
+
* const subtract = { start: new Date('2024-01-10'), end: new Date('2024-01-20') };
|
|
162
|
+
*
|
|
163
|
+
* subtractRange(range, subtract);
|
|
164
|
+
* // [
|
|
165
|
+
* // { start: Date('2024-01-01'), end: Date('2024-01-09') },
|
|
166
|
+
* // { start: Date('2024-01-21'), end: Date('2024-01-31') }
|
|
167
|
+
* // ]
|
|
168
|
+
* ```
|
|
169
|
+
*/
|
|
170
|
+
export declare function subtractRange(range: DateRange, subtract: DateRange): DateRange[];
|
|
171
|
+
/**
|
|
172
|
+
* Calculates the duration of a date range in milliseconds
|
|
173
|
+
* @param range - The date range
|
|
174
|
+
* @returns Duration in milliseconds
|
|
175
|
+
*
|
|
176
|
+
* @example
|
|
177
|
+
* ```ts
|
|
178
|
+
* const range = {
|
|
179
|
+
* start: new Date('2024-01-01'),
|
|
180
|
+
* end: new Date('2024-01-02')
|
|
181
|
+
* };
|
|
182
|
+
*
|
|
183
|
+
* getRangeDuration(range); // 86400000 (1 day in ms)
|
|
184
|
+
* ```
|
|
185
|
+
*/
|
|
186
|
+
export declare function getRangeDuration(range: DateRange): number;
|
|
187
|
+
/**
|
|
188
|
+
* Expands a date range by a specified amount
|
|
189
|
+
* @param range - The date range to expand
|
|
190
|
+
* @param amount - Amount to expand by
|
|
191
|
+
* @param unit - Unit for expansion
|
|
192
|
+
* @param options - Expansion options
|
|
193
|
+
* @returns Expanded date range
|
|
194
|
+
*
|
|
195
|
+
* @example
|
|
196
|
+
* ```ts
|
|
197
|
+
* const range = {
|
|
198
|
+
* start: new Date('2024-01-10'),
|
|
199
|
+
* end: new Date('2024-01-20')
|
|
200
|
+
* };
|
|
201
|
+
*
|
|
202
|
+
* expandRange(range, 5, 'day');
|
|
203
|
+
* // { start: Date('2024-01-05'), end: Date('2024-01-25') }
|
|
204
|
+
*
|
|
205
|
+
* expandRange(range, 5, 'day', { direction: 'before' });
|
|
206
|
+
* // { start: Date('2024-01-05'), end: Date('2024-01-20') }
|
|
207
|
+
* ```
|
|
208
|
+
*/
|
|
209
|
+
export declare function expandRange(range: DateRange, amount: number, unit: 'millisecond' | 'second' | 'minute' | 'hour' | 'day' | 'week' | 'month' | 'year', options?: {
|
|
210
|
+
direction?: 'both' | 'before' | 'after';
|
|
211
|
+
}): DateRange;
|
|
212
|
+
/**
|
|
213
|
+
* Shrinks a date range by a specified amount
|
|
214
|
+
* @param range - The date range to shrink
|
|
215
|
+
* @param amount - Amount to shrink by
|
|
216
|
+
* @param unit - Unit for shrinking
|
|
217
|
+
* @param options - Shrink options
|
|
218
|
+
* @returns Shrunk date range, or null if result would be invalid
|
|
219
|
+
*
|
|
220
|
+
* @example
|
|
221
|
+
* ```ts
|
|
222
|
+
* const range = {
|
|
223
|
+
* start: new Date('2024-01-01'),
|
|
224
|
+
* end: new Date('2024-01-31')
|
|
225
|
+
* };
|
|
226
|
+
*
|
|
227
|
+
* shrinkRange(range, 5, 'day');
|
|
228
|
+
* // { start: Date('2024-01-06'), end: Date('2024-01-26') }
|
|
229
|
+
* ```
|
|
230
|
+
*/
|
|
231
|
+
export declare function shrinkRange(range: DateRange, amount: number, unit: 'millisecond' | 'second' | 'minute' | 'hour' | 'day' | 'week' | 'month' | 'year', options?: {
|
|
232
|
+
direction?: 'both' | 'start' | 'end';
|
|
233
|
+
}): DateRange | null;
|
|
234
|
+
/**
|
|
235
|
+
* Checks if one date range completely contains another
|
|
236
|
+
* @param outer - The potentially containing range
|
|
237
|
+
* @param inner - The potentially contained range
|
|
238
|
+
* @returns True if outer completely contains inner
|
|
239
|
+
*
|
|
240
|
+
* @example
|
|
241
|
+
* ```ts
|
|
242
|
+
* const outer = { start: new Date('2024-01-01'), end: new Date('2024-01-31') };
|
|
243
|
+
* const inner = { start: new Date('2024-01-10'), end: new Date('2024-01-20') };
|
|
244
|
+
*
|
|
245
|
+
* rangeContains(outer, inner); // true
|
|
246
|
+
* ```
|
|
247
|
+
*/
|
|
248
|
+
export declare function rangeContains(outer: DateRange, inner: DateRange): boolean;
|
|
249
|
+
/**
|
|
250
|
+
* Sorts an array of date ranges by start date
|
|
251
|
+
* @param ranges - Array of date ranges
|
|
252
|
+
* @param order - Sort order ('asc' or 'desc')
|
|
253
|
+
* @returns Sorted array of date ranges
|
|
254
|
+
*
|
|
255
|
+
* @example
|
|
256
|
+
* ```ts
|
|
257
|
+
* const ranges = [
|
|
258
|
+
* { start: new Date('2024-01-15'), end: new Date('2024-01-20') },
|
|
259
|
+
* { start: new Date('2024-01-01'), end: new Date('2024-01-10') }
|
|
260
|
+
* ];
|
|
261
|
+
*
|
|
262
|
+
* sortRanges(ranges); // Sorted by start date ascending
|
|
263
|
+
* ```
|
|
264
|
+
*/
|
|
265
|
+
export declare function sortRanges(ranges: DateRange[], order?: 'asc' | 'desc'): DateRange[];
|
|
266
|
+
//# sourceMappingURL=dateRange.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dateRange.d.ts","sourceRoot":"","sources":["../../src/dateRange.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAGvD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,GAAG,OAAO,CAE9E;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,SAAS,EAAE,GAAG,OAAO,CASjE;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,SAAS,EAAE,GAAG,SAAS,EAAE,CA0BhE;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,QAAQ,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,MAAM,CAAC,EAAE,SAAS,GAAG,SAAS,EAAE,CA2C7E;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,UAAU,CACxB,KAAK,EAAE,SAAS,EAChB,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,aAAa,GAAG,QAAQ,GAAG,QAAQ,GAAG,MAAM,GAAG,KAAK,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,GACrF,SAAS,EAAE,CA2Bb;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,YAAY,CAC1B,KAAK,EAAE,SAAS,EAChB,IAAI,EAAE,SAAS,EACf,SAAS,UAAO,GACf,OAAO,CAQT;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,GAAG,SAAS,GAAG,IAAI,CAStF;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,QAAQ,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,GAAG,SAAS,CAKxE;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,SAAS,GAAG,SAAS,EAAE,CAyBhF;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,SAAS,GAAG,MAAM,CAEzD;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,WAAW,CACzB,KAAK,EAAE,SAAS,EAChB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,aAAa,GAAG,QAAQ,GAAG,QAAQ,GAAG,MAAM,GAAG,KAAK,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,EACtF,OAAO,GAAE;IACP,SAAS,CAAC,EAAE,MAAM,GAAG,QAAQ,GAAG,OAAO,CAAA;CACnC,GACL,SAAS,CAkBX;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,WAAW,CACzB,KAAK,EAAE,SAAS,EAChB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,aAAa,GAAG,QAAQ,GAAG,QAAQ,GAAG,MAAM,GAAG,KAAK,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,EACtF,OAAO,GAAE;IACP,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,KAAK,CAAA;CAChC,GACL,SAAS,GAAG,IAAI,CAuBlB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,SAAS,GAAG,OAAO,CAEzE;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,KAAK,GAAE,KAAK,GAAG,MAAc,GAAG,SAAS,EAAE,CAO1F"}
|
|
@@ -0,0 +1,433 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Extended date range operations and utilities
|
|
3
|
+
* Provides advanced operations for working with date ranges beyond basic intervals
|
|
4
|
+
*/
|
|
5
|
+
import { addTime } from './calculate.js';
|
|
6
|
+
/**
|
|
7
|
+
* Checks if two date ranges overlap
|
|
8
|
+
* @param range1 - First date range
|
|
9
|
+
* @param range2 - Second date range
|
|
10
|
+
* @returns True if the ranges overlap
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```ts
|
|
14
|
+
* const range1 = { start: new Date('2024-01-01'), end: new Date('2024-01-10') };
|
|
15
|
+
* const range2 = { start: new Date('2024-01-05'), end: new Date('2024-01-15') };
|
|
16
|
+
*
|
|
17
|
+
* dateRangeOverlap(range1, range2); // true
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
export function dateRangeOverlap(range1, range2) {
|
|
21
|
+
return range1.start <= range2.end && range2.start <= range1.end;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Checks if multiple date ranges have any overlaps
|
|
25
|
+
* @param ranges - Array of date ranges
|
|
26
|
+
* @returns True if any two ranges overlap
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```ts
|
|
30
|
+
* const ranges = [
|
|
31
|
+
* { start: new Date('2024-01-01'), end: new Date('2024-01-10') },
|
|
32
|
+
* { start: new Date('2024-01-05'), end: new Date('2024-01-15') }
|
|
33
|
+
* ];
|
|
34
|
+
*
|
|
35
|
+
* hasOverlappingRanges(ranges); // true
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
export function hasOverlappingRanges(ranges) {
|
|
39
|
+
for (let i = 0; i < ranges.length; i++) {
|
|
40
|
+
for (let j = i + 1; j < ranges.length; j++) {
|
|
41
|
+
if (dateRangeOverlap(ranges[i], ranges[j])) {
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Merges overlapping or adjacent date ranges
|
|
50
|
+
* @param ranges - Array of date ranges to merge
|
|
51
|
+
* @returns Array of merged, non-overlapping ranges
|
|
52
|
+
*
|
|
53
|
+
* @example
|
|
54
|
+
* ```ts
|
|
55
|
+
* const ranges = [
|
|
56
|
+
* { start: new Date('2024-01-01'), end: new Date('2024-01-10') },
|
|
57
|
+
* { start: new Date('2024-01-05'), end: new Date('2024-01-15') },
|
|
58
|
+
* { start: new Date('2024-01-20'), end: new Date('2024-01-25') }
|
|
59
|
+
* ];
|
|
60
|
+
*
|
|
61
|
+
* mergeDateRanges(ranges);
|
|
62
|
+
* // [
|
|
63
|
+
* // { start: Date('2024-01-01'), end: Date('2024-01-15') },
|
|
64
|
+
* // { start: Date('2024-01-20'), end: Date('2024-01-25') }
|
|
65
|
+
* // ]
|
|
66
|
+
* ```
|
|
67
|
+
*/
|
|
68
|
+
export function mergeDateRanges(ranges) {
|
|
69
|
+
if (ranges.length === 0)
|
|
70
|
+
return [];
|
|
71
|
+
// Sort ranges by start date
|
|
72
|
+
const sorted = [...ranges].sort((a, b) => a.start.getTime() - b.start.getTime());
|
|
73
|
+
const merged = [{ ...sorted[0] }];
|
|
74
|
+
for (let i = 1; i < sorted.length; i++) {
|
|
75
|
+
const current = sorted[i];
|
|
76
|
+
const lastMerged = merged[merged.length - 1];
|
|
77
|
+
// Check if current range overlaps or is adjacent to last merged range
|
|
78
|
+
if (current.start <= lastMerged.end ||
|
|
79
|
+
current.start.getTime() === lastMerged.end.getTime() + 1) {
|
|
80
|
+
// Merge by extending the end date if needed
|
|
81
|
+
if (current.end > lastMerged.end) {
|
|
82
|
+
lastMerged.end = new Date(current.end);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
// No overlap, add as new range
|
|
87
|
+
merged.push({ ...current });
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return merged;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Finds gaps between date ranges within specified bounds
|
|
94
|
+
* @param ranges - Array of date ranges
|
|
95
|
+
* @param bounds - Optional bounds to search within
|
|
96
|
+
* @returns Array of date ranges representing gaps
|
|
97
|
+
*
|
|
98
|
+
* @example
|
|
99
|
+
* ```ts
|
|
100
|
+
* const ranges = [
|
|
101
|
+
* { start: new Date('2024-01-01'), end: new Date('2024-01-05') },
|
|
102
|
+
* { start: new Date('2024-01-10'), end: new Date('2024-01-15') }
|
|
103
|
+
* ];
|
|
104
|
+
*
|
|
105
|
+
* findGaps(ranges, {
|
|
106
|
+
* start: new Date('2024-01-01'),
|
|
107
|
+
* end: new Date('2024-01-20')
|
|
108
|
+
* });
|
|
109
|
+
* // [
|
|
110
|
+
* // { start: Date('2024-01-06'), end: Date('2024-01-09') },
|
|
111
|
+
* // { start: Date('2024-01-16'), end: Date('2024-01-20') }
|
|
112
|
+
* // ]
|
|
113
|
+
* ```
|
|
114
|
+
*/
|
|
115
|
+
export function findGaps(ranges, bounds) {
|
|
116
|
+
if (ranges.length === 0) {
|
|
117
|
+
return bounds ? [{ ...bounds }] : [];
|
|
118
|
+
}
|
|
119
|
+
// Merge overlapping ranges first
|
|
120
|
+
const merged = mergeDateRanges(ranges);
|
|
121
|
+
// Sort by start date
|
|
122
|
+
const sorted = merged.sort((a, b) => a.start.getTime() - b.start.getTime());
|
|
123
|
+
const gaps = [];
|
|
124
|
+
// Check gap before first range if bounds provided
|
|
125
|
+
if (bounds && sorted[0].start > bounds.start) {
|
|
126
|
+
gaps.push({
|
|
127
|
+
start: new Date(bounds.start),
|
|
128
|
+
end: addTime(sorted[0].start, -1, 'millisecond')
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
// Find gaps between ranges
|
|
132
|
+
for (let i = 0; i < sorted.length - 1; i++) {
|
|
133
|
+
const currentEnd = sorted[i].end;
|
|
134
|
+
const nextStart = sorted[i + 1].start;
|
|
135
|
+
if (nextStart > currentEnd) {
|
|
136
|
+
gaps.push({
|
|
137
|
+
start: addTime(currentEnd, 1, 'day'),
|
|
138
|
+
end: addTime(nextStart, -1, 'day')
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
// Check gap after last range if bounds provided
|
|
143
|
+
if (bounds && sorted[sorted.length - 1].end < bounds.end) {
|
|
144
|
+
gaps.push({
|
|
145
|
+
start: addTime(sorted[sorted.length - 1].end, 1, 'millisecond'),
|
|
146
|
+
end: new Date(bounds.end)
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
return gaps;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Splits a date range into smaller chunks
|
|
153
|
+
* @param range - The date range to split
|
|
154
|
+
* @param chunkSize - Size of each chunk
|
|
155
|
+
* @param unit - Unit for chunk size
|
|
156
|
+
* @returns Array of date ranges
|
|
157
|
+
*
|
|
158
|
+
* @example
|
|
159
|
+
* ```ts
|
|
160
|
+
* const range = {
|
|
161
|
+
* start: new Date('2024-01-01'),
|
|
162
|
+
* end: new Date('2024-01-10')
|
|
163
|
+
* };
|
|
164
|
+
*
|
|
165
|
+
* splitRange(range, 3, 'day');
|
|
166
|
+
* // Returns 4 ranges: 3 days, 3 days, 3 days, 1 day
|
|
167
|
+
* ```
|
|
168
|
+
*/
|
|
169
|
+
export function splitRange(range, chunkSize, unit) {
|
|
170
|
+
const chunks = [];
|
|
171
|
+
let current = new Date(range.start);
|
|
172
|
+
const rangeEnd = new Date(range.end);
|
|
173
|
+
// Keep looping while current hasn't passed the end
|
|
174
|
+
while (current.getTime() <= rangeEnd.getTime()) {
|
|
175
|
+
const chunkEnd = addTime(current, chunkSize, unit);
|
|
176
|
+
// Don't go past the range end
|
|
177
|
+
const effectiveEnd = chunkEnd > rangeEnd ? new Date(rangeEnd) : new Date(chunkEnd);
|
|
178
|
+
chunks.push({
|
|
179
|
+
start: new Date(current),
|
|
180
|
+
end: effectiveEnd
|
|
181
|
+
});
|
|
182
|
+
// Move to the next chunk
|
|
183
|
+
current = chunkEnd;
|
|
184
|
+
// If the next chunk would start after the range end, we're done
|
|
185
|
+
if (current.getTime() > rangeEnd.getTime()) {
|
|
186
|
+
break;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
return chunks;
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Checks if a date falls within a date range
|
|
193
|
+
* @param range - The date range
|
|
194
|
+
* @param date - The date to check
|
|
195
|
+
* @param inclusive - Whether to include boundary dates (default: true)
|
|
196
|
+
* @returns True if date is within range
|
|
197
|
+
*
|
|
198
|
+
* @example
|
|
199
|
+
* ```ts
|
|
200
|
+
* const range = {
|
|
201
|
+
* start: new Date('2024-01-01'),
|
|
202
|
+
* end: new Date('2024-01-31')
|
|
203
|
+
* };
|
|
204
|
+
*
|
|
205
|
+
* containsDate(range, new Date('2024-01-15')); // true
|
|
206
|
+
* containsDate(range, new Date('2024-02-01')); // false
|
|
207
|
+
* ```
|
|
208
|
+
*/
|
|
209
|
+
export function containsDate(range, date, inclusive = true) {
|
|
210
|
+
const checkDate = new Date(date);
|
|
211
|
+
if (inclusive) {
|
|
212
|
+
return checkDate >= range.start && checkDate <= range.end;
|
|
213
|
+
}
|
|
214
|
+
return checkDate > range.start && checkDate < range.end;
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Gets the intersection of two date ranges
|
|
218
|
+
* @param range1 - First date range
|
|
219
|
+
* @param range2 - Second date range
|
|
220
|
+
* @returns The overlapping range, or null if no overlap
|
|
221
|
+
*
|
|
222
|
+
* @example
|
|
223
|
+
* ```ts
|
|
224
|
+
* const range1 = { start: new Date('2024-01-01'), end: new Date('2024-01-15') };
|
|
225
|
+
* const range2 = { start: new Date('2024-01-10'), end: new Date('2024-01-20') };
|
|
226
|
+
*
|
|
227
|
+
* getIntersection(range1, range2);
|
|
228
|
+
* // { start: Date('2024-01-10'), end: Date('2024-01-15') }
|
|
229
|
+
* ```
|
|
230
|
+
*/
|
|
231
|
+
export function getIntersection(range1, range2) {
|
|
232
|
+
if (!dateRangeOverlap(range1, range2)) {
|
|
233
|
+
return null;
|
|
234
|
+
}
|
|
235
|
+
return {
|
|
236
|
+
start: new Date(Math.max(range1.start.getTime(), range2.start.getTime())),
|
|
237
|
+
end: new Date(Math.min(range1.end.getTime(), range2.end.getTime()))
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Gets the union (combined coverage) of two date ranges
|
|
242
|
+
* @param range1 - First date range
|
|
243
|
+
* @param range2 - Second date range
|
|
244
|
+
* @returns The combined range covering both inputs
|
|
245
|
+
*
|
|
246
|
+
* @example
|
|
247
|
+
* ```ts
|
|
248
|
+
* const range1 = { start: new Date('2024-01-01'), end: new Date('2024-01-15') };
|
|
249
|
+
* const range2 = { start: new Date('2024-01-10'), end: new Date('2024-01-20') };
|
|
250
|
+
*
|
|
251
|
+
* getUnion(range1, range2);
|
|
252
|
+
* // { start: Date('2024-01-01'), end: Date('2024-01-20') }
|
|
253
|
+
* ```
|
|
254
|
+
*/
|
|
255
|
+
export function getUnion(range1, range2) {
|
|
256
|
+
return {
|
|
257
|
+
start: new Date(Math.min(range1.start.getTime(), range2.start.getTime())),
|
|
258
|
+
end: new Date(Math.max(range1.end.getTime(), range2.end.getTime()))
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Subtracts one date range from another
|
|
263
|
+
* @param range - The range to subtract from
|
|
264
|
+
* @param subtract - The range to subtract
|
|
265
|
+
* @returns Array of remaining date ranges (0-2 ranges)
|
|
266
|
+
*
|
|
267
|
+
* @example
|
|
268
|
+
* ```ts
|
|
269
|
+
* const range = { start: new Date('2024-01-01'), end: new Date('2024-01-31') };
|
|
270
|
+
* const subtract = { start: new Date('2024-01-10'), end: new Date('2024-01-20') };
|
|
271
|
+
*
|
|
272
|
+
* subtractRange(range, subtract);
|
|
273
|
+
* // [
|
|
274
|
+
* // { start: Date('2024-01-01'), end: Date('2024-01-09') },
|
|
275
|
+
* // { start: Date('2024-01-21'), end: Date('2024-01-31') }
|
|
276
|
+
* // ]
|
|
277
|
+
* ```
|
|
278
|
+
*/
|
|
279
|
+
export function subtractRange(range, subtract) {
|
|
280
|
+
// No overlap, return original range
|
|
281
|
+
if (!dateRangeOverlap(range, subtract)) {
|
|
282
|
+
return [{ ...range }];
|
|
283
|
+
}
|
|
284
|
+
const result = [];
|
|
285
|
+
// Check if there's a range before the subtraction
|
|
286
|
+
if (range.start < subtract.start) {
|
|
287
|
+
result.push({
|
|
288
|
+
start: new Date(range.start),
|
|
289
|
+
end: addTime(subtract.start, -1, 'day')
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
// Check if there's a range after the subtraction
|
|
293
|
+
if (range.end > subtract.end) {
|
|
294
|
+
result.push({
|
|
295
|
+
start: addTime(subtract.end, 1, 'day'),
|
|
296
|
+
end: new Date(range.end)
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
return result;
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Calculates the duration of a date range in milliseconds
|
|
303
|
+
* @param range - The date range
|
|
304
|
+
* @returns Duration in milliseconds
|
|
305
|
+
*
|
|
306
|
+
* @example
|
|
307
|
+
* ```ts
|
|
308
|
+
* const range = {
|
|
309
|
+
* start: new Date('2024-01-01'),
|
|
310
|
+
* end: new Date('2024-01-02')
|
|
311
|
+
* };
|
|
312
|
+
*
|
|
313
|
+
* getRangeDuration(range); // 86400000 (1 day in ms)
|
|
314
|
+
* ```
|
|
315
|
+
*/
|
|
316
|
+
export function getRangeDuration(range) {
|
|
317
|
+
return range.end.getTime() - range.start.getTime();
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* Expands a date range by a specified amount
|
|
321
|
+
* @param range - The date range to expand
|
|
322
|
+
* @param amount - Amount to expand by
|
|
323
|
+
* @param unit - Unit for expansion
|
|
324
|
+
* @param options - Expansion options
|
|
325
|
+
* @returns Expanded date range
|
|
326
|
+
*
|
|
327
|
+
* @example
|
|
328
|
+
* ```ts
|
|
329
|
+
* const range = {
|
|
330
|
+
* start: new Date('2024-01-10'),
|
|
331
|
+
* end: new Date('2024-01-20')
|
|
332
|
+
* };
|
|
333
|
+
*
|
|
334
|
+
* expandRange(range, 5, 'day');
|
|
335
|
+
* // { start: Date('2024-01-05'), end: Date('2024-01-25') }
|
|
336
|
+
*
|
|
337
|
+
* expandRange(range, 5, 'day', { direction: 'before' });
|
|
338
|
+
* // { start: Date('2024-01-05'), end: Date('2024-01-20') }
|
|
339
|
+
* ```
|
|
340
|
+
*/
|
|
341
|
+
export function expandRange(range, amount, unit, options = {}) {
|
|
342
|
+
const { direction = 'both' } = options;
|
|
343
|
+
let newStart = new Date(range.start);
|
|
344
|
+
let newEnd = new Date(range.end);
|
|
345
|
+
if (direction === 'both' || direction === 'before') {
|
|
346
|
+
newStart = addTime(newStart, -amount, unit);
|
|
347
|
+
}
|
|
348
|
+
if (direction === 'both' || direction === 'after') {
|
|
349
|
+
newEnd = addTime(newEnd, amount, unit);
|
|
350
|
+
}
|
|
351
|
+
return {
|
|
352
|
+
start: newStart,
|
|
353
|
+
end: newEnd
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
/**
|
|
357
|
+
* Shrinks a date range by a specified amount
|
|
358
|
+
* @param range - The date range to shrink
|
|
359
|
+
* @param amount - Amount to shrink by
|
|
360
|
+
* @param unit - Unit for shrinking
|
|
361
|
+
* @param options - Shrink options
|
|
362
|
+
* @returns Shrunk date range, or null if result would be invalid
|
|
363
|
+
*
|
|
364
|
+
* @example
|
|
365
|
+
* ```ts
|
|
366
|
+
* const range = {
|
|
367
|
+
* start: new Date('2024-01-01'),
|
|
368
|
+
* end: new Date('2024-01-31')
|
|
369
|
+
* };
|
|
370
|
+
*
|
|
371
|
+
* shrinkRange(range, 5, 'day');
|
|
372
|
+
* // { start: Date('2024-01-06'), end: Date('2024-01-26') }
|
|
373
|
+
* ```
|
|
374
|
+
*/
|
|
375
|
+
export function shrinkRange(range, amount, unit, options = {}) {
|
|
376
|
+
const { direction = 'both' } = options;
|
|
377
|
+
let newStart = new Date(range.start);
|
|
378
|
+
let newEnd = new Date(range.end);
|
|
379
|
+
if (direction === 'both' || direction === 'start') {
|
|
380
|
+
newStart = addTime(newStart, amount, unit);
|
|
381
|
+
}
|
|
382
|
+
if (direction === 'both' || direction === 'end') {
|
|
383
|
+
newEnd = addTime(newEnd, -amount, unit);
|
|
384
|
+
}
|
|
385
|
+
// Check if result is valid
|
|
386
|
+
if (newStart >= newEnd) {
|
|
387
|
+
return null;
|
|
388
|
+
}
|
|
389
|
+
return {
|
|
390
|
+
start: newStart,
|
|
391
|
+
end: newEnd
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
/**
|
|
395
|
+
* Checks if one date range completely contains another
|
|
396
|
+
* @param outer - The potentially containing range
|
|
397
|
+
* @param inner - The potentially contained range
|
|
398
|
+
* @returns True if outer completely contains inner
|
|
399
|
+
*
|
|
400
|
+
* @example
|
|
401
|
+
* ```ts
|
|
402
|
+
* const outer = { start: new Date('2024-01-01'), end: new Date('2024-01-31') };
|
|
403
|
+
* const inner = { start: new Date('2024-01-10'), end: new Date('2024-01-20') };
|
|
404
|
+
*
|
|
405
|
+
* rangeContains(outer, inner); // true
|
|
406
|
+
* ```
|
|
407
|
+
*/
|
|
408
|
+
export function rangeContains(outer, inner) {
|
|
409
|
+
return outer.start <= inner.start && outer.end >= inner.end;
|
|
410
|
+
}
|
|
411
|
+
/**
|
|
412
|
+
* Sorts an array of date ranges by start date
|
|
413
|
+
* @param ranges - Array of date ranges
|
|
414
|
+
* @param order - Sort order ('asc' or 'desc')
|
|
415
|
+
* @returns Sorted array of date ranges
|
|
416
|
+
*
|
|
417
|
+
* @example
|
|
418
|
+
* ```ts
|
|
419
|
+
* const ranges = [
|
|
420
|
+
* { start: new Date('2024-01-15'), end: new Date('2024-01-20') },
|
|
421
|
+
* { start: new Date('2024-01-01'), end: new Date('2024-01-10') }
|
|
422
|
+
* ];
|
|
423
|
+
*
|
|
424
|
+
* sortRanges(ranges); // Sorted by start date ascending
|
|
425
|
+
* ```
|
|
426
|
+
*/
|
|
427
|
+
export function sortRanges(ranges, order = 'asc') {
|
|
428
|
+
const sorted = [...ranges].sort((a, b) => {
|
|
429
|
+
const diff = a.start.getTime() - b.start.getTime();
|
|
430
|
+
return order === 'asc' ? diff : -diff;
|
|
431
|
+
});
|
|
432
|
+
return sorted;
|
|
433
|
+
}
|