ts-time-utils 1.0.0 → 2.0.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 +682 -11
- package/dist/calculate.d.ts +7 -2
- package/dist/calculate.d.ts.map +1 -1
- package/dist/calculate.js +37 -13
- package/dist/calendar.d.ts +103 -0
- package/dist/calendar.d.ts.map +1 -1
- package/dist/calendar.js +224 -0
- package/dist/compare.d.ts +217 -0
- package/dist/compare.d.ts.map +1 -0
- package/dist/compare.js +417 -0
- package/dist/countdown.d.ts +217 -0
- package/dist/countdown.d.ts.map +1 -0
- package/dist/countdown.js +298 -0
- package/dist/cron.d.ts +82 -0
- package/dist/cron.d.ts.map +1 -0
- package/dist/cron.js +294 -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 +7 -2
- package/dist/esm/calculate.d.ts.map +1 -1
- package/dist/esm/calculate.js +37 -13
- package/dist/esm/calendar.d.ts +103 -0
- package/dist/esm/calendar.d.ts.map +1 -1
- package/dist/esm/calendar.js +224 -0
- package/dist/esm/compare.d.ts +217 -0
- package/dist/esm/compare.d.ts.map +1 -0
- package/dist/esm/compare.js +417 -0
- 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/cron.d.ts +82 -0
- package/dist/esm/cron.d.ts.map +1 -0
- package/dist/esm/cron.js +294 -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/fiscal.d.ts +195 -0
- package/dist/esm/fiscal.d.ts.map +1 -0
- package/dist/esm/fiscal.js +295 -0
- package/dist/esm/format.d.ts +65 -0
- package/dist/esm/format.d.ts.map +1 -1
- package/dist/esm/format.js +202 -0
- package/dist/esm/index.d.ts +18 -7
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +22 -6
- package/dist/esm/iterate.d.ts +212 -0
- package/dist/esm/iterate.d.ts.map +1 -0
- package/dist/esm/iterate.js +409 -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/parse.d.ts +45 -0
- package/dist/esm/parse.d.ts.map +1 -1
- package/dist/esm/parse.js +207 -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/timezone.d.ts +52 -0
- package/dist/esm/timezone.d.ts.map +1 -1
- package/dist/esm/timezone.js +171 -0
- package/dist/esm/types.d.ts +21 -0
- package/dist/esm/types.d.ts.map +1 -1
- package/dist/esm/validate.d.ts +51 -0
- package/dist/esm/validate.d.ts.map +1 -1
- package/dist/esm/validate.js +92 -0
- package/dist/esm/workingHours.d.ts +70 -0
- package/dist/esm/workingHours.d.ts.map +1 -1
- package/dist/esm/workingHours.js +161 -0
- package/dist/fiscal.d.ts +195 -0
- package/dist/fiscal.d.ts.map +1 -0
- package/dist/fiscal.js +295 -0
- package/dist/format.d.ts +65 -0
- package/dist/format.d.ts.map +1 -1
- package/dist/format.js +202 -0
- package/dist/index.d.ts +18 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +22 -6
- package/dist/iterate.d.ts +212 -0
- package/dist/iterate.d.ts.map +1 -0
- package/dist/iterate.js +409 -0
- package/dist/naturalLanguage.d.ts +107 -0
- package/dist/naturalLanguage.d.ts.map +1 -0
- package/dist/naturalLanguage.js +344 -0
- package/dist/parse.d.ts +45 -0
- package/dist/parse.d.ts.map +1 -1
- package/dist/parse.js +207 -0
- package/dist/recurrence.d.ts +149 -0
- package/dist/recurrence.d.ts.map +1 -0
- package/dist/recurrence.js +404 -0
- package/dist/timezone.d.ts +52 -0
- package/dist/timezone.d.ts.map +1 -1
- package/dist/timezone.js +171 -0
- package/dist/types.d.ts +21 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/validate.d.ts +51 -0
- package/dist/validate.d.ts.map +1 -1
- package/dist/validate.js +92 -0
- package/dist/workingHours.d.ts +70 -0
- package/dist/workingHours.d.ts.map +1 -1
- package/dist/workingHours.js +161 -0
- package/package.json +59 -12
|
@@ -0,0 +1,417 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Date comparison, sorting, and array manipulation utilities
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Compare two dates for sorting
|
|
6
|
+
* @param a - First date
|
|
7
|
+
* @param b - Second date
|
|
8
|
+
* @returns Negative if a < b, positive if a > b, 0 if equal
|
|
9
|
+
* @example
|
|
10
|
+
* [date3, date1, date2].sort(compareDates) // [date1, date2, date3]
|
|
11
|
+
*/
|
|
12
|
+
export function compareDates(a, b) {
|
|
13
|
+
return a.getTime() - b.getTime();
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Compare two dates in reverse order for sorting
|
|
17
|
+
* @param a - First date
|
|
18
|
+
* @param b - Second date
|
|
19
|
+
* @returns Positive if a < b, negative if a > b, 0 if equal
|
|
20
|
+
* @example
|
|
21
|
+
* [date1, date3, date2].sort(compareDatesDesc) // [date3, date2, date1]
|
|
22
|
+
*/
|
|
23
|
+
export function compareDatesDesc(a, b) {
|
|
24
|
+
return b.getTime() - a.getTime();
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Sort an array of dates
|
|
28
|
+
* @param dates - Array of dates to sort
|
|
29
|
+
* @param direction - Sort direction: 'asc' (oldest first) or 'desc' (newest first)
|
|
30
|
+
* @returns New sorted array (does not mutate original)
|
|
31
|
+
* @example
|
|
32
|
+
* sortDates([date3, date1, date2]) // [date1, date2, date3]
|
|
33
|
+
* sortDates([date1, date2, date3], 'desc') // [date3, date2, date1]
|
|
34
|
+
*/
|
|
35
|
+
export function sortDates(dates, direction = 'asc') {
|
|
36
|
+
const sorted = [...dates];
|
|
37
|
+
sorted.sort(direction === 'asc' ? compareDates : compareDatesDesc);
|
|
38
|
+
return sorted;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Find the minimum (earliest) date in an array
|
|
42
|
+
* @param dates - Array of dates
|
|
43
|
+
* @returns The earliest date, or undefined if array is empty
|
|
44
|
+
* @example
|
|
45
|
+
* minDate([date2, date1, date3]) // date1
|
|
46
|
+
*/
|
|
47
|
+
export function minDate(dates) {
|
|
48
|
+
if (dates.length === 0)
|
|
49
|
+
return undefined;
|
|
50
|
+
return dates.reduce((min, date) => (date < min ? date : min));
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Find the maximum (latest) date in an array
|
|
54
|
+
* @param dates - Array of dates
|
|
55
|
+
* @returns The latest date, or undefined if array is empty
|
|
56
|
+
* @example
|
|
57
|
+
* maxDate([date1, date3, date2]) // date3
|
|
58
|
+
*/
|
|
59
|
+
export function maxDate(dates) {
|
|
60
|
+
if (dates.length === 0)
|
|
61
|
+
return undefined;
|
|
62
|
+
return dates.reduce((max, date) => (date > max ? date : max));
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Find the date range (min and max) in an array
|
|
66
|
+
* @param dates - Array of dates
|
|
67
|
+
* @returns Object with min and max dates, or undefined if array is empty
|
|
68
|
+
* @example
|
|
69
|
+
* dateRange([date2, date1, date3]) // { min: date1, max: date3 }
|
|
70
|
+
*/
|
|
71
|
+
export function dateExtent(dates) {
|
|
72
|
+
if (dates.length === 0)
|
|
73
|
+
return undefined;
|
|
74
|
+
let min = dates[0];
|
|
75
|
+
let max = dates[0];
|
|
76
|
+
for (const date of dates) {
|
|
77
|
+
if (date < min)
|
|
78
|
+
min = date;
|
|
79
|
+
if (date > max)
|
|
80
|
+
max = date;
|
|
81
|
+
}
|
|
82
|
+
return { min, max };
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Remove duplicate dates from an array
|
|
86
|
+
* @param dates - Array of dates
|
|
87
|
+
* @param precision - Precision for comparison: 'ms' (exact), 'second', 'minute', 'hour', 'day'
|
|
88
|
+
* @returns New array with duplicates removed (preserves first occurrence)
|
|
89
|
+
* @example
|
|
90
|
+
* uniqueDates([date1, date1Copy, date2]) // [date1, date2]
|
|
91
|
+
*/
|
|
92
|
+
export function uniqueDates(dates, precision = 'ms') {
|
|
93
|
+
const seen = new Set();
|
|
94
|
+
const result = [];
|
|
95
|
+
for (const date of dates) {
|
|
96
|
+
const key = getDateKey(date, precision);
|
|
97
|
+
if (!seen.has(key)) {
|
|
98
|
+
seen.add(key);
|
|
99
|
+
result.push(date);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return result;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Get a numeric key for a date based on precision
|
|
106
|
+
*/
|
|
107
|
+
function getDateKey(date, precision) {
|
|
108
|
+
switch (precision) {
|
|
109
|
+
case 'ms':
|
|
110
|
+
return date.getTime();
|
|
111
|
+
case 'second':
|
|
112
|
+
return Math.floor(date.getTime() / 1000);
|
|
113
|
+
case 'minute':
|
|
114
|
+
return Math.floor(date.getTime() / 60000);
|
|
115
|
+
case 'hour':
|
|
116
|
+
return Math.floor(date.getTime() / 3600000);
|
|
117
|
+
case 'day':
|
|
118
|
+
return Math.floor(date.getTime() / 86400000);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Find the closest date to a target from an array of candidates
|
|
123
|
+
* @param target - The target date
|
|
124
|
+
* @param candidates - Array of candidate dates
|
|
125
|
+
* @returns The closest date, or undefined if candidates is empty
|
|
126
|
+
* @example
|
|
127
|
+
* closestDate(targetDate, [date1, date2, date3]) // closest to target
|
|
128
|
+
*/
|
|
129
|
+
export function closestDate(target, candidates) {
|
|
130
|
+
if (candidates.length === 0)
|
|
131
|
+
return undefined;
|
|
132
|
+
const targetTime = target.getTime();
|
|
133
|
+
let closest = candidates[0];
|
|
134
|
+
let minDiff = Math.abs(closest.getTime() - targetTime);
|
|
135
|
+
for (const candidate of candidates) {
|
|
136
|
+
const diff = Math.abs(candidate.getTime() - targetTime);
|
|
137
|
+
if (diff < minDiff) {
|
|
138
|
+
minDiff = diff;
|
|
139
|
+
closest = candidate;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return closest;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Find the closest future date to a target
|
|
146
|
+
* @param target - The target date
|
|
147
|
+
* @param candidates - Array of candidate dates
|
|
148
|
+
* @returns The closest future date, or undefined if none found
|
|
149
|
+
*/
|
|
150
|
+
export function closestFutureDate(target, candidates) {
|
|
151
|
+
const targetTime = target.getTime();
|
|
152
|
+
const futureDates = candidates.filter(d => d.getTime() > targetTime);
|
|
153
|
+
return minDate(futureDates);
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Find the closest past date to a target
|
|
157
|
+
* @param target - The target date
|
|
158
|
+
* @param candidates - Array of candidate dates
|
|
159
|
+
* @returns The closest past date, or undefined if none found
|
|
160
|
+
*/
|
|
161
|
+
export function closestPastDate(target, candidates) {
|
|
162
|
+
const targetTime = target.getTime();
|
|
163
|
+
const pastDates = candidates.filter(d => d.getTime() < targetTime);
|
|
164
|
+
return maxDate(pastDates);
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Clamp a date to be within a range
|
|
168
|
+
* @param date - The date to clamp
|
|
169
|
+
* @param min - Minimum allowed date
|
|
170
|
+
* @param max - Maximum allowed date
|
|
171
|
+
* @returns The clamped date
|
|
172
|
+
* @example
|
|
173
|
+
* clampDate(earlyDate, minDate, maxDate) // returns minDate
|
|
174
|
+
* clampDate(lateDate, minDate, maxDate) // returns maxDate
|
|
175
|
+
* clampDate(middleDate, minDate, maxDate) // returns middleDate
|
|
176
|
+
*/
|
|
177
|
+
export function clampDate(date, min, max) {
|
|
178
|
+
if (date < min)
|
|
179
|
+
return new Date(min);
|
|
180
|
+
if (date > max)
|
|
181
|
+
return new Date(max);
|
|
182
|
+
return new Date(date);
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Check if a date is within a range (inclusive)
|
|
186
|
+
* @param date - The date to check
|
|
187
|
+
* @param min - Start of range
|
|
188
|
+
* @param max - End of range
|
|
189
|
+
* @returns True if date is within range
|
|
190
|
+
*/
|
|
191
|
+
export function isDateInRange(date, min, max) {
|
|
192
|
+
const time = date.getTime();
|
|
193
|
+
return time >= min.getTime() && time <= max.getTime();
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Filter dates to only those within a range
|
|
197
|
+
* @param dates - Array of dates
|
|
198
|
+
* @param min - Start of range
|
|
199
|
+
* @param max - End of range
|
|
200
|
+
* @returns New array with only dates in range
|
|
201
|
+
*/
|
|
202
|
+
export function filterDatesInRange(dates, min, max) {
|
|
203
|
+
return dates.filter(date => isDateInRange(date, min, max));
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Group dates by a key function
|
|
207
|
+
* @param dates - Array of dates
|
|
208
|
+
* @param keyFn - Function to generate group key from date
|
|
209
|
+
* @returns Map of key to array of dates
|
|
210
|
+
* @example
|
|
211
|
+
* groupDates(dates, d => d.getFullYear()) // Map { 2023 => [...], 2024 => [...] }
|
|
212
|
+
* groupDates(dates, d => d.toISOString().slice(0, 7)) // Group by month
|
|
213
|
+
*/
|
|
214
|
+
export function groupDates(dates, keyFn) {
|
|
215
|
+
const groups = new Map();
|
|
216
|
+
for (const date of dates) {
|
|
217
|
+
const key = keyFn(date);
|
|
218
|
+
const group = groups.get(key);
|
|
219
|
+
if (group) {
|
|
220
|
+
group.push(date);
|
|
221
|
+
}
|
|
222
|
+
else {
|
|
223
|
+
groups.set(key, [date]);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
return groups;
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Group dates by year
|
|
230
|
+
* @param dates - Array of dates
|
|
231
|
+
* @returns Map of year to array of dates
|
|
232
|
+
*/
|
|
233
|
+
export function groupDatesByYear(dates) {
|
|
234
|
+
return groupDates(dates, d => d.getFullYear());
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Group dates by month (YYYY-MM format)
|
|
238
|
+
* @param dates - Array of dates
|
|
239
|
+
* @returns Map of month key to array of dates
|
|
240
|
+
*/
|
|
241
|
+
export function groupDatesByMonth(dates) {
|
|
242
|
+
return groupDates(dates, d => {
|
|
243
|
+
const year = d.getFullYear();
|
|
244
|
+
const month = String(d.getMonth() + 1).padStart(2, '0');
|
|
245
|
+
return `${year}-${month}`;
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Group dates by day (YYYY-MM-DD format)
|
|
250
|
+
* @param dates - Array of dates
|
|
251
|
+
* @returns Map of day key to array of dates
|
|
252
|
+
*/
|
|
253
|
+
export function groupDatesByDay(dates) {
|
|
254
|
+
return groupDates(dates, d => d.toISOString().slice(0, 10));
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Group dates by day of week (0-6, Sunday-Saturday)
|
|
258
|
+
* @param dates - Array of dates
|
|
259
|
+
* @returns Map of day of week to array of dates
|
|
260
|
+
*/
|
|
261
|
+
export function groupDatesByDayOfWeek(dates) {
|
|
262
|
+
return groupDates(dates, d => d.getDay());
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Calculate the median date from an array
|
|
266
|
+
* @param dates - Array of dates
|
|
267
|
+
* @returns The median date, or undefined if array is empty
|
|
268
|
+
*/
|
|
269
|
+
export function medianDate(dates) {
|
|
270
|
+
if (dates.length === 0)
|
|
271
|
+
return undefined;
|
|
272
|
+
const sorted = sortDates(dates);
|
|
273
|
+
const mid = Math.floor(sorted.length / 2);
|
|
274
|
+
if (sorted.length % 2 === 0) {
|
|
275
|
+
// Average of two middle values
|
|
276
|
+
const time1 = sorted[mid - 1].getTime();
|
|
277
|
+
const time2 = sorted[mid].getTime();
|
|
278
|
+
return new Date((time1 + time2) / 2);
|
|
279
|
+
}
|
|
280
|
+
return new Date(sorted[mid]);
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Calculate the average/mean date from an array
|
|
284
|
+
* @param dates - Array of dates
|
|
285
|
+
* @returns The average date, or undefined if array is empty
|
|
286
|
+
*/
|
|
287
|
+
export function averageDate(dates) {
|
|
288
|
+
if (dates.length === 0)
|
|
289
|
+
return undefined;
|
|
290
|
+
const sum = dates.reduce((acc, date) => acc + date.getTime(), 0);
|
|
291
|
+
return new Date(sum / dates.length);
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* Round a date to the nearest unit
|
|
295
|
+
* @param date - The date to round
|
|
296
|
+
* @param unit - Unit to round to
|
|
297
|
+
* @returns New rounded date
|
|
298
|
+
* @example
|
|
299
|
+
* roundDate(new Date('2024-03-15T14:37:00'), 'hour') // 2024-03-15T15:00:00
|
|
300
|
+
* roundDate(new Date('2024-03-15T14:22:00'), 'hour') // 2024-03-15T14:00:00
|
|
301
|
+
*/
|
|
302
|
+
export function roundDate(date, unit) {
|
|
303
|
+
const d = new Date(date);
|
|
304
|
+
switch (unit) {
|
|
305
|
+
case 'minute':
|
|
306
|
+
if (d.getSeconds() >= 30) {
|
|
307
|
+
d.setMinutes(d.getMinutes() + 1);
|
|
308
|
+
}
|
|
309
|
+
d.setSeconds(0, 0);
|
|
310
|
+
break;
|
|
311
|
+
case 'hour':
|
|
312
|
+
if (d.getMinutes() >= 30) {
|
|
313
|
+
d.setHours(d.getHours() + 1);
|
|
314
|
+
}
|
|
315
|
+
d.setMinutes(0, 0, 0);
|
|
316
|
+
break;
|
|
317
|
+
case 'day':
|
|
318
|
+
if (d.getHours() >= 12) {
|
|
319
|
+
d.setDate(d.getDate() + 1);
|
|
320
|
+
}
|
|
321
|
+
d.setHours(0, 0, 0, 0);
|
|
322
|
+
break;
|
|
323
|
+
}
|
|
324
|
+
return d;
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* Snap a date to a grid interval
|
|
328
|
+
* @param date - The date to snap
|
|
329
|
+
* @param intervalMinutes - Interval in minutes (e.g., 15 for quarter-hour)
|
|
330
|
+
* @param mode - Snap mode: 'floor' (down), 'ceil' (up), or 'round' (nearest)
|
|
331
|
+
* @returns New snapped date
|
|
332
|
+
* @example
|
|
333
|
+
* snapDate(new Date('2024-03-15T14:37:00'), 15) // 2024-03-15T14:30:00
|
|
334
|
+
* snapDate(new Date('2024-03-15T14:37:00'), 15, 'ceil') // 2024-03-15T14:45:00
|
|
335
|
+
*/
|
|
336
|
+
export function snapDate(date, intervalMinutes, mode = 'round') {
|
|
337
|
+
const ms = date.getTime();
|
|
338
|
+
const intervalMs = intervalMinutes * 60 * 1000;
|
|
339
|
+
let snapped;
|
|
340
|
+
switch (mode) {
|
|
341
|
+
case 'floor':
|
|
342
|
+
snapped = Math.floor(ms / intervalMs) * intervalMs;
|
|
343
|
+
break;
|
|
344
|
+
case 'ceil':
|
|
345
|
+
snapped = Math.ceil(ms / intervalMs) * intervalMs;
|
|
346
|
+
break;
|
|
347
|
+
case 'round':
|
|
348
|
+
snapped = Math.round(ms / intervalMs) * intervalMs;
|
|
349
|
+
break;
|
|
350
|
+
}
|
|
351
|
+
return new Date(snapped);
|
|
352
|
+
}
|
|
353
|
+
/**
|
|
354
|
+
* Check if dates are in chronological order
|
|
355
|
+
* @param dates - Array of dates
|
|
356
|
+
* @param strict - If true, requires strictly increasing (no duplicates)
|
|
357
|
+
* @returns True if dates are in order
|
|
358
|
+
*/
|
|
359
|
+
export function isChronological(dates, strict = false) {
|
|
360
|
+
for (let i = 1; i < dates.length; i++) {
|
|
361
|
+
const prev = dates[i - 1].getTime();
|
|
362
|
+
const curr = dates[i].getTime();
|
|
363
|
+
if (strict ? curr <= prev : curr < prev) {
|
|
364
|
+
return false;
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
return true;
|
|
368
|
+
}
|
|
369
|
+
/**
|
|
370
|
+
* Get the span (duration) between min and max dates
|
|
371
|
+
* @param dates - Array of dates
|
|
372
|
+
* @returns Duration in milliseconds, or 0 if less than 2 dates
|
|
373
|
+
*/
|
|
374
|
+
export function dateSpan(dates) {
|
|
375
|
+
const extent = dateExtent(dates);
|
|
376
|
+
if (!extent)
|
|
377
|
+
return 0;
|
|
378
|
+
return extent.max.getTime() - extent.min.getTime();
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* Partition dates into buckets based on a predicate
|
|
382
|
+
* @param dates - Array of dates
|
|
383
|
+
* @param predicate - Function that returns true for dates in first partition
|
|
384
|
+
* @returns Tuple of [matching, non-matching] date arrays
|
|
385
|
+
*/
|
|
386
|
+
export function partitionDates(dates, predicate) {
|
|
387
|
+
const matching = [];
|
|
388
|
+
const nonMatching = [];
|
|
389
|
+
for (const date of dates) {
|
|
390
|
+
if (predicate(date)) {
|
|
391
|
+
matching.push(date);
|
|
392
|
+
}
|
|
393
|
+
else {
|
|
394
|
+
nonMatching.push(date);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
return [matching, nonMatching];
|
|
398
|
+
}
|
|
399
|
+
/**
|
|
400
|
+
* Get the nth date from an array (supports negative indices)
|
|
401
|
+
* @param dates - Array of dates (will be sorted)
|
|
402
|
+
* @param n - Index (0-based, negative counts from end)
|
|
403
|
+
* @returns The nth date, or undefined if out of bounds
|
|
404
|
+
* @example
|
|
405
|
+
* nthDate(dates, 0) // earliest
|
|
406
|
+
* nthDate(dates, -1) // latest
|
|
407
|
+
* nthDate(dates, 2) // third earliest
|
|
408
|
+
*/
|
|
409
|
+
export function nthDate(dates, n) {
|
|
410
|
+
if (dates.length === 0)
|
|
411
|
+
return undefined;
|
|
412
|
+
const sorted = sortDates(dates);
|
|
413
|
+
const index = n < 0 ? sorted.length + n : n;
|
|
414
|
+
if (index < 0 || index >= sorted.length)
|
|
415
|
+
return undefined;
|
|
416
|
+
return sorted[index];
|
|
417
|
+
}
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Countdown and timer utilities for tracking time until/since a target date
|
|
3
|
+
* Provides countdown timers, remaining time calculations, and progress tracking
|
|
4
|
+
*/
|
|
5
|
+
import type { DateInput } from './types.js';
|
|
6
|
+
/**
|
|
7
|
+
* Represents the remaining time broken down by units
|
|
8
|
+
*/
|
|
9
|
+
export interface RemainingTime {
|
|
10
|
+
/** Total milliseconds remaining */
|
|
11
|
+
totalMilliseconds: number;
|
|
12
|
+
/** Total seconds remaining */
|
|
13
|
+
totalSeconds: number;
|
|
14
|
+
/** Total minutes remaining */
|
|
15
|
+
totalMinutes: number;
|
|
16
|
+
/** Total hours remaining */
|
|
17
|
+
totalHours: number;
|
|
18
|
+
/** Total days remaining */
|
|
19
|
+
totalDays: number;
|
|
20
|
+
/** Milliseconds component (0-999) */
|
|
21
|
+
milliseconds: number;
|
|
22
|
+
/** Seconds component (0-59) */
|
|
23
|
+
seconds: number;
|
|
24
|
+
/** Minutes component (0-59) */
|
|
25
|
+
minutes: number;
|
|
26
|
+
/** Hours component (0-23) */
|
|
27
|
+
hours: number;
|
|
28
|
+
/** Days component */
|
|
29
|
+
days: number;
|
|
30
|
+
/** Weeks component */
|
|
31
|
+
weeks: number;
|
|
32
|
+
/** Whether the target date has passed */
|
|
33
|
+
isExpired: boolean;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Options for countdown creation
|
|
37
|
+
*/
|
|
38
|
+
export interface CountdownOptions {
|
|
39
|
+
/** Callback fired on each tick */
|
|
40
|
+
onTick?: (remaining: RemainingTime) => void;
|
|
41
|
+
/** Callback fired when countdown reaches zero */
|
|
42
|
+
onComplete?: () => void;
|
|
43
|
+
/** Callback fired if target date is in the past */
|
|
44
|
+
onExpired?: () => void;
|
|
45
|
+
/** Tick interval in milliseconds (default: 1000) */
|
|
46
|
+
interval?: number;
|
|
47
|
+
/** Whether to fire onTick immediately (default: true) */
|
|
48
|
+
immediate?: boolean;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Countdown timer instance
|
|
52
|
+
*/
|
|
53
|
+
export interface Countdown {
|
|
54
|
+
/** Start the countdown */
|
|
55
|
+
start: () => void;
|
|
56
|
+
/** Stop the countdown */
|
|
57
|
+
stop: () => void;
|
|
58
|
+
/** Reset countdown with a new target date */
|
|
59
|
+
reset: (targetDate: DateInput) => void;
|
|
60
|
+
/** Get current remaining time */
|
|
61
|
+
getRemaining: () => RemainingTime;
|
|
62
|
+
/** Check if countdown is running */
|
|
63
|
+
isRunning: () => boolean;
|
|
64
|
+
/** Check if target date has passed */
|
|
65
|
+
isExpired: () => boolean;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Creates a countdown timer to a target date
|
|
69
|
+
* @param targetDate - The date to count down to
|
|
70
|
+
* @param options - Countdown options and callbacks
|
|
71
|
+
* @returns A countdown instance with control methods
|
|
72
|
+
*
|
|
73
|
+
* @example
|
|
74
|
+
* ```ts
|
|
75
|
+
* const countdown = createCountdown(
|
|
76
|
+
* new Date('2024-12-31T23:59:59'),
|
|
77
|
+
* {
|
|
78
|
+
* onTick: (remaining) => {
|
|
79
|
+
* console.log(`${remaining.days}d ${remaining.hours}h ${remaining.minutes}m ${remaining.seconds}s`);
|
|
80
|
+
* },
|
|
81
|
+
* onComplete: () => {
|
|
82
|
+
* console.log('Happy New Year!');
|
|
83
|
+
* }
|
|
84
|
+
* }
|
|
85
|
+
* );
|
|
86
|
+
*
|
|
87
|
+
* countdown.start();
|
|
88
|
+
* // Later...
|
|
89
|
+
* countdown.stop();
|
|
90
|
+
* ```
|
|
91
|
+
*/
|
|
92
|
+
export declare function createCountdown(targetDate: DateInput, options?: CountdownOptions): Countdown;
|
|
93
|
+
/**
|
|
94
|
+
* Gets the remaining time until/since a target date
|
|
95
|
+
* @param targetDate - The target date
|
|
96
|
+
* @param fromDate - The date to calculate from (defaults to now)
|
|
97
|
+
* @returns Object with remaining time broken down by units
|
|
98
|
+
*
|
|
99
|
+
* @example
|
|
100
|
+
* ```ts
|
|
101
|
+
* const remaining = getRemainingTime(new Date('2024-12-31'));
|
|
102
|
+
* console.log(`${remaining.days} days, ${remaining.hours} hours remaining`);
|
|
103
|
+
*
|
|
104
|
+
* // Check if expired
|
|
105
|
+
* if (remaining.isExpired) {
|
|
106
|
+
* console.log('Target date has passed');
|
|
107
|
+
* }
|
|
108
|
+
* ```
|
|
109
|
+
*/
|
|
110
|
+
export declare function getRemainingTime(targetDate: DateInput, fromDate?: DateInput): RemainingTime;
|
|
111
|
+
/**
|
|
112
|
+
* Formats the remaining time as a human-readable string
|
|
113
|
+
* @param targetDate - The target date
|
|
114
|
+
* @param options - Formatting options
|
|
115
|
+
* @returns Formatted countdown string
|
|
116
|
+
*
|
|
117
|
+
* @example
|
|
118
|
+
* ```ts
|
|
119
|
+
* formatCountdown(new Date('2024-12-31'));
|
|
120
|
+
* // "45d 12h 30m 15s"
|
|
121
|
+
*
|
|
122
|
+
* formatCountdown(new Date('2024-12-31'), { units: ['days', 'hours'] });
|
|
123
|
+
* // "45 days, 12 hours"
|
|
124
|
+
*
|
|
125
|
+
* formatCountdown(new Date('2024-12-31'), { short: false });
|
|
126
|
+
* // "45 days 12 hours 30 minutes 15 seconds"
|
|
127
|
+
* ```
|
|
128
|
+
*/
|
|
129
|
+
export declare function formatCountdown(targetDate: DateInput, options?: {
|
|
130
|
+
/** Date to calculate from (defaults to now) */
|
|
131
|
+
from?: DateInput;
|
|
132
|
+
/** Units to include in output */
|
|
133
|
+
units?: ('weeks' | 'days' | 'hours' | 'minutes' | 'seconds' | 'milliseconds')[];
|
|
134
|
+
/** Use short format (d, h, m, s) */
|
|
135
|
+
short?: boolean;
|
|
136
|
+
/** Maximum number of units to show */
|
|
137
|
+
maxUnits?: number;
|
|
138
|
+
/** Show zero values */
|
|
139
|
+
showZero?: boolean;
|
|
140
|
+
/** Separator between units */
|
|
141
|
+
separator?: string;
|
|
142
|
+
}): string;
|
|
143
|
+
/**
|
|
144
|
+
* Checks if a date has expired (is in the past)
|
|
145
|
+
* @param date - The date to check
|
|
146
|
+
* @param fromDate - The reference date (defaults to now)
|
|
147
|
+
* @returns True if the date is in the past
|
|
148
|
+
*
|
|
149
|
+
* @example
|
|
150
|
+
* ```ts
|
|
151
|
+
* isExpired(new Date('2020-01-01')); // true
|
|
152
|
+
* isExpired(new Date('2030-01-01')); // false
|
|
153
|
+
* ```
|
|
154
|
+
*/
|
|
155
|
+
export declare function isExpired(date: DateInput, fromDate?: DateInput): boolean;
|
|
156
|
+
/**
|
|
157
|
+
* Calculates the progress percentage between two dates
|
|
158
|
+
* @param startDate - The start date
|
|
159
|
+
* @param endDate - The end date
|
|
160
|
+
* @param currentDate - The current date (defaults to now)
|
|
161
|
+
* @returns Progress percentage (0-100), clamped to range
|
|
162
|
+
*
|
|
163
|
+
* @example
|
|
164
|
+
* ```ts
|
|
165
|
+
* const progress = getProgressPercentage(
|
|
166
|
+
* new Date('2024-01-01'),
|
|
167
|
+
* new Date('2024-12-31'),
|
|
168
|
+
* new Date('2024-07-01')
|
|
169
|
+
* );
|
|
170
|
+
* console.log(`${progress}% complete`); // ~50% complete
|
|
171
|
+
* ```
|
|
172
|
+
*/
|
|
173
|
+
export declare function getProgressPercentage(startDate: DateInput, endDate: DateInput, currentDate?: DateInput): number;
|
|
174
|
+
/**
|
|
175
|
+
* Gets time until a target date in a specific unit
|
|
176
|
+
* @param targetDate - The target date
|
|
177
|
+
* @param unit - The unit to return
|
|
178
|
+
* @param fromDate - The date to calculate from (defaults to now)
|
|
179
|
+
* @returns Time remaining in the specified unit
|
|
180
|
+
*
|
|
181
|
+
* @example
|
|
182
|
+
* ```ts
|
|
183
|
+
* getTimeUntil(new Date('2024-12-31'), 'days'); // 45.5
|
|
184
|
+
* getTimeUntil(new Date('2024-12-31'), 'hours'); // 1092
|
|
185
|
+
* getTimeUntil(new Date('2024-12-31'), 'weeks'); // 6.5
|
|
186
|
+
* ```
|
|
187
|
+
*/
|
|
188
|
+
export declare function getTimeUntil(targetDate: DateInput, unit: 'milliseconds' | 'seconds' | 'minutes' | 'hours' | 'days' | 'weeks', fromDate?: DateInput): number;
|
|
189
|
+
/**
|
|
190
|
+
* Creates a deadline object with useful methods
|
|
191
|
+
* @param targetDate - The deadline date
|
|
192
|
+
* @returns An object with deadline-related methods
|
|
193
|
+
*
|
|
194
|
+
* @example
|
|
195
|
+
* ```ts
|
|
196
|
+
* const deadline = createDeadline(new Date('2024-12-31'));
|
|
197
|
+
*
|
|
198
|
+
* deadline.isExpired(); // false
|
|
199
|
+
* deadline.daysRemaining(); // 45
|
|
200
|
+
* deadline.hoursRemaining(); // 1092
|
|
201
|
+
* deadline.formatRemaining(); // "45d 12h 30m"
|
|
202
|
+
* deadline.progressFrom(new Date('2024-01-01')); // 67.5%
|
|
203
|
+
* ```
|
|
204
|
+
*/
|
|
205
|
+
export declare function createDeadline(targetDate: DateInput): {
|
|
206
|
+
target: Date;
|
|
207
|
+
isExpired: () => boolean;
|
|
208
|
+
getRemaining: () => RemainingTime;
|
|
209
|
+
daysRemaining: () => number;
|
|
210
|
+
hoursRemaining: () => number;
|
|
211
|
+
minutesRemaining: () => number;
|
|
212
|
+
secondsRemaining: () => number;
|
|
213
|
+
formatRemaining: (options?: Parameters<typeof formatCountdown>[1]) => string;
|
|
214
|
+
progressFrom: (startDate: DateInput) => number;
|
|
215
|
+
countdown: (options?: CountdownOptions) => Countdown;
|
|
216
|
+
};
|
|
217
|
+
//# sourceMappingURL=countdown.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"countdown.d.ts","sourceRoot":"","sources":["../../src/countdown.ts"],"names":[],"mappings":"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"}
|