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,298 @@
|
|
|
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
|
+
/**
|
|
6
|
+
* Creates a countdown timer to a target date
|
|
7
|
+
* @param targetDate - The date to count down to
|
|
8
|
+
* @param options - Countdown options and callbacks
|
|
9
|
+
* @returns A countdown instance with control methods
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```ts
|
|
13
|
+
* const countdown = createCountdown(
|
|
14
|
+
* new Date('2024-12-31T23:59:59'),
|
|
15
|
+
* {
|
|
16
|
+
* onTick: (remaining) => {
|
|
17
|
+
* console.log(`${remaining.days}d ${remaining.hours}h ${remaining.minutes}m ${remaining.seconds}s`);
|
|
18
|
+
* },
|
|
19
|
+
* onComplete: () => {
|
|
20
|
+
* console.log('Happy New Year!');
|
|
21
|
+
* }
|
|
22
|
+
* }
|
|
23
|
+
* );
|
|
24
|
+
*
|
|
25
|
+
* countdown.start();
|
|
26
|
+
* // Later...
|
|
27
|
+
* countdown.stop();
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
export function createCountdown(targetDate, options = {}) {
|
|
31
|
+
let target = new Date(targetDate);
|
|
32
|
+
let intervalId = null;
|
|
33
|
+
let running = false;
|
|
34
|
+
const { onTick, onComplete, onExpired, interval = 1000, immediate = true } = options;
|
|
35
|
+
const getRemaining = () => {
|
|
36
|
+
return getRemainingTime(target);
|
|
37
|
+
};
|
|
38
|
+
const tick = () => {
|
|
39
|
+
const remaining = getRemaining();
|
|
40
|
+
if (onTick) {
|
|
41
|
+
onTick(remaining);
|
|
42
|
+
}
|
|
43
|
+
if (remaining.isExpired) {
|
|
44
|
+
stop();
|
|
45
|
+
if (onComplete) {
|
|
46
|
+
onComplete();
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
const start = () => {
|
|
51
|
+
if (running)
|
|
52
|
+
return;
|
|
53
|
+
const remaining = getRemaining();
|
|
54
|
+
if (remaining.totalMilliseconds < 0) {
|
|
55
|
+
if (onExpired) {
|
|
56
|
+
onExpired();
|
|
57
|
+
}
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
running = true;
|
|
61
|
+
if (immediate) {
|
|
62
|
+
tick();
|
|
63
|
+
}
|
|
64
|
+
intervalId = setInterval(tick, interval);
|
|
65
|
+
};
|
|
66
|
+
const stop = () => {
|
|
67
|
+
if (!running)
|
|
68
|
+
return;
|
|
69
|
+
running = false;
|
|
70
|
+
if (intervalId !== null) {
|
|
71
|
+
clearInterval(intervalId);
|
|
72
|
+
intervalId = null;
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
const reset = (newTarget) => {
|
|
76
|
+
stop();
|
|
77
|
+
target = new Date(newTarget);
|
|
78
|
+
};
|
|
79
|
+
const isRunning = () => running;
|
|
80
|
+
const isExpiredCheck = () => {
|
|
81
|
+
return getRemaining().isExpired;
|
|
82
|
+
};
|
|
83
|
+
return {
|
|
84
|
+
start,
|
|
85
|
+
stop,
|
|
86
|
+
reset,
|
|
87
|
+
getRemaining,
|
|
88
|
+
isRunning,
|
|
89
|
+
isExpired: isExpiredCheck
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Gets the remaining time until/since a target date
|
|
94
|
+
* @param targetDate - The target date
|
|
95
|
+
* @param fromDate - The date to calculate from (defaults to now)
|
|
96
|
+
* @returns Object with remaining time broken down by units
|
|
97
|
+
*
|
|
98
|
+
* @example
|
|
99
|
+
* ```ts
|
|
100
|
+
* const remaining = getRemainingTime(new Date('2024-12-31'));
|
|
101
|
+
* console.log(`${remaining.days} days, ${remaining.hours} hours remaining`);
|
|
102
|
+
*
|
|
103
|
+
* // Check if expired
|
|
104
|
+
* if (remaining.isExpired) {
|
|
105
|
+
* console.log('Target date has passed');
|
|
106
|
+
* }
|
|
107
|
+
* ```
|
|
108
|
+
*/
|
|
109
|
+
export function getRemainingTime(targetDate, fromDate = new Date()) {
|
|
110
|
+
const target = new Date(targetDate);
|
|
111
|
+
const from = new Date(fromDate);
|
|
112
|
+
const totalMilliseconds = target.getTime() - from.getTime();
|
|
113
|
+
const isExpired = totalMilliseconds <= 0;
|
|
114
|
+
// Use absolute values for calculations
|
|
115
|
+
const absTotalMs = Math.abs(totalMilliseconds);
|
|
116
|
+
const totalSeconds = Math.floor(absTotalMs / 1000);
|
|
117
|
+
const totalMinutes = Math.floor(totalSeconds / 60);
|
|
118
|
+
const totalHours = Math.floor(totalMinutes / 60);
|
|
119
|
+
const totalDays = Math.floor(totalHours / 24);
|
|
120
|
+
const milliseconds = Math.floor(absTotalMs % 1000);
|
|
121
|
+
const seconds = totalSeconds % 60;
|
|
122
|
+
const minutes = totalMinutes % 60;
|
|
123
|
+
const hours = totalHours % 24;
|
|
124
|
+
const days = totalDays; // Don't mod by 7 - let the formatter decide
|
|
125
|
+
const weeks = Math.floor(totalDays / 7);
|
|
126
|
+
return {
|
|
127
|
+
totalMilliseconds,
|
|
128
|
+
totalSeconds: totalMilliseconds >= 0 ? totalSeconds : -totalSeconds,
|
|
129
|
+
totalMinutes: totalMilliseconds >= 0 ? totalMinutes : -totalMinutes,
|
|
130
|
+
totalHours: totalMilliseconds >= 0 ? totalHours : -totalHours,
|
|
131
|
+
totalDays: totalMilliseconds >= 0 ? totalDays : -totalDays,
|
|
132
|
+
milliseconds,
|
|
133
|
+
seconds,
|
|
134
|
+
minutes,
|
|
135
|
+
hours,
|
|
136
|
+
days,
|
|
137
|
+
weeks,
|
|
138
|
+
isExpired
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Formats the remaining time as a human-readable string
|
|
143
|
+
* @param targetDate - The target date
|
|
144
|
+
* @param options - Formatting options
|
|
145
|
+
* @returns Formatted countdown string
|
|
146
|
+
*
|
|
147
|
+
* @example
|
|
148
|
+
* ```ts
|
|
149
|
+
* formatCountdown(new Date('2024-12-31'));
|
|
150
|
+
* // "45d 12h 30m 15s"
|
|
151
|
+
*
|
|
152
|
+
* formatCountdown(new Date('2024-12-31'), { units: ['days', 'hours'] });
|
|
153
|
+
* // "45 days, 12 hours"
|
|
154
|
+
*
|
|
155
|
+
* formatCountdown(new Date('2024-12-31'), { short: false });
|
|
156
|
+
* // "45 days 12 hours 30 minutes 15 seconds"
|
|
157
|
+
* ```
|
|
158
|
+
*/
|
|
159
|
+
export function formatCountdown(targetDate, options = {}) {
|
|
160
|
+
const { from, units = ['days', 'hours', 'minutes', 'seconds'], short = true, maxUnits, showZero = false, separator = ' ' } = options;
|
|
161
|
+
const remaining = getRemainingTime(targetDate, from);
|
|
162
|
+
if (remaining.isExpired) {
|
|
163
|
+
return 'Expired';
|
|
164
|
+
}
|
|
165
|
+
const parts = [];
|
|
166
|
+
for (const unit of units) {
|
|
167
|
+
const value = remaining[unit];
|
|
168
|
+
if (value === 0 && !showZero && parts.length === 0) {
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
if (value === 0 && !showZero) {
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
if (maxUnits && parts.length >= maxUnits) {
|
|
175
|
+
break;
|
|
176
|
+
}
|
|
177
|
+
if (short) {
|
|
178
|
+
const shortUnit = unit[0];
|
|
179
|
+
parts.push(`${value}${shortUnit}`);
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
const unitName = value === 1 ? unit.slice(0, -1) : unit;
|
|
183
|
+
parts.push(`${value} ${unitName}`);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return parts.length > 0 ? parts.join(separator) : '0s';
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Checks if a date has expired (is in the past)
|
|
190
|
+
* @param date - The date to check
|
|
191
|
+
* @param fromDate - The reference date (defaults to now)
|
|
192
|
+
* @returns True if the date is in the past
|
|
193
|
+
*
|
|
194
|
+
* @example
|
|
195
|
+
* ```ts
|
|
196
|
+
* isExpired(new Date('2020-01-01')); // true
|
|
197
|
+
* isExpired(new Date('2030-01-01')); // false
|
|
198
|
+
* ```
|
|
199
|
+
*/
|
|
200
|
+
export function isExpired(date, fromDate = new Date()) {
|
|
201
|
+
const checkDate = new Date(date);
|
|
202
|
+
const from = new Date(fromDate);
|
|
203
|
+
return checkDate.getTime() < from.getTime();
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Calculates the progress percentage between two dates
|
|
207
|
+
* @param startDate - The start date
|
|
208
|
+
* @param endDate - The end date
|
|
209
|
+
* @param currentDate - The current date (defaults to now)
|
|
210
|
+
* @returns Progress percentage (0-100), clamped to range
|
|
211
|
+
*
|
|
212
|
+
* @example
|
|
213
|
+
* ```ts
|
|
214
|
+
* const progress = getProgressPercentage(
|
|
215
|
+
* new Date('2024-01-01'),
|
|
216
|
+
* new Date('2024-12-31'),
|
|
217
|
+
* new Date('2024-07-01')
|
|
218
|
+
* );
|
|
219
|
+
* console.log(`${progress}% complete`); // ~50% complete
|
|
220
|
+
* ```
|
|
221
|
+
*/
|
|
222
|
+
export function getProgressPercentage(startDate, endDate, currentDate = new Date()) {
|
|
223
|
+
const start = new Date(startDate).getTime();
|
|
224
|
+
const end = new Date(endDate).getTime();
|
|
225
|
+
const current = new Date(currentDate).getTime();
|
|
226
|
+
if (start >= end) {
|
|
227
|
+
throw new Error('Start date must be before end date');
|
|
228
|
+
}
|
|
229
|
+
const total = end - start;
|
|
230
|
+
const elapsed = current - start;
|
|
231
|
+
const percentage = (elapsed / total) * 100;
|
|
232
|
+
// Clamp between 0 and 100
|
|
233
|
+
return Math.max(0, Math.min(100, percentage));
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Gets time until a target date in a specific unit
|
|
237
|
+
* @param targetDate - The target date
|
|
238
|
+
* @param unit - The unit to return
|
|
239
|
+
* @param fromDate - The date to calculate from (defaults to now)
|
|
240
|
+
* @returns Time remaining in the specified unit
|
|
241
|
+
*
|
|
242
|
+
* @example
|
|
243
|
+
* ```ts
|
|
244
|
+
* getTimeUntil(new Date('2024-12-31'), 'days'); // 45.5
|
|
245
|
+
* getTimeUntil(new Date('2024-12-31'), 'hours'); // 1092
|
|
246
|
+
* getTimeUntil(new Date('2024-12-31'), 'weeks'); // 6.5
|
|
247
|
+
* ```
|
|
248
|
+
*/
|
|
249
|
+
export function getTimeUntil(targetDate, unit, fromDate = new Date()) {
|
|
250
|
+
const remaining = getRemainingTime(targetDate, fromDate);
|
|
251
|
+
switch (unit) {
|
|
252
|
+
case 'milliseconds':
|
|
253
|
+
return remaining.totalMilliseconds;
|
|
254
|
+
case 'seconds':
|
|
255
|
+
return remaining.totalSeconds;
|
|
256
|
+
case 'minutes':
|
|
257
|
+
return remaining.totalMinutes;
|
|
258
|
+
case 'hours':
|
|
259
|
+
return remaining.totalHours;
|
|
260
|
+
case 'days':
|
|
261
|
+
return remaining.totalDays;
|
|
262
|
+
case 'weeks':
|
|
263
|
+
return remaining.totalDays / 7;
|
|
264
|
+
default:
|
|
265
|
+
return remaining.totalMilliseconds;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Creates a deadline object with useful methods
|
|
270
|
+
* @param targetDate - The deadline date
|
|
271
|
+
* @returns An object with deadline-related methods
|
|
272
|
+
*
|
|
273
|
+
* @example
|
|
274
|
+
* ```ts
|
|
275
|
+
* const deadline = createDeadline(new Date('2024-12-31'));
|
|
276
|
+
*
|
|
277
|
+
* deadline.isExpired(); // false
|
|
278
|
+
* deadline.daysRemaining(); // 45
|
|
279
|
+
* deadline.hoursRemaining(); // 1092
|
|
280
|
+
* deadline.formatRemaining(); // "45d 12h 30m"
|
|
281
|
+
* deadline.progressFrom(new Date('2024-01-01')); // 67.5%
|
|
282
|
+
* ```
|
|
283
|
+
*/
|
|
284
|
+
export function createDeadline(targetDate) {
|
|
285
|
+
const target = new Date(targetDate);
|
|
286
|
+
return {
|
|
287
|
+
target,
|
|
288
|
+
isExpired: () => isExpired(target),
|
|
289
|
+
getRemaining: () => getRemainingTime(target),
|
|
290
|
+
daysRemaining: () => getTimeUntil(target, 'days'),
|
|
291
|
+
hoursRemaining: () => getTimeUntil(target, 'hours'),
|
|
292
|
+
minutesRemaining: () => getTimeUntil(target, 'minutes'),
|
|
293
|
+
secondsRemaining: () => getTimeUntil(target, 'seconds'),
|
|
294
|
+
formatRemaining: (options) => formatCountdown(target, options),
|
|
295
|
+
progressFrom: (startDate) => getProgressPercentage(startDate, target),
|
|
296
|
+
countdown: (options) => createCountdown(target, options)
|
|
297
|
+
};
|
|
298
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cron expression utilities for scheduling
|
|
3
|
+
*/
|
|
4
|
+
export interface CronParts {
|
|
5
|
+
minute: string;
|
|
6
|
+
hour: string;
|
|
7
|
+
dayOfMonth: string;
|
|
8
|
+
month: string;
|
|
9
|
+
dayOfWeek: string;
|
|
10
|
+
}
|
|
11
|
+
export interface ParsedCronField {
|
|
12
|
+
type: 'all' | 'specific' | 'range' | 'step' | 'list';
|
|
13
|
+
values: number[];
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Parse a cron expression into its parts
|
|
17
|
+
* @param expression - cron expression (5 fields: minute hour dayOfMonth month dayOfWeek)
|
|
18
|
+
*/
|
|
19
|
+
export declare function parseCronExpression(expression: string): CronParts | null;
|
|
20
|
+
/**
|
|
21
|
+
* Parse a cron field into its numeric values
|
|
22
|
+
* @param field - cron field string (e.g., "star/5", "1-5", "1,2,3", "*")
|
|
23
|
+
* @param min - minimum valid value
|
|
24
|
+
* @param max - maximum valid value
|
|
25
|
+
*/
|
|
26
|
+
export declare function parseCronField(field: string, min: number, max: number): ParsedCronField | null;
|
|
27
|
+
/**
|
|
28
|
+
* Check if a date matches a cron expression
|
|
29
|
+
* @param date - date to check
|
|
30
|
+
* @param expression - cron expression
|
|
31
|
+
*/
|
|
32
|
+
export declare function matchesCron(date: Date, expression: string): boolean;
|
|
33
|
+
/**
|
|
34
|
+
* Get the next date that matches a cron expression
|
|
35
|
+
* @param expression - cron expression
|
|
36
|
+
* @param after - start searching after this date (default: now)
|
|
37
|
+
* @param maxIterations - maximum iterations to prevent infinite loops
|
|
38
|
+
*/
|
|
39
|
+
export declare function getNextCronDate(expression: string, after?: Date, maxIterations?: number): Date | null;
|
|
40
|
+
/**
|
|
41
|
+
* Get the N next dates that match a cron expression
|
|
42
|
+
* @param expression - cron expression
|
|
43
|
+
* @param count - number of dates to get
|
|
44
|
+
* @param after - start searching after this date
|
|
45
|
+
*/
|
|
46
|
+
export declare function getNextCronDates(expression: string, count: number, after?: Date): Date[];
|
|
47
|
+
/**
|
|
48
|
+
* Get the previous date that matched a cron expression
|
|
49
|
+
* @param expression - cron expression
|
|
50
|
+
* @param before - start searching before this date
|
|
51
|
+
* @param maxIterations - maximum iterations to prevent infinite loops
|
|
52
|
+
*/
|
|
53
|
+
export declare function getPreviousCronDate(expression: string, before?: Date, maxIterations?: number): Date | null;
|
|
54
|
+
/**
|
|
55
|
+
* Validate a cron expression
|
|
56
|
+
* @param expression - cron expression to validate
|
|
57
|
+
*/
|
|
58
|
+
export declare function isValidCron(expression: string): boolean;
|
|
59
|
+
/**
|
|
60
|
+
* Convert a cron expression to a human-readable description
|
|
61
|
+
* @param expression - cron expression
|
|
62
|
+
*/
|
|
63
|
+
export declare function describeCron(expression: string): string | null;
|
|
64
|
+
/**
|
|
65
|
+
* Common cron expressions
|
|
66
|
+
*/
|
|
67
|
+
export declare const CRON_PRESETS: {
|
|
68
|
+
readonly everyMinute: "* * * * *";
|
|
69
|
+
readonly everyHour: "0 * * * *";
|
|
70
|
+
readonly everyDay: "0 0 * * *";
|
|
71
|
+
readonly everyDayAt9am: "0 9 * * *";
|
|
72
|
+
readonly everyDayAt6pm: "0 18 * * *";
|
|
73
|
+
readonly everyWeek: "0 0 * * 0";
|
|
74
|
+
readonly everyMonth: "0 0 1 * *";
|
|
75
|
+
readonly everyYear: "0 0 1 1 *";
|
|
76
|
+
readonly weekdays: "0 0 * * 1-5";
|
|
77
|
+
readonly weekends: "0 0 * * 0,6";
|
|
78
|
+
readonly every5Minutes: "*/5 * * * *";
|
|
79
|
+
readonly every15Minutes: "*/15 * * * *";
|
|
80
|
+
readonly every30Minutes: "*/30 * * * *";
|
|
81
|
+
};
|
|
82
|
+
//# sourceMappingURL=cron.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cron.d.ts","sourceRoot":"","sources":["../../src/cron.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,MAAM,WAAW,SAAS;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,KAAK,GAAG,UAAU,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,CAAC;IACrD,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAcxE;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,eAAe,GAAG,IAAI,CA+D9F;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAqBnE;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAC7B,UAAU,EAAE,MAAM,EAClB,KAAK,GAAE,IAAiB,EACxB,aAAa,GAAE,MAAe,GAC7B,IAAI,GAAG,IAAI,CAiCb;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAC9B,UAAU,EAAE,MAAM,EAClB,KAAK,EAAE,MAAM,EACb,KAAK,GAAE,IAAiB,GACvB,IAAI,EAAE,CAYR;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CACjC,UAAU,EAAE,MAAM,EAClB,MAAM,GAAE,IAAiB,EACzB,aAAa,GAAE,MAAe,GAC7B,IAAI,GAAG,IAAI,CAiCb;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAWvD;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAwE9D;AAED;;GAEG;AACH,eAAO,MAAM,YAAY;;;;;;;;;;;;;;CAcf,CAAC"}
|
package/dist/esm/cron.js
ADDED
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cron expression utilities for scheduling
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Parse a cron expression into its parts
|
|
6
|
+
* @param expression - cron expression (5 fields: minute hour dayOfMonth month dayOfWeek)
|
|
7
|
+
*/
|
|
8
|
+
export function parseCronExpression(expression) {
|
|
9
|
+
const parts = expression.trim().split(/\s+/);
|
|
10
|
+
if (parts.length !== 5) {
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
return {
|
|
14
|
+
minute: parts[0],
|
|
15
|
+
hour: parts[1],
|
|
16
|
+
dayOfMonth: parts[2],
|
|
17
|
+
month: parts[3],
|
|
18
|
+
dayOfWeek: parts[4],
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Parse a cron field into its numeric values
|
|
23
|
+
* @param field - cron field string (e.g., "star/5", "1-5", "1,2,3", "*")
|
|
24
|
+
* @param min - minimum valid value
|
|
25
|
+
* @param max - maximum valid value
|
|
26
|
+
*/
|
|
27
|
+
export function parseCronField(field, min, max) {
|
|
28
|
+
const values = [];
|
|
29
|
+
// Handle wildcard
|
|
30
|
+
if (field === '*') {
|
|
31
|
+
for (let i = min; i <= max; i++) {
|
|
32
|
+
values.push(i);
|
|
33
|
+
}
|
|
34
|
+
return { type: 'all', values };
|
|
35
|
+
}
|
|
36
|
+
// Handle step values (*/n or range/n)
|
|
37
|
+
if (field.includes('/')) {
|
|
38
|
+
const [range, stepStr] = field.split('/');
|
|
39
|
+
const step = parseInt(stepStr, 10);
|
|
40
|
+
if (isNaN(step) || step <= 0)
|
|
41
|
+
return null;
|
|
42
|
+
let start = min;
|
|
43
|
+
let end = max;
|
|
44
|
+
if (range !== '*') {
|
|
45
|
+
if (range.includes('-')) {
|
|
46
|
+
const [s, e] = range.split('-').map(Number);
|
|
47
|
+
start = s;
|
|
48
|
+
end = e;
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
start = parseInt(range, 10);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
for (let i = start; i <= end; i += step) {
|
|
55
|
+
values.push(i);
|
|
56
|
+
}
|
|
57
|
+
return { type: 'step', values };
|
|
58
|
+
}
|
|
59
|
+
// Handle range (n-m)
|
|
60
|
+
if (field.includes('-')) {
|
|
61
|
+
const [start, end] = field.split('-').map(Number);
|
|
62
|
+
if (isNaN(start) || isNaN(end) || start > end)
|
|
63
|
+
return null;
|
|
64
|
+
for (let i = start; i <= end; i++) {
|
|
65
|
+
values.push(i);
|
|
66
|
+
}
|
|
67
|
+
return { type: 'range', values };
|
|
68
|
+
}
|
|
69
|
+
// Handle list (n,m,o)
|
|
70
|
+
if (field.includes(',')) {
|
|
71
|
+
const items = field.split(',').map(Number);
|
|
72
|
+
if (items.some(isNaN))
|
|
73
|
+
return null;
|
|
74
|
+
return { type: 'list', values: items.sort((a, b) => a - b) };
|
|
75
|
+
}
|
|
76
|
+
// Handle specific value
|
|
77
|
+
const value = parseInt(field, 10);
|
|
78
|
+
if (isNaN(value) || value < min || value > max)
|
|
79
|
+
return null;
|
|
80
|
+
return { type: 'specific', values: [value] };
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Check if a date matches a cron expression
|
|
84
|
+
* @param date - date to check
|
|
85
|
+
* @param expression - cron expression
|
|
86
|
+
*/
|
|
87
|
+
export function matchesCron(date, expression) {
|
|
88
|
+
const parts = parseCronExpression(expression);
|
|
89
|
+
if (!parts)
|
|
90
|
+
return false;
|
|
91
|
+
const minute = parseCronField(parts.minute, 0, 59);
|
|
92
|
+
const hour = parseCronField(parts.hour, 0, 23);
|
|
93
|
+
const dayOfMonth = parseCronField(parts.dayOfMonth, 1, 31);
|
|
94
|
+
const month = parseCronField(parts.month, 1, 12);
|
|
95
|
+
const dayOfWeek = parseCronField(parts.dayOfWeek, 0, 6);
|
|
96
|
+
if (!minute || !hour || !dayOfMonth || !month || !dayOfWeek) {
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
return (minute.values.includes(date.getMinutes()) &&
|
|
100
|
+
hour.values.includes(date.getHours()) &&
|
|
101
|
+
dayOfMonth.values.includes(date.getDate()) &&
|
|
102
|
+
month.values.includes(date.getMonth() + 1) &&
|
|
103
|
+
dayOfWeek.values.includes(date.getDay()));
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Get the next date that matches a cron expression
|
|
107
|
+
* @param expression - cron expression
|
|
108
|
+
* @param after - start searching after this date (default: now)
|
|
109
|
+
* @param maxIterations - maximum iterations to prevent infinite loops
|
|
110
|
+
*/
|
|
111
|
+
export function getNextCronDate(expression, after = new Date(), maxIterations = 525600 // Max 1 year in minutes
|
|
112
|
+
) {
|
|
113
|
+
const parts = parseCronExpression(expression);
|
|
114
|
+
if (!parts)
|
|
115
|
+
return null;
|
|
116
|
+
const minute = parseCronField(parts.minute, 0, 59);
|
|
117
|
+
const hour = parseCronField(parts.hour, 0, 23);
|
|
118
|
+
const dayOfMonth = parseCronField(parts.dayOfMonth, 1, 31);
|
|
119
|
+
const month = parseCronField(parts.month, 1, 12);
|
|
120
|
+
const dayOfWeek = parseCronField(parts.dayOfWeek, 0, 6);
|
|
121
|
+
if (!minute || !hour || !dayOfMonth || !month || !dayOfWeek) {
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
const candidate = new Date(after);
|
|
125
|
+
candidate.setSeconds(0, 0);
|
|
126
|
+
candidate.setMinutes(candidate.getMinutes() + 1);
|
|
127
|
+
for (let i = 0; i < maxIterations; i++) {
|
|
128
|
+
if (minute.values.includes(candidate.getMinutes()) &&
|
|
129
|
+
hour.values.includes(candidate.getHours()) &&
|
|
130
|
+
dayOfMonth.values.includes(candidate.getDate()) &&
|
|
131
|
+
month.values.includes(candidate.getMonth() + 1) &&
|
|
132
|
+
dayOfWeek.values.includes(candidate.getDay())) {
|
|
133
|
+
return candidate;
|
|
134
|
+
}
|
|
135
|
+
candidate.setMinutes(candidate.getMinutes() + 1);
|
|
136
|
+
}
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Get the N next dates that match a cron expression
|
|
141
|
+
* @param expression - cron expression
|
|
142
|
+
* @param count - number of dates to get
|
|
143
|
+
* @param after - start searching after this date
|
|
144
|
+
*/
|
|
145
|
+
export function getNextCronDates(expression, count, after = new Date()) {
|
|
146
|
+
const dates = [];
|
|
147
|
+
let currentAfter = after;
|
|
148
|
+
for (let i = 0; i < count; i++) {
|
|
149
|
+
const next = getNextCronDate(expression, currentAfter);
|
|
150
|
+
if (!next)
|
|
151
|
+
break;
|
|
152
|
+
dates.push(next);
|
|
153
|
+
currentAfter = next;
|
|
154
|
+
}
|
|
155
|
+
return dates;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Get the previous date that matched a cron expression
|
|
159
|
+
* @param expression - cron expression
|
|
160
|
+
* @param before - start searching before this date
|
|
161
|
+
* @param maxIterations - maximum iterations to prevent infinite loops
|
|
162
|
+
*/
|
|
163
|
+
export function getPreviousCronDate(expression, before = new Date(), maxIterations = 525600) {
|
|
164
|
+
const parts = parseCronExpression(expression);
|
|
165
|
+
if (!parts)
|
|
166
|
+
return null;
|
|
167
|
+
const minute = parseCronField(parts.minute, 0, 59);
|
|
168
|
+
const hour = parseCronField(parts.hour, 0, 23);
|
|
169
|
+
const dayOfMonth = parseCronField(parts.dayOfMonth, 1, 31);
|
|
170
|
+
const month = parseCronField(parts.month, 1, 12);
|
|
171
|
+
const dayOfWeek = parseCronField(parts.dayOfWeek, 0, 6);
|
|
172
|
+
if (!minute || !hour || !dayOfMonth || !month || !dayOfWeek) {
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
175
|
+
const candidate = new Date(before);
|
|
176
|
+
candidate.setSeconds(0, 0);
|
|
177
|
+
candidate.setMinutes(candidate.getMinutes() - 1);
|
|
178
|
+
for (let i = 0; i < maxIterations; i++) {
|
|
179
|
+
if (minute.values.includes(candidate.getMinutes()) &&
|
|
180
|
+
hour.values.includes(candidate.getHours()) &&
|
|
181
|
+
dayOfMonth.values.includes(candidate.getDate()) &&
|
|
182
|
+
month.values.includes(candidate.getMonth() + 1) &&
|
|
183
|
+
dayOfWeek.values.includes(candidate.getDay())) {
|
|
184
|
+
return candidate;
|
|
185
|
+
}
|
|
186
|
+
candidate.setMinutes(candidate.getMinutes() - 1);
|
|
187
|
+
}
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Validate a cron expression
|
|
192
|
+
* @param expression - cron expression to validate
|
|
193
|
+
*/
|
|
194
|
+
export function isValidCron(expression) {
|
|
195
|
+
const parts = parseCronExpression(expression);
|
|
196
|
+
if (!parts)
|
|
197
|
+
return false;
|
|
198
|
+
const minute = parseCronField(parts.minute, 0, 59);
|
|
199
|
+
const hour = parseCronField(parts.hour, 0, 23);
|
|
200
|
+
const dayOfMonth = parseCronField(parts.dayOfMonth, 1, 31);
|
|
201
|
+
const month = parseCronField(parts.month, 1, 12);
|
|
202
|
+
const dayOfWeek = parseCronField(parts.dayOfWeek, 0, 6);
|
|
203
|
+
return !!(minute && hour && dayOfMonth && month && dayOfWeek);
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Convert a cron expression to a human-readable description
|
|
207
|
+
* @param expression - cron expression
|
|
208
|
+
*/
|
|
209
|
+
export function describeCron(expression) {
|
|
210
|
+
const parts = parseCronExpression(expression);
|
|
211
|
+
if (!parts)
|
|
212
|
+
return null;
|
|
213
|
+
const descriptions = [];
|
|
214
|
+
// Handle common patterns
|
|
215
|
+
if (expression === '* * * * *') {
|
|
216
|
+
return 'Every minute';
|
|
217
|
+
}
|
|
218
|
+
if (expression === '0 * * * *') {
|
|
219
|
+
return 'Every hour';
|
|
220
|
+
}
|
|
221
|
+
if (expression === '0 0 * * *') {
|
|
222
|
+
return 'Every day at midnight';
|
|
223
|
+
}
|
|
224
|
+
if (expression === '0 0 * * 0') {
|
|
225
|
+
return 'Every Sunday at midnight';
|
|
226
|
+
}
|
|
227
|
+
if (expression === '0 0 1 * *') {
|
|
228
|
+
return 'First day of every month at midnight';
|
|
229
|
+
}
|
|
230
|
+
// Build description
|
|
231
|
+
const minute = parts.minute;
|
|
232
|
+
const hour = parts.hour;
|
|
233
|
+
if (minute === '0' && hour !== '*') {
|
|
234
|
+
if (hour.includes('/')) {
|
|
235
|
+
const step = hour.split('/')[1];
|
|
236
|
+
descriptions.push(`Every ${step} hours`);
|
|
237
|
+
}
|
|
238
|
+
else if (hour.includes('-')) {
|
|
239
|
+
descriptions.push(`Every hour from ${hour} at minute 0`);
|
|
240
|
+
}
|
|
241
|
+
else {
|
|
242
|
+
descriptions.push(`At ${hour}:00`);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
else if (minute !== '*' && hour === '*') {
|
|
246
|
+
descriptions.push(`At minute ${minute} of every hour`);
|
|
247
|
+
}
|
|
248
|
+
else if (minute.includes('/')) {
|
|
249
|
+
const step = minute.split('/')[1];
|
|
250
|
+
descriptions.push(`Every ${step} minutes`);
|
|
251
|
+
}
|
|
252
|
+
if (parts.dayOfMonth !== '*') {
|
|
253
|
+
descriptions.push(`on day ${parts.dayOfMonth} of the month`);
|
|
254
|
+
}
|
|
255
|
+
if (parts.month !== '*') {
|
|
256
|
+
const monthNames = ['', 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
|
|
257
|
+
const monthNum = parseInt(parts.month, 10);
|
|
258
|
+
if (!isNaN(monthNum) && monthNum >= 1 && monthNum <= 12) {
|
|
259
|
+
descriptions.push(`in ${monthNames[monthNum]}`);
|
|
260
|
+
}
|
|
261
|
+
else {
|
|
262
|
+
descriptions.push(`in month ${parts.month}`);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
if (parts.dayOfWeek !== '*') {
|
|
266
|
+
const dayNames = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
|
|
267
|
+
const dayNum = parseInt(parts.dayOfWeek, 10);
|
|
268
|
+
if (!isNaN(dayNum) && dayNum >= 0 && dayNum <= 6) {
|
|
269
|
+
descriptions.push(`on ${dayNames[dayNum]}`);
|
|
270
|
+
}
|
|
271
|
+
else {
|
|
272
|
+
descriptions.push(`on day of week ${parts.dayOfWeek}`);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
return descriptions.join(' ') || expression;
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Common cron expressions
|
|
279
|
+
*/
|
|
280
|
+
export const CRON_PRESETS = {
|
|
281
|
+
everyMinute: '* * * * *',
|
|
282
|
+
everyHour: '0 * * * *',
|
|
283
|
+
everyDay: '0 0 * * *',
|
|
284
|
+
everyDayAt9am: '0 9 * * *',
|
|
285
|
+
everyDayAt6pm: '0 18 * * *',
|
|
286
|
+
everyWeek: '0 0 * * 0',
|
|
287
|
+
everyMonth: '0 0 1 * *',
|
|
288
|
+
everyYear: '0 0 1 1 *',
|
|
289
|
+
weekdays: '0 0 * * 1-5',
|
|
290
|
+
weekends: '0 0 * * 0,6',
|
|
291
|
+
every5Minutes: '*/5 * * * *',
|
|
292
|
+
every15Minutes: '*/15 * * * *',
|
|
293
|
+
every30Minutes: '*/30 * * * *',
|
|
294
|
+
};
|