@stacksjs/scheduler 0.58.51 → 0.58.52
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/dist/index.js +682 -152
- package/package.json +7 -4
- package/src/constants.ts +81 -0
- package/src/errors.ts +7 -0
- package/src/index.ts +15 -317
- package/src/job.ts +315 -0
- package/src/schedule.ts +111 -0
- package/src/time.ts +823 -0
- package/src/types/cron.ts +98 -0
- package/src/types/utils.ts +14 -0
- package/src/utils.ts +19 -0
package/dist/index.js
CHANGED
|
@@ -1,209 +1,739 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
// src/
|
|
3
|
-
import
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
2
|
+
// src/schedule.ts
|
|
3
|
+
import {log} from "@stacksjs/cli";
|
|
4
|
+
|
|
5
|
+
// src/time.ts
|
|
6
|
+
import {DateTime} from "luxon";
|
|
7
|
+
|
|
8
|
+
// src/constants.ts
|
|
9
|
+
var CONSTRAINTS = Object.freeze({
|
|
10
|
+
second: [0, 59],
|
|
11
|
+
minute: [0, 59],
|
|
12
|
+
hour: [0, 23],
|
|
13
|
+
dayOfMonth: [1, 31],
|
|
14
|
+
month: [1, 12],
|
|
15
|
+
dayOfWeek: [0, 7]
|
|
16
|
+
});
|
|
17
|
+
var MONTH_CONSTRAINTS = Object.freeze({
|
|
18
|
+
1: 31,
|
|
19
|
+
2: 29,
|
|
20
|
+
3: 31,
|
|
21
|
+
4: 30,
|
|
22
|
+
5: 31,
|
|
23
|
+
6: 30,
|
|
24
|
+
7: 31,
|
|
25
|
+
8: 31,
|
|
26
|
+
9: 30,
|
|
27
|
+
10: 31,
|
|
28
|
+
11: 30,
|
|
29
|
+
12: 31
|
|
30
|
+
});
|
|
31
|
+
var PARSE_DEFAULTS = Object.freeze({
|
|
32
|
+
second: "0",
|
|
33
|
+
minute: "*",
|
|
34
|
+
hour: "*",
|
|
35
|
+
dayOfMonth: "*",
|
|
36
|
+
month: "*",
|
|
37
|
+
dayOfWeek: "*"
|
|
38
|
+
});
|
|
39
|
+
var ALIASES = Object.freeze({
|
|
40
|
+
jan: 1,
|
|
41
|
+
feb: 2,
|
|
42
|
+
mar: 3,
|
|
43
|
+
apr: 4,
|
|
44
|
+
may: 5,
|
|
45
|
+
jun: 6,
|
|
46
|
+
jul: 7,
|
|
47
|
+
aug: 8,
|
|
48
|
+
sep: 9,
|
|
49
|
+
oct: 10,
|
|
50
|
+
nov: 11,
|
|
51
|
+
dec: 12,
|
|
52
|
+
sun: 0,
|
|
53
|
+
mon: 1,
|
|
54
|
+
tue: 2,
|
|
55
|
+
wed: 3,
|
|
56
|
+
thu: 4,
|
|
57
|
+
fri: 5,
|
|
58
|
+
sat: 6
|
|
59
|
+
});
|
|
60
|
+
var TIME_UNITS_MAP = Object.freeze({
|
|
61
|
+
SECOND: "second",
|
|
62
|
+
MINUTE: "minute",
|
|
63
|
+
HOUR: "hour",
|
|
64
|
+
DAY_OF_MONTH: "dayOfMonth",
|
|
65
|
+
MONTH: "month",
|
|
66
|
+
DAY_OF_WEEK: "dayOfWeek"
|
|
67
|
+
});
|
|
68
|
+
var TIME_UNITS = Object.freeze(Object.values(TIME_UNITS_MAP));
|
|
69
|
+
var TIME_UNITS_LEN = TIME_UNITS.length;
|
|
70
|
+
var PRESETS = Object.freeze({
|
|
71
|
+
"@yearly": "0 0 0 1 1 *",
|
|
72
|
+
"@monthly": "0 0 0 1 * *",
|
|
73
|
+
"@weekly": "0 0 0 * * 0",
|
|
74
|
+
"@daily": "0 0 0 * * *",
|
|
75
|
+
"@hourly": "0 0 * * * *",
|
|
76
|
+
"@minutely": "0 * * * * *",
|
|
77
|
+
"@secondly": "* * * * * *",
|
|
78
|
+
"@weekdays": "0 0 0 * * 1-5",
|
|
79
|
+
"@weekends": "0 0 0 * * 0,6"
|
|
80
|
+
});
|
|
81
|
+
var RE_WILDCARDS = /\*/g;
|
|
82
|
+
var RE_RANGE = /^(\d+)(?:-(\d+))?(?:\/(\d+))?$/g;
|
|
83
|
+
|
|
84
|
+
// src/errors.ts
|
|
85
|
+
class CronError extends Error {
|
|
86
|
+
constructor() {
|
|
87
|
+
super(...arguments);
|
|
88
|
+
}
|
|
85
89
|
}
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
+
|
|
91
|
+
class ExclusiveParametersError extends CronError {
|
|
92
|
+
constructor(param1, param2) {
|
|
93
|
+
super(`You can't specify both ${param1} and ${param2}`);
|
|
94
|
+
}
|
|
90
95
|
}
|
|
91
96
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
97
|
+
// src/utils.ts
|
|
98
|
+
function getRecordKeys(record) {
|
|
99
|
+
return Object.keys(record);
|
|
100
|
+
}
|
|
101
|
+
function getTimeZoneAndOffset(timeZone, utcOffset) {
|
|
102
|
+
if (timeZone != null && utcOffset != null)
|
|
103
|
+
throw new ExclusiveParametersError("timeZone", "utcOffset");
|
|
104
|
+
if (timeZone != null)
|
|
105
|
+
return { timeZone, utcOffset: null };
|
|
106
|
+
if (utcOffset != null)
|
|
107
|
+
return { timeZone: null, utcOffset };
|
|
108
|
+
return { timeZone: null, utcOffset: null };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// src/time.ts
|
|
112
|
+
class CronTime {
|
|
113
|
+
source;
|
|
114
|
+
timeZone;
|
|
115
|
+
utcOffset;
|
|
116
|
+
realDate = false;
|
|
117
|
+
second = {};
|
|
118
|
+
minute = {};
|
|
119
|
+
hour = {};
|
|
120
|
+
dayOfMonth = {};
|
|
121
|
+
month = {};
|
|
122
|
+
dayOfWeek = {};
|
|
123
|
+
constructor(source, timeZone, utcOffset) {
|
|
124
|
+
if (timeZone != null && utcOffset != null)
|
|
125
|
+
throw new ExclusiveParametersError("timeZone", "utcOffset");
|
|
126
|
+
if (timeZone) {
|
|
127
|
+
const dt = DateTime.fromObject({}, { zone: timeZone });
|
|
128
|
+
if (!dt.isValid)
|
|
129
|
+
throw new CronError("Invalid timezone.");
|
|
130
|
+
this.timeZone = timeZone;
|
|
131
|
+
}
|
|
132
|
+
if (utcOffset != null)
|
|
133
|
+
this.utcOffset = utcOffset;
|
|
134
|
+
if (source instanceof Date || source instanceof DateTime) {
|
|
135
|
+
this.source = source instanceof Date ? DateTime.fromJSDate(source) : source;
|
|
136
|
+
this.realDate = true;
|
|
137
|
+
} else {
|
|
138
|
+
this.source = source;
|
|
139
|
+
this._parse(this.source);
|
|
140
|
+
this._verifyParse();
|
|
141
|
+
}
|
|
107
142
|
}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
return this;
|
|
143
|
+
_getWeekDay(date) {
|
|
144
|
+
return date.weekday === 7 ? 0 : date.weekday;
|
|
111
145
|
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
146
|
+
_verifyParse() {
|
|
147
|
+
const months = getRecordKeys(this.month);
|
|
148
|
+
const daysOfMonth = getRecordKeys(this.dayOfMonth);
|
|
149
|
+
let isOk = false;
|
|
150
|
+
let lastWrongMonth = null;
|
|
151
|
+
for (const m of months) {
|
|
152
|
+
const con = MONTH_CONSTRAINTS[m];
|
|
153
|
+
for (const day of daysOfMonth) {
|
|
154
|
+
if (day <= con)
|
|
155
|
+
isOk = true;
|
|
156
|
+
}
|
|
157
|
+
if (!isOk) {
|
|
158
|
+
lastWrongMonth = m;
|
|
159
|
+
console.warn(`Month '${m}' is limited to '${con}' days.`);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
if (!isOk && lastWrongMonth !== null) {
|
|
163
|
+
const notOkCon = MONTH_CONSTRAINTS[lastWrongMonth];
|
|
164
|
+
for (const notOkDay of daysOfMonth) {
|
|
165
|
+
if (notOkDay > notOkCon) {
|
|
166
|
+
delete this.dayOfMonth[notOkDay];
|
|
167
|
+
const fixedDay = notOkDay % notOkCon;
|
|
168
|
+
this.dayOfMonth[fixedDay] = true;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
115
172
|
}
|
|
116
|
-
|
|
117
|
-
this.
|
|
118
|
-
|
|
173
|
+
sendAt(i) {
|
|
174
|
+
let date = this.realDate && this.source instanceof DateTime ? this.source : DateTime.local();
|
|
175
|
+
if (this.timeZone)
|
|
176
|
+
date = date.setZone(this.timeZone);
|
|
177
|
+
if (this.utcOffset !== undefined) {
|
|
178
|
+
const sign = this.utcOffset < 0 ? "-" : "+";
|
|
179
|
+
const offsetHours = Math.trunc(this.utcOffset / 60);
|
|
180
|
+
const offsetHoursStr = String(Math.abs(offsetHours)).padStart(2, "0");
|
|
181
|
+
const offsetMins = Math.abs(this.utcOffset - offsetHours * 60);
|
|
182
|
+
const offsetMinsStr = String(offsetMins).padStart(2, "0");
|
|
183
|
+
const utcZone = `UTC${sign}${offsetHoursStr}:${offsetMinsStr}`;
|
|
184
|
+
date = date.setZone(utcZone);
|
|
185
|
+
if (!date.isValid)
|
|
186
|
+
throw new CronError("ERROR: You specified an invalid UTC offset.");
|
|
187
|
+
}
|
|
188
|
+
if (this.realDate) {
|
|
189
|
+
if (DateTime.local() > date)
|
|
190
|
+
throw new CronError("WARNING: Date in past. Will never be fired.");
|
|
191
|
+
return date;
|
|
192
|
+
}
|
|
193
|
+
if (i === undefined || Number.isNaN(i) || i < 0) {
|
|
194
|
+
return this.getNextDateFrom(date);
|
|
195
|
+
} else {
|
|
196
|
+
const dates = [];
|
|
197
|
+
for (;i > 0; i--) {
|
|
198
|
+
date = this.getNextDateFrom(date);
|
|
199
|
+
dates.push(date);
|
|
200
|
+
}
|
|
201
|
+
return dates;
|
|
202
|
+
}
|
|
119
203
|
}
|
|
120
|
-
|
|
121
|
-
this.
|
|
122
|
-
return this;
|
|
204
|
+
getTimeout() {
|
|
205
|
+
return Math.max(-1, this.sendAt().toMillis() - DateTime.local().toMillis());
|
|
123
206
|
}
|
|
124
|
-
|
|
125
|
-
return this;
|
|
207
|
+
toString() {
|
|
208
|
+
return this.toJSON().join(" ");
|
|
209
|
+
}
|
|
210
|
+
toJSON() {
|
|
211
|
+
return TIME_UNITS.map((unit) => {
|
|
212
|
+
return this._wcOrAll(unit);
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
getNextDateFrom(start, timeZone) {
|
|
216
|
+
if (start instanceof Date)
|
|
217
|
+
start = DateTime.fromJSDate(start);
|
|
218
|
+
let date = start;
|
|
219
|
+
const firstDate = start.toMillis();
|
|
220
|
+
if (timeZone)
|
|
221
|
+
date = date.setZone(timeZone);
|
|
222
|
+
if (!this.realDate) {
|
|
223
|
+
if (date.millisecond > 0)
|
|
224
|
+
date = date.set({ millisecond: 0, second: date.second + 1 });
|
|
225
|
+
}
|
|
226
|
+
if (!date.isValid)
|
|
227
|
+
throw new CronError("ERROR: You specified an invalid date.");
|
|
228
|
+
const maxMatch = DateTime.now().plus({ years: 8 });
|
|
229
|
+
while (true) {
|
|
230
|
+
const diff = date.toMillis() - start.toMillis();
|
|
231
|
+
if (date > maxMatch) {
|
|
232
|
+
throw new CronError(`Something went wrong. No execution date was found in the next 8 years.
|
|
233
|
+
Please provide the following string if you would like to help debug:
|
|
234
|
+
Time Zone: ${timeZone?.toString() ?? '""'} - Cron String: ${this.source.toString()} - UTC offset: ${date.offset} - current Date: ${DateTime.local().toString()}`);
|
|
235
|
+
}
|
|
236
|
+
if (!(date.month in this.month) && Object.keys(this.month).length !== 12) {
|
|
237
|
+
date = date.plus({ months: 1 });
|
|
238
|
+
date = date.set({ day: 1, hour: 0, minute: 0, second: 0 });
|
|
239
|
+
if (this._forwardDSTJump(0, 0, date)) {
|
|
240
|
+
const [isDone, newDate] = this._findPreviousDSTJump(date);
|
|
241
|
+
date = newDate;
|
|
242
|
+
if (isDone)
|
|
243
|
+
break;
|
|
244
|
+
}
|
|
245
|
+
continue;
|
|
246
|
+
}
|
|
247
|
+
if (!(date.day in this.dayOfMonth) && Object.keys(this.dayOfMonth).length !== 31 && !((this._getWeekDay(date) in this.dayOfWeek) && Object.keys(this.dayOfWeek).length !== 7)) {
|
|
248
|
+
date = date.plus({ days: 1 });
|
|
249
|
+
date = date.set({ hour: 0, minute: 0, second: 0 });
|
|
250
|
+
if (this._forwardDSTJump(0, 0, date)) {
|
|
251
|
+
const [isDone, newDate] = this._findPreviousDSTJump(date);
|
|
252
|
+
date = newDate;
|
|
253
|
+
if (isDone)
|
|
254
|
+
break;
|
|
255
|
+
}
|
|
256
|
+
continue;
|
|
257
|
+
}
|
|
258
|
+
if (!(this._getWeekDay(date) in this.dayOfWeek) && Object.keys(this.dayOfWeek).length !== 7 && !((date.day in this.dayOfMonth) && Object.keys(this.dayOfMonth).length !== 31)) {
|
|
259
|
+
date = date.plus({ days: 1 });
|
|
260
|
+
date = date.set({ hour: 0, minute: 0, second: 0 });
|
|
261
|
+
if (this._forwardDSTJump(0, 0, date)) {
|
|
262
|
+
const [isDone, newDate] = this._findPreviousDSTJump(date);
|
|
263
|
+
date = newDate;
|
|
264
|
+
if (isDone)
|
|
265
|
+
break;
|
|
266
|
+
}
|
|
267
|
+
continue;
|
|
268
|
+
}
|
|
269
|
+
if (!(date.hour in this.hour) && Object.keys(this.hour).length !== 24) {
|
|
270
|
+
const expectedHour = date.hour === 23 && diff > 86400000 ? 0 : date.hour + 1;
|
|
271
|
+
const expectedMinute = date.minute;
|
|
272
|
+
date = date.set({ hour: expectedHour });
|
|
273
|
+
date = date.set({ minute: 0, second: 0 });
|
|
274
|
+
if (this._forwardDSTJump(expectedHour, expectedMinute, date)) {
|
|
275
|
+
const [isDone, newDate] = this._findPreviousDSTJump(date);
|
|
276
|
+
date = newDate;
|
|
277
|
+
if (isDone)
|
|
278
|
+
break;
|
|
279
|
+
}
|
|
280
|
+
continue;
|
|
281
|
+
}
|
|
282
|
+
if (!(date.minute in this.minute) && Object.keys(this.minute).length !== 60) {
|
|
283
|
+
const expectedMinute = date.minute === 59 && diff > 3600000 ? 0 : date.minute + 1;
|
|
284
|
+
const expectedHour = date.hour + (expectedMinute === 60 ? 1 : 0);
|
|
285
|
+
date = date.set({ minute: expectedMinute });
|
|
286
|
+
date = date.set({ second: 0 });
|
|
287
|
+
if (this._forwardDSTJump(expectedHour, expectedMinute, date)) {
|
|
288
|
+
const [isDone, newDate] = this._findPreviousDSTJump(date);
|
|
289
|
+
date = newDate;
|
|
290
|
+
if (isDone)
|
|
291
|
+
break;
|
|
292
|
+
}
|
|
293
|
+
continue;
|
|
294
|
+
}
|
|
295
|
+
if (!(date.second in this.second) && Object.keys(this.second).length !== 60) {
|
|
296
|
+
const expectedSecond = date.second === 59 && diff > 60000 ? 0 : date.second + 1;
|
|
297
|
+
const expectedMinute = date.minute + (expectedSecond === 60 ? 1 : 0);
|
|
298
|
+
const expectedHour = date.hour + (expectedMinute === 60 ? 1 : 0);
|
|
299
|
+
date = date.set({ second: expectedSecond });
|
|
300
|
+
if (this._forwardDSTJump(expectedHour, expectedMinute, date)) {
|
|
301
|
+
const [isDone, newDate] = this._findPreviousDSTJump(date);
|
|
302
|
+
date = newDate;
|
|
303
|
+
if (isDone)
|
|
304
|
+
break;
|
|
305
|
+
}
|
|
306
|
+
continue;
|
|
307
|
+
}
|
|
308
|
+
if (date.toMillis() === firstDate) {
|
|
309
|
+
const expectedSecond = date.second + 1;
|
|
310
|
+
const expectedMinute = date.minute + (expectedSecond === 60 ? 1 : 0);
|
|
311
|
+
const expectedHour = date.hour + (expectedMinute === 60 ? 1 : 0);
|
|
312
|
+
date = date.set({ second: expectedSecond });
|
|
313
|
+
if (this._forwardDSTJump(expectedHour, expectedMinute, date)) {
|
|
314
|
+
const [isDone, newDate] = this._findPreviousDSTJump(date);
|
|
315
|
+
date = newDate;
|
|
316
|
+
if (isDone)
|
|
317
|
+
break;
|
|
318
|
+
}
|
|
319
|
+
continue;
|
|
320
|
+
}
|
|
321
|
+
break;
|
|
322
|
+
}
|
|
323
|
+
return date;
|
|
324
|
+
}
|
|
325
|
+
_findPreviousDSTJump(date) {
|
|
326
|
+
let expectedMinute, expectedHour, actualMinute, actualHour;
|
|
327
|
+
let maybeJumpingPoint = date;
|
|
328
|
+
const iterationLimit = 1440;
|
|
329
|
+
let iteration = 0;
|
|
330
|
+
do {
|
|
331
|
+
if (++iteration > iterationLimit) {
|
|
332
|
+
throw new CronError(`ERROR: This DST checking related function assumes the input DateTime (${date.toISO() ?? date.toMillis()}) is within 24 hours of a DST jump.`);
|
|
333
|
+
}
|
|
334
|
+
expectedMinute = maybeJumpingPoint.minute - 1;
|
|
335
|
+
expectedHour = maybeJumpingPoint.hour;
|
|
336
|
+
if (expectedMinute < 0) {
|
|
337
|
+
expectedMinute += 60;
|
|
338
|
+
expectedHour = (expectedHour + 24 - 1) % 24;
|
|
339
|
+
}
|
|
340
|
+
maybeJumpingPoint = maybeJumpingPoint.minus({ minute: 1 });
|
|
341
|
+
actualMinute = maybeJumpingPoint.minute;
|
|
342
|
+
actualHour = maybeJumpingPoint.hour;
|
|
343
|
+
} while (expectedMinute === actualMinute && expectedHour === actualHour);
|
|
344
|
+
const afterJumpingPoint = maybeJumpingPoint.plus({ minute: 1 }).set({ second: 0, millisecond: 0 });
|
|
345
|
+
const beforeJumpingPoint = afterJumpingPoint.minus({ second: 1 });
|
|
346
|
+
if (date.month + 1 in this.month && date.day in this.dayOfMonth && this._getWeekDay(date) in this.dayOfWeek) {
|
|
347
|
+
return [
|
|
348
|
+
this._checkTimeInSkippedRange(beforeJumpingPoint, afterJumpingPoint),
|
|
349
|
+
afterJumpingPoint
|
|
350
|
+
];
|
|
351
|
+
}
|
|
352
|
+
return [false, afterJumpingPoint];
|
|
353
|
+
}
|
|
354
|
+
_checkTimeInSkippedRange(beforeJumpingPoint, afterJumpingPoint) {
|
|
355
|
+
const startingMinute = (beforeJumpingPoint.minute + 1) % 60;
|
|
356
|
+
const startingHour = (beforeJumpingPoint.hour + (startingMinute === 0 ? 1 : 0)) % 24;
|
|
357
|
+
const hourRangeSize = afterJumpingPoint.hour - startingHour + 1;
|
|
358
|
+
const isHourJump = startingMinute === 0 && afterJumpingPoint.minute === 0;
|
|
359
|
+
if (hourRangeSize === 2 && isHourJump) {
|
|
360
|
+
return startingHour in this.hour;
|
|
361
|
+
} else if (hourRangeSize === 1) {
|
|
362
|
+
return startingHour in this.hour && this._checkTimeInSkippedRangeSingleHour(startingMinute, afterJumpingPoint.minute);
|
|
363
|
+
} else {
|
|
364
|
+
return this._checkTimeInSkippedRangeMultiHour(startingHour, startingMinute, afterJumpingPoint.hour, afterJumpingPoint.minute);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
_checkTimeInSkippedRangeSingleHour(startMinute, endMinute) {
|
|
368
|
+
for (let minute = startMinute;minute < endMinute; ++minute) {
|
|
369
|
+
if (minute in this.minute)
|
|
370
|
+
return true;
|
|
371
|
+
}
|
|
372
|
+
return endMinute in this.minute && 0 in this.second;
|
|
373
|
+
}
|
|
374
|
+
_checkTimeInSkippedRangeMultiHour(startHour, startMinute, endHour, endMinute) {
|
|
375
|
+
if (startHour >= endHour) {
|
|
376
|
+
throw new CronError(`ERROR: This DST checking related function assumes the forward jump starting hour (${startHour}) is less than the end hour (${endHour})`);
|
|
377
|
+
}
|
|
378
|
+
const firstHourMinuteRange = Array.from({ length: 60 - startMinute }, (_, k) => startMinute + k);
|
|
379
|
+
const lastHourMinuteRange = Array.from({ length: endMinute }, (_, k) => k);
|
|
380
|
+
const middleHourMinuteRange = Array.from({ length: 60 }, (_, k) => k);
|
|
381
|
+
const selectRange = (forHour) => {
|
|
382
|
+
if (forHour === startHour)
|
|
383
|
+
return firstHourMinuteRange;
|
|
384
|
+
else if (forHour === endHour)
|
|
385
|
+
return lastHourMinuteRange;
|
|
386
|
+
else
|
|
387
|
+
return middleHourMinuteRange;
|
|
388
|
+
};
|
|
389
|
+
for (let hour = startHour;hour <= endHour; ++hour) {
|
|
390
|
+
if (!(hour in this.hour))
|
|
391
|
+
continue;
|
|
392
|
+
const usingRange = selectRange(hour);
|
|
393
|
+
for (const minute of usingRange) {
|
|
394
|
+
if (minute in this.minute)
|
|
395
|
+
return true;
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
return endHour in this.hour && endMinute in this.minute && 0 in this.second;
|
|
399
|
+
}
|
|
400
|
+
_forwardDSTJump(expectedHour, expectedMinute, actualDate) {
|
|
401
|
+
const actualHour = actualDate.hour;
|
|
402
|
+
const actualMinute = actualDate.minute;
|
|
403
|
+
const didHoursJumped = expectedHour % 24 < actualHour;
|
|
404
|
+
const didMinutesJumped = expectedMinute % 60 < actualMinute;
|
|
405
|
+
return didHoursJumped || didMinutesJumped;
|
|
406
|
+
}
|
|
407
|
+
_wcOrAll(unit) {
|
|
408
|
+
if (this._hasAll(unit))
|
|
409
|
+
return "*";
|
|
410
|
+
const all = [];
|
|
411
|
+
for (const time in this[unit])
|
|
412
|
+
all.push(time);
|
|
413
|
+
return all.join(",");
|
|
414
|
+
}
|
|
415
|
+
_hasAll(unit) {
|
|
416
|
+
const constraints = CONSTRAINTS[unit];
|
|
417
|
+
const low = constraints[0];
|
|
418
|
+
const high = unit === TIME_UNITS_MAP.DAY_OF_WEEK ? constraints[1] - 1 : constraints[1];
|
|
419
|
+
for (let i = low, n = high;i < n; i++) {
|
|
420
|
+
if (!(i in this[unit]))
|
|
421
|
+
return false;
|
|
422
|
+
}
|
|
423
|
+
return true;
|
|
424
|
+
}
|
|
425
|
+
_parse(source) {
|
|
426
|
+
source = source.toLowerCase();
|
|
427
|
+
if (Object.keys(PRESETS).includes(source))
|
|
428
|
+
source = PRESETS[source];
|
|
429
|
+
source = source.replace(/[a-z]{1,3}/gi, (alias) => {
|
|
430
|
+
if (Object.keys(ALIASES).includes(alias))
|
|
431
|
+
return ALIASES[alias].toString();
|
|
432
|
+
throw new CronError(`Unknown alias: ${alias}`);
|
|
433
|
+
});
|
|
434
|
+
const units = source.trim().split(/\s+/);
|
|
435
|
+
if (units.length < TIME_UNITS_LEN - 1)
|
|
436
|
+
throw new CronError("Too few fields");
|
|
437
|
+
if (units.length > TIME_UNITS_LEN)
|
|
438
|
+
throw new CronError("Too many fields");
|
|
439
|
+
const unitsLen = units.length;
|
|
440
|
+
for (const unit of TIME_UNITS) {
|
|
441
|
+
const i = TIME_UNITS.indexOf(unit);
|
|
442
|
+
const cur = units[i - (TIME_UNITS_LEN - unitsLen)] ?? PARSE_DEFAULTS[unit];
|
|
443
|
+
this._parseField(cur, unit);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
_parseField(value, unit) {
|
|
447
|
+
const typeObj = this[unit];
|
|
448
|
+
let pointer;
|
|
449
|
+
const constraints = CONSTRAINTS[unit];
|
|
450
|
+
const low = constraints[0];
|
|
451
|
+
const high = constraints[1];
|
|
452
|
+
const fields = value.split(",");
|
|
453
|
+
fields.forEach((field) => {
|
|
454
|
+
const wildcardIndex = field.indexOf("*");
|
|
455
|
+
if (wildcardIndex !== -1 && wildcardIndex !== 0) {
|
|
456
|
+
throw new CronError(`Field (${field}) has an invalid wildcard expression`);
|
|
457
|
+
}
|
|
458
|
+
});
|
|
459
|
+
value = value.replace(RE_WILDCARDS, `${low}-${high}`);
|
|
460
|
+
const allRanges = value.split(",");
|
|
461
|
+
for (const range of allRanges) {
|
|
462
|
+
const match = [...range.matchAll(RE_RANGE)][0];
|
|
463
|
+
if (match?.[1] !== undefined) {
|
|
464
|
+
const [, mLower, mUpper, mStep] = match;
|
|
465
|
+
let lower = Number.parseInt(mLower, 10);
|
|
466
|
+
let upper = mUpper !== undefined ? Number.parseInt(mUpper, 10) : undefined;
|
|
467
|
+
const wasStepDefined = mStep !== undefined;
|
|
468
|
+
const step = Number.parseInt(mStep ?? "1", 10);
|
|
469
|
+
if (step === 0)
|
|
470
|
+
throw new CronError(`Field (${unit}) has a step of zero`);
|
|
471
|
+
if (upper !== undefined && lower > upper)
|
|
472
|
+
throw new CronError(`Field (${unit}) has an invalid range`);
|
|
473
|
+
const isOutOfRange = lower < low || upper !== undefined && upper > high || upper === undefined && lower > high;
|
|
474
|
+
if (isOutOfRange)
|
|
475
|
+
throw new CronError(`Field value (${value}) is out of range`);
|
|
476
|
+
lower = Math.min(Math.max(low, ~~Math.abs(lower)), high);
|
|
477
|
+
if (upper !== undefined) {
|
|
478
|
+
upper = Math.min(high, ~~Math.abs(upper));
|
|
479
|
+
} else {
|
|
480
|
+
upper = wasStepDefined ? high : lower;
|
|
481
|
+
}
|
|
482
|
+
pointer = lower;
|
|
483
|
+
do {
|
|
484
|
+
typeObj[pointer] = true;
|
|
485
|
+
pointer += step;
|
|
486
|
+
} while (pointer <= upper);
|
|
487
|
+
if (unit === "dayOfWeek") {
|
|
488
|
+
if (!typeObj[0] && !!typeObj[7])
|
|
489
|
+
typeObj[0] = typeObj[7];
|
|
490
|
+
delete typeObj[7];
|
|
491
|
+
}
|
|
492
|
+
} else {
|
|
493
|
+
throw new CronError(`Field (${unit}) cannot be parsed`);
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
// src/schedule.ts
|
|
500
|
+
function sendAt(cronTime) {
|
|
501
|
+
return new CronTime(cronTime).sendAt();
|
|
502
|
+
}
|
|
503
|
+
function timeout(cronTime) {
|
|
504
|
+
return new CronTime(cronTime).getTimeout();
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
class Schedule {
|
|
508
|
+
cronPattern = "";
|
|
509
|
+
timezone = "America/Los_Angeles";
|
|
510
|
+
task;
|
|
511
|
+
cmd;
|
|
512
|
+
constructor(task) {
|
|
513
|
+
this.task = task;
|
|
126
514
|
}
|
|
127
515
|
everySecond() {
|
|
516
|
+
this.cronPattern = "* * * * * *";
|
|
517
|
+
return this;
|
|
518
|
+
}
|
|
519
|
+
everyMinute() {
|
|
520
|
+
this.cronPattern = "0 * * * * *";
|
|
128
521
|
return this;
|
|
129
522
|
}
|
|
130
523
|
everyFiveMinutes() {
|
|
524
|
+
this.cronPattern = "*/5 * * * *";
|
|
131
525
|
return this;
|
|
132
526
|
}
|
|
133
527
|
everyTenMinutes() {
|
|
528
|
+
this.cronPattern = "*/10 * * * *";
|
|
134
529
|
return this;
|
|
135
530
|
}
|
|
136
531
|
everyThirtyMinutes() {
|
|
532
|
+
this.cronPattern = "*/30 * * * *";
|
|
137
533
|
return this;
|
|
138
534
|
}
|
|
139
535
|
hourly() {
|
|
536
|
+
this.cronPattern = "0 0 * * * *";
|
|
140
537
|
return this;
|
|
141
538
|
}
|
|
142
539
|
daily() {
|
|
143
|
-
|
|
144
|
-
}
|
|
145
|
-
twiceDaily(_hour1, _hour2) {
|
|
540
|
+
this.cronPattern = "0 0 0 * * *";
|
|
146
541
|
return this;
|
|
147
542
|
}
|
|
148
543
|
weekly() {
|
|
544
|
+
this.cronPattern = "0 0 0 * * 0";
|
|
149
545
|
return this;
|
|
150
546
|
}
|
|
151
547
|
monthly() {
|
|
152
|
-
|
|
153
|
-
}
|
|
154
|
-
quarterly() {
|
|
548
|
+
this.cronPattern = "0 0 0 1 * *";
|
|
155
549
|
return this;
|
|
156
550
|
}
|
|
157
551
|
yearly() {
|
|
552
|
+
this.cronPattern = "0 0 0 1 1 *";
|
|
158
553
|
return this;
|
|
159
554
|
}
|
|
160
|
-
|
|
555
|
+
onDays(days) {
|
|
556
|
+
const dayPattern = days.join(",");
|
|
557
|
+
this.cronPattern = `0 0 0 * * ${dayPattern}`;
|
|
161
558
|
return this;
|
|
162
559
|
}
|
|
163
|
-
|
|
560
|
+
at(time2) {
|
|
561
|
+
const [hour, minute] = time2.split(":").map(Number);
|
|
562
|
+
this.cronPattern = `${minute} ${hour} * * *`;
|
|
164
563
|
return this;
|
|
165
564
|
}
|
|
166
|
-
|
|
565
|
+
setTimeZone(timezone) {
|
|
566
|
+
this.timezone = timezone;
|
|
167
567
|
return this;
|
|
168
568
|
}
|
|
169
|
-
|
|
170
|
-
|
|
569
|
+
start() {
|
|
570
|
+
new CronJob(this.cronPattern, this.task, null, true, this.timezone);
|
|
571
|
+
log.info(`Scheduled task with pattern: ${this.cronPattern} in timezone: ${this.timezone}`);
|
|
171
572
|
}
|
|
172
|
-
|
|
573
|
+
static command(cmd) {
|
|
574
|
+
this.cmd = cmd;
|
|
173
575
|
return this;
|
|
174
576
|
}
|
|
175
|
-
|
|
176
|
-
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
// src/job.ts
|
|
580
|
+
import {spawn} from "child_process";
|
|
581
|
+
class CronJob2 {
|
|
582
|
+
cronTime;
|
|
583
|
+
running = false;
|
|
584
|
+
unrefTimeout = false;
|
|
585
|
+
lastExecution = null;
|
|
586
|
+
runOnce = false;
|
|
587
|
+
context;
|
|
588
|
+
onComplete;
|
|
589
|
+
_timeout;
|
|
590
|
+
_callbacks = [];
|
|
591
|
+
_errorHandler;
|
|
592
|
+
constructor(cronTime, onTick, onComplete, start, timeZone, context, runOnInit, utcOffset, unrefTimeout, errorHandler) {
|
|
593
|
+
this._errorHandler = errorHandler;
|
|
594
|
+
this.context = context ?? this;
|
|
595
|
+
const { timeZone: tz, utcOffset: uo } = getTimeZoneAndOffset(timeZone, utcOffset);
|
|
596
|
+
this.cronTime = new CronTime(cronTime, tz, uo);
|
|
597
|
+
if (unrefTimeout != null)
|
|
598
|
+
this.unrefTimeout = unrefTimeout;
|
|
599
|
+
if (onComplete != null) {
|
|
600
|
+
this.onComplete = this._fnWrap(onComplete);
|
|
601
|
+
}
|
|
602
|
+
if (this.cronTime.realDate)
|
|
603
|
+
this.runOnce = true;
|
|
604
|
+
this.addCallback(this._fnWrap(onTick));
|
|
605
|
+
if (runOnInit) {
|
|
606
|
+
this.lastExecution = new Date;
|
|
607
|
+
this.fireOnTick();
|
|
608
|
+
}
|
|
609
|
+
if (start)
|
|
610
|
+
this.start();
|
|
177
611
|
}
|
|
178
|
-
|
|
179
|
-
|
|
612
|
+
static from(params) {
|
|
613
|
+
if (params.timeZone != null && params.utcOffset != null)
|
|
614
|
+
throw new ExclusiveParametersError("timeZone", "utcOffset");
|
|
615
|
+
if (params.timeZone != null) {
|
|
616
|
+
return new CronJob2(params.cronTime, params.onTick, params.onComplete, params.start, params.timeZone, params.context, params.runOnInit, params.utcOffset, params.unrefTimeout);
|
|
617
|
+
} else if (params.utcOffset != null) {
|
|
618
|
+
return new CronJob2(params.cronTime, params.onTick, params.onComplete, params.start, null, params.context, params.runOnInit, params.utcOffset, params.unrefTimeout);
|
|
619
|
+
} else {
|
|
620
|
+
return new CronJob2(params.cronTime, params.onTick, params.onComplete, params.start, params.timeZone, params.context, params.runOnInit, params.utcOffset, params.unrefTimeout);
|
|
621
|
+
}
|
|
180
622
|
}
|
|
181
|
-
|
|
182
|
-
|
|
623
|
+
_fnWrap(cmd) {
|
|
624
|
+
switch (typeof cmd) {
|
|
625
|
+
case "function": {
|
|
626
|
+
return cmd;
|
|
627
|
+
}
|
|
628
|
+
case "string": {
|
|
629
|
+
const [command, ...args] = cmd.split(" ");
|
|
630
|
+
return spawn.bind(undefined, command ?? cmd, args, {});
|
|
631
|
+
}
|
|
632
|
+
case "object": {
|
|
633
|
+
return spawn.bind(undefined, cmd.command, cmd.args ?? [], cmd.options ?? {});
|
|
634
|
+
}
|
|
635
|
+
}
|
|
183
636
|
}
|
|
184
|
-
|
|
185
|
-
|
|
637
|
+
addCallback(callback2) {
|
|
638
|
+
if (typeof callback2 === "function")
|
|
639
|
+
this._callbacks.push(callback2);
|
|
186
640
|
}
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
641
|
+
setTime(time3) {
|
|
642
|
+
if (!(time3 instanceof CronTime))
|
|
643
|
+
throw new CronError("time must be an instance of CronTime.");
|
|
644
|
+
const wasRunning = this.running;
|
|
645
|
+
this.stop();
|
|
646
|
+
this.cronTime = time3;
|
|
647
|
+
if (time3.realDate)
|
|
648
|
+
this.runOnce = true;
|
|
649
|
+
if (wasRunning)
|
|
650
|
+
this.start();
|
|
191
651
|
}
|
|
192
|
-
|
|
193
|
-
this.
|
|
194
|
-
return this;
|
|
652
|
+
nextDate() {
|
|
653
|
+
return this.cronTime.sendAt();
|
|
195
654
|
}
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
655
|
+
fireOnTick() {
|
|
656
|
+
try {
|
|
657
|
+
callback.call(this.context, this.onComplete);
|
|
658
|
+
} catch (error) {
|
|
659
|
+
if (this._errorHandler && error instanceof Error) {
|
|
660
|
+
this._errorHandler(error);
|
|
661
|
+
} else {
|
|
662
|
+
console.error("An error occurred in the cron job callback:", error);
|
|
663
|
+
}
|
|
664
|
+
}
|
|
199
665
|
}
|
|
200
|
-
|
|
201
|
-
this.
|
|
202
|
-
|
|
666
|
+
nextDates(i) {
|
|
667
|
+
return this.cronTime.sendAt(i ?? 0);
|
|
668
|
+
}
|
|
669
|
+
start() {
|
|
670
|
+
if (this.running)
|
|
671
|
+
return;
|
|
672
|
+
const MAXDELAY = 2147483647;
|
|
673
|
+
let timeout2 = this.cronTime.getTimeout();
|
|
674
|
+
let remaining = 0;
|
|
675
|
+
let startTime;
|
|
676
|
+
const setCronTimeout = (t) => {
|
|
677
|
+
startTime = Date.now();
|
|
678
|
+
this._timeout = setTimeout(callbackWrapper, t);
|
|
679
|
+
if (this.unrefTimeout && typeof this._timeout.unref === "function")
|
|
680
|
+
this._timeout.unref();
|
|
681
|
+
};
|
|
682
|
+
const callbackWrapper = () => {
|
|
683
|
+
const diff = startTime + timeout2 - Date.now();
|
|
684
|
+
if (diff > 0) {
|
|
685
|
+
let newTimeout = this.cronTime.getTimeout();
|
|
686
|
+
if (newTimeout > diff)
|
|
687
|
+
newTimeout = diff;
|
|
688
|
+
remaining += newTimeout;
|
|
689
|
+
}
|
|
690
|
+
if (remaining) {
|
|
691
|
+
if (remaining > MAXDELAY) {
|
|
692
|
+
remaining -= MAXDELAY;
|
|
693
|
+
timeout2 = MAXDELAY;
|
|
694
|
+
} else {
|
|
695
|
+
timeout2 = remaining;
|
|
696
|
+
remaining = 0;
|
|
697
|
+
}
|
|
698
|
+
setCronTimeout(timeout2);
|
|
699
|
+
} else {
|
|
700
|
+
this.lastExecution = new Date;
|
|
701
|
+
this.running = false;
|
|
702
|
+
if (!this.runOnce)
|
|
703
|
+
this.start();
|
|
704
|
+
this.fireOnTick();
|
|
705
|
+
}
|
|
706
|
+
};
|
|
707
|
+
if (timeout2 >= 0) {
|
|
708
|
+
this.running = true;
|
|
709
|
+
if (timeout2 > MAXDELAY) {
|
|
710
|
+
remaining = timeout2 - MAXDELAY;
|
|
711
|
+
timeout2 = MAXDELAY;
|
|
712
|
+
}
|
|
713
|
+
setCronTimeout(timeout2);
|
|
714
|
+
} else {
|
|
715
|
+
this.stop();
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
lastDate() {
|
|
719
|
+
return this.lastExecution;
|
|
720
|
+
}
|
|
721
|
+
stop() {
|
|
722
|
+
if (this._timeout)
|
|
723
|
+
clearTimeout(this._timeout);
|
|
724
|
+
this.running = false;
|
|
725
|
+
if (typeof this.onComplete === "function")
|
|
726
|
+
this.onComplete.call(this.context);
|
|
203
727
|
}
|
|
204
728
|
}
|
|
729
|
+
|
|
730
|
+
// src/index.ts
|
|
731
|
+
var src_default = Schedule;
|
|
205
732
|
export {
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
733
|
+
timeout,
|
|
734
|
+
sendAt,
|
|
735
|
+
src_default as default,
|
|
736
|
+
Schedule,
|
|
737
|
+
CronTime,
|
|
738
|
+
CronJob2 as CronJob
|
|
209
739
|
};
|