node-schedule 1.3.1 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +34 -0
- package/README.md +98 -51
- package/UPGRADING.md +20 -0
- package/index.js +17 -0
- package/lib/Invocation.js +348 -0
- package/lib/Job.js +287 -0
- package/lib/schedule.js +55 -598
- package/lib/utils/dateUtils.js +11 -0
- package/package.json +35 -13
- package/.eslintrc +0 -17
- package/.travis.yml +0 -15
- package/CONTRIBUTING.md +0 -31
- package/test/.eslintrc +0 -5
- package/test/cancel-long-running-jobs.js +0 -24
- package/test/convenience-method-test.js +0 -713
- package/test/date-convenience-methods-test.js +0 -59
- package/test/es6/job-test.js +0 -33
- package/test/job-test.js +0 -517
- package/test/range-test.js +0 -77
- package/test/recurrence-rule-test.js +0 -375
- package/test/schedule-cron-jobs.js +0 -134
- package/test/start-end-test.js +0 -327
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const lt = require('long-timeout')
|
|
4
|
+
const CronDate = require('cron-parser/lib/date')
|
|
5
|
+
const sorted = require('sorted-array-functions')
|
|
6
|
+
|
|
7
|
+
const invocations = [];
|
|
8
|
+
let currentInvocation = null;
|
|
9
|
+
|
|
10
|
+
/* DoesntRecur rule */
|
|
11
|
+
const DoesntRecur = new RecurrenceRule();
|
|
12
|
+
DoesntRecur.recurs = false;
|
|
13
|
+
|
|
14
|
+
/* Invocation object */
|
|
15
|
+
function Invocation(job, fireDate, recurrenceRule, endDate) {
|
|
16
|
+
this.job = job;
|
|
17
|
+
this.fireDate = fireDate;
|
|
18
|
+
this.endDate = endDate;
|
|
19
|
+
this.recurrenceRule = recurrenceRule || DoesntRecur;
|
|
20
|
+
|
|
21
|
+
this.timerID = null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function sorter(a, b) {
|
|
25
|
+
return (a.fireDate.getTime() - b.fireDate.getTime());
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/* Range object */
|
|
29
|
+
function Range(start, end, step) {
|
|
30
|
+
this.start = start || 0;
|
|
31
|
+
this.end = end || 60;
|
|
32
|
+
this.step = step || 1;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
Range.prototype.contains = function(val) {
|
|
36
|
+
if (this.step === null || this.step === 1) {
|
|
37
|
+
return (val >= this.start && val <= this.end);
|
|
38
|
+
} else {
|
|
39
|
+
for (let i = this.start; i < this.end; i += this.step) {
|
|
40
|
+
if (i === val) {
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
/* RecurrenceRule object */
|
|
50
|
+
/*
|
|
51
|
+
Interpreting each property:
|
|
52
|
+
null - any value is valid
|
|
53
|
+
number - fixed value
|
|
54
|
+
Range - value must fall in range
|
|
55
|
+
array - value must validate against any item in list
|
|
56
|
+
|
|
57
|
+
NOTE: Cron months are 1-based, but RecurrenceRule months are 0-based.
|
|
58
|
+
*/
|
|
59
|
+
function RecurrenceRule(year, month, date, dayOfWeek, hour, minute, second) {
|
|
60
|
+
this.recurs = true;
|
|
61
|
+
|
|
62
|
+
this.year = (year == null) ? null : year;
|
|
63
|
+
this.month = (month == null) ? null : month;
|
|
64
|
+
this.date = (date == null) ? null : date;
|
|
65
|
+
this.dayOfWeek = (dayOfWeek == null) ? null : dayOfWeek;
|
|
66
|
+
this.hour = (hour == null) ? null : hour;
|
|
67
|
+
this.minute = (minute == null) ? null : minute;
|
|
68
|
+
this.second = (second == null) ? 0 : second;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
RecurrenceRule.prototype.isValid = function() {
|
|
72
|
+
function isValidType(num) {
|
|
73
|
+
if (Array.isArray(num) || (num instanceof Array)) {
|
|
74
|
+
return num.every(function(e) {
|
|
75
|
+
return isValidType(e);
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
return !(Number.isNaN(Number(num)) && !(num instanceof Range));
|
|
79
|
+
}
|
|
80
|
+
if (this.month !== null && (this.month < 0 || this.month > 11 || !isValidType(this.month))) {
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
if (this.dayOfWeek !== null && (this.dayOfWeek < 0 || this.dayOfWeek > 6 || !isValidType(this.dayOfWeek))) {
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
if (this.hour !== null && (this.hour < 0 || this.hour > 23 || !isValidType(this.hour))) {
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
if (this.minute !== null && (this.minute < 0 || this.minute > 59 || !isValidType(this.minute))) {
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
if (this.second !== null && (this.second < 0 || this.second > 59 || !isValidType(this.second))) {
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
if (this.date !== null) {
|
|
96
|
+
if(!isValidType(this.date)) {
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
switch (this.month) {
|
|
100
|
+
case 3:
|
|
101
|
+
case 5:
|
|
102
|
+
case 8:
|
|
103
|
+
case 10:
|
|
104
|
+
if (this.date < 1 || this. date > 30) {
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
break;
|
|
108
|
+
case 1:
|
|
109
|
+
if (this.date < 1 || this. date > 29) {
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
break;
|
|
113
|
+
default:
|
|
114
|
+
if (this.date < 1 || this. date > 31) {
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return true;
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
RecurrenceRule.prototype.nextInvocationDate = function(base) {
|
|
123
|
+
const next = this._nextInvocationDate(base);
|
|
124
|
+
return next ? next.toDate() : null;
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
RecurrenceRule.prototype._nextInvocationDate = function(base) {
|
|
128
|
+
base = ((base instanceof CronDate) || (base instanceof Date)) ? base : (new Date());
|
|
129
|
+
if (!this.recurs) {
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if(!this.isValid()) {
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const now = new CronDate(Date.now(), this.tz);
|
|
138
|
+
let fullYear = now.getFullYear();
|
|
139
|
+
if ((this.year !== null) &&
|
|
140
|
+
(typeof this.year == 'number') &&
|
|
141
|
+
(this.year < fullYear)) {
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
let next = new CronDate(base.getTime(), this.tz);
|
|
146
|
+
next.addSecond();
|
|
147
|
+
|
|
148
|
+
while (true) {
|
|
149
|
+
if (this.year !== null) {
|
|
150
|
+
fullYear = next.getFullYear();
|
|
151
|
+
if ((typeof this.year == 'number') && (this.year < fullYear)) {
|
|
152
|
+
next = null;
|
|
153
|
+
break;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (!recurMatch(fullYear, this.year)) {
|
|
157
|
+
next.addYear();
|
|
158
|
+
next.setMonth(0);
|
|
159
|
+
next.setDate(1);
|
|
160
|
+
next.setHours(0);
|
|
161
|
+
next.setMinutes(0);
|
|
162
|
+
next.setSeconds(0);
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
if (this.month != null && !recurMatch(next.getMonth(), this.month)) {
|
|
167
|
+
next.addMonth();
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
if (this.date != null && !recurMatch(next.getDate(), this.date)) {
|
|
171
|
+
next.addDay();
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
if (this.dayOfWeek != null && !recurMatch(next.getDay(), this.dayOfWeek)) {
|
|
175
|
+
next.addDay();
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
if (this.hour != null && !recurMatch(next.getHours(), this.hour)) {
|
|
179
|
+
next.addHour();
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
if (this.minute != null && !recurMatch(next.getMinutes(), this.minute)) {
|
|
183
|
+
next.addMinute();
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
if (this.second != null && !recurMatch(next.getSeconds(), this.second)) {
|
|
187
|
+
next.addSecond();
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
break;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return next;
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
function recurMatch(val, matcher) {
|
|
198
|
+
if (matcher == null) {
|
|
199
|
+
return true;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (typeof matcher === 'number') {
|
|
203
|
+
return (val === matcher);
|
|
204
|
+
} else if(typeof matcher === 'string') {
|
|
205
|
+
return (val === Number(matcher));
|
|
206
|
+
} else if (matcher instanceof Range) {
|
|
207
|
+
return matcher.contains(val);
|
|
208
|
+
} else if (Array.isArray(matcher) || (matcher instanceof Array)) {
|
|
209
|
+
for (let i = 0; i < matcher.length; i++) {
|
|
210
|
+
if (recurMatch(val, matcher[i])) {
|
|
211
|
+
return true;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return false;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/* Date-based scheduler */
|
|
220
|
+
function runOnDate(date, job) {
|
|
221
|
+
const now = Date.now();
|
|
222
|
+
const then = date.getTime();
|
|
223
|
+
|
|
224
|
+
return lt.setTimeout(function() {
|
|
225
|
+
if (then > Date.now())
|
|
226
|
+
runOnDate(date, job);
|
|
227
|
+
else
|
|
228
|
+
job();
|
|
229
|
+
}, (then < now ? 0 : then - now));
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function scheduleInvocation(invocation) {
|
|
233
|
+
sorted.add(invocations, invocation, sorter);
|
|
234
|
+
prepareNextInvocation();
|
|
235
|
+
const date = invocation.fireDate instanceof CronDate ? invocation.fireDate.toDate() : invocation.fireDate;
|
|
236
|
+
invocation.job.emit('scheduled', date);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function prepareNextInvocation() {
|
|
240
|
+
if (invocations.length > 0 && currentInvocation !== invocations[0]) {
|
|
241
|
+
if (currentInvocation !== null) {
|
|
242
|
+
lt.clearTimeout(currentInvocation.timerID);
|
|
243
|
+
currentInvocation.timerID = null;
|
|
244
|
+
currentInvocation = null;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
currentInvocation = invocations[0];
|
|
248
|
+
|
|
249
|
+
const job = currentInvocation.job;
|
|
250
|
+
const cinv = currentInvocation;
|
|
251
|
+
currentInvocation.timerID = runOnDate(currentInvocation.fireDate, function() {
|
|
252
|
+
currentInvocationFinished();
|
|
253
|
+
|
|
254
|
+
if (job.callback) {
|
|
255
|
+
job.callback();
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
if (cinv.recurrenceRule.recurs || cinv.recurrenceRule._endDate === null) {
|
|
259
|
+
const inv = scheduleNextRecurrence(cinv.recurrenceRule, cinv.job, cinv.fireDate, cinv.endDate);
|
|
260
|
+
if (inv !== null) {
|
|
261
|
+
inv.job.trackInvocation(inv);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
job.stopTrackingInvocation(cinv);
|
|
266
|
+
|
|
267
|
+
try {
|
|
268
|
+
const result = job.invoke(cinv.fireDate instanceof CronDate ? cinv.fireDate.toDate() : cinv.fireDate);
|
|
269
|
+
job.emit('run');
|
|
270
|
+
job.running += 1;
|
|
271
|
+
|
|
272
|
+
if (result instanceof Promise) {
|
|
273
|
+
result.then(function (value) {
|
|
274
|
+
job.emit('success', value);
|
|
275
|
+
job.running -= 1;
|
|
276
|
+
}).catch(function (err) {
|
|
277
|
+
job.emit('error', err);
|
|
278
|
+
job.running -= 1;
|
|
279
|
+
});
|
|
280
|
+
} else {
|
|
281
|
+
job.emit('success', result);
|
|
282
|
+
job.running -= 1;
|
|
283
|
+
}
|
|
284
|
+
} catch (err) {
|
|
285
|
+
job.emit('error', err);
|
|
286
|
+
job.running -= 1;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
if (job.isOneTimeJob) {
|
|
290
|
+
job.deleteFromSchedule();
|
|
291
|
+
}
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
function currentInvocationFinished() {
|
|
297
|
+
invocations.shift();
|
|
298
|
+
currentInvocation = null;
|
|
299
|
+
prepareNextInvocation();
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function cancelInvocation(invocation) {
|
|
303
|
+
const idx = invocations.indexOf(invocation);
|
|
304
|
+
if (idx > -1) {
|
|
305
|
+
invocations.splice(idx, 1);
|
|
306
|
+
if (invocation.timerID !== null) {
|
|
307
|
+
lt.clearTimeout(invocation.timerID);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
if (currentInvocation === invocation) {
|
|
311
|
+
currentInvocation = null;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
invocation.job.emit('canceled', invocation.fireDate);
|
|
315
|
+
prepareNextInvocation();
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/* Recurrence scheduler */
|
|
320
|
+
function scheduleNextRecurrence(rule, job, prevDate, endDate) {
|
|
321
|
+
|
|
322
|
+
prevDate = (prevDate instanceof CronDate) ? prevDate : new CronDate();
|
|
323
|
+
|
|
324
|
+
const date = (rule instanceof RecurrenceRule) ? rule._nextInvocationDate(prevDate) : rule.next();
|
|
325
|
+
if (date === null) {
|
|
326
|
+
return null;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
if ((endDate instanceof CronDate) && date.getTime() > endDate.getTime()) {
|
|
330
|
+
return null;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
const inv = new Invocation(job, date, rule, endDate);
|
|
334
|
+
scheduleInvocation(inv);
|
|
335
|
+
|
|
336
|
+
return inv;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
module.exports = {
|
|
340
|
+
Range,
|
|
341
|
+
RecurrenceRule,
|
|
342
|
+
Invocation,
|
|
343
|
+
cancelInvocation,
|
|
344
|
+
scheduleInvocation,
|
|
345
|
+
scheduleNextRecurrence,
|
|
346
|
+
sorter,
|
|
347
|
+
_invocations: invocations
|
|
348
|
+
}
|
package/lib/Job.js
ADDED
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const events = require('events')
|
|
4
|
+
const cronParser = require('cron-parser')
|
|
5
|
+
const CronDate = require('cron-parser/lib/date')
|
|
6
|
+
const sorted = require('sorted-array-functions')
|
|
7
|
+
|
|
8
|
+
const { scheduleNextRecurrence, scheduleInvocation, cancelInvocation, RecurrenceRule, sorter, Invocation } = require('./Invocation')
|
|
9
|
+
const { isValidDate } = require('./utils/dateUtils')
|
|
10
|
+
|
|
11
|
+
const scheduledJobs = {};
|
|
12
|
+
|
|
13
|
+
let anonJobCounter = 0;
|
|
14
|
+
function resolveAnonJobName() {
|
|
15
|
+
const now = new Date()
|
|
16
|
+
if (anonJobCounter === Number.MAX_SAFE_INTEGER) {
|
|
17
|
+
anonJobCounter = 0
|
|
18
|
+
}
|
|
19
|
+
anonJobCounter++
|
|
20
|
+
|
|
21
|
+
return `<Anonymous Job ${anonJobCounter} ${now.toISOString()}>`
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function Job(name, job, callback) {
|
|
25
|
+
// setup a private pendingInvocations variable
|
|
26
|
+
this.pendingInvocations = [];
|
|
27
|
+
|
|
28
|
+
//setup a private number of invocations variable
|
|
29
|
+
let triggeredJobs = 0;
|
|
30
|
+
|
|
31
|
+
// Set scope vars
|
|
32
|
+
const jobName = name && typeof name === 'string' ? name : resolveAnonJobName();
|
|
33
|
+
this.job = name && typeof name === 'function' ? name : job;
|
|
34
|
+
|
|
35
|
+
// Make sure callback is actually a callback
|
|
36
|
+
if (this.job === name) {
|
|
37
|
+
// Name wasn't provided and maybe a callback is there
|
|
38
|
+
this.callback = typeof job === 'function' ? job : false;
|
|
39
|
+
} else {
|
|
40
|
+
// Name was provided, and maybe a callback is there
|
|
41
|
+
this.callback = typeof callback === 'function' ? callback : false;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// task count
|
|
45
|
+
this.running = 0;
|
|
46
|
+
|
|
47
|
+
// Check for generator
|
|
48
|
+
if (typeof this.job === 'function' &&
|
|
49
|
+
this.job.prototype &&
|
|
50
|
+
this.job.prototype.next) {
|
|
51
|
+
this.job = function() {
|
|
52
|
+
return this.next().value;
|
|
53
|
+
}.bind(this.job.call(this));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// define properties
|
|
57
|
+
Object.defineProperty(this, 'name', {
|
|
58
|
+
value: jobName,
|
|
59
|
+
writable: false,
|
|
60
|
+
enumerable: true
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
// method that require private access
|
|
64
|
+
this.trackInvocation = function(invocation) {
|
|
65
|
+
// add to our invocation list
|
|
66
|
+
sorted.add(this.pendingInvocations, invocation, sorter);
|
|
67
|
+
return true;
|
|
68
|
+
};
|
|
69
|
+
this.stopTrackingInvocation = function(invocation) {
|
|
70
|
+
const invIdx = this.pendingInvocations.indexOf(invocation);
|
|
71
|
+
if (invIdx > -1) {
|
|
72
|
+
this.pendingInvocations.splice(invIdx, 1);
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return false;
|
|
77
|
+
};
|
|
78
|
+
this.triggeredJobs = function() {
|
|
79
|
+
return triggeredJobs;
|
|
80
|
+
};
|
|
81
|
+
this.setTriggeredJobs = function(triggeredJob) {
|
|
82
|
+
triggeredJobs = triggeredJob;
|
|
83
|
+
};
|
|
84
|
+
this.deleteFromSchedule = function() {
|
|
85
|
+
deleteScheduledJob(this.name)
|
|
86
|
+
};
|
|
87
|
+
this.cancel = function(reschedule) {
|
|
88
|
+
reschedule = (typeof reschedule == 'boolean') ? reschedule : false;
|
|
89
|
+
|
|
90
|
+
let inv, newInv;
|
|
91
|
+
const newInvs = [];
|
|
92
|
+
for (let j = 0; j < this.pendingInvocations.length; j++) {
|
|
93
|
+
inv = this.pendingInvocations[j];
|
|
94
|
+
|
|
95
|
+
cancelInvocation(inv);
|
|
96
|
+
|
|
97
|
+
if (reschedule && (inv.recurrenceRule.recurs || inv.recurrenceRule.next)) {
|
|
98
|
+
newInv = scheduleNextRecurrence(inv.recurrenceRule, this, inv.fireDate, inv.endDate);
|
|
99
|
+
if (newInv !== null) {
|
|
100
|
+
newInvs.push(newInv);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
this.pendingInvocations = [];
|
|
106
|
+
|
|
107
|
+
for (let k = 0; k < newInvs.length; k++) {
|
|
108
|
+
this.trackInvocation(newInvs[k]);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// remove from scheduledJobs if reschedule === false
|
|
112
|
+
if (!reschedule) {
|
|
113
|
+
this.deleteFromSchedule()
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return true;
|
|
117
|
+
};
|
|
118
|
+
this.cancelNext = function(reschedule) {
|
|
119
|
+
reschedule = (typeof reschedule == 'boolean') ? reschedule : true;
|
|
120
|
+
|
|
121
|
+
if (!this.pendingInvocations.length) {
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
let newInv;
|
|
126
|
+
const nextInv = this.pendingInvocations.shift();
|
|
127
|
+
|
|
128
|
+
cancelInvocation(nextInv);
|
|
129
|
+
|
|
130
|
+
if (reschedule && (nextInv.recurrenceRule.recurs || nextInv.recurrenceRule.next)) {
|
|
131
|
+
newInv = scheduleNextRecurrence(nextInv.recurrenceRule, this, nextInv.fireDate, nextInv.endDate);
|
|
132
|
+
if (newInv !== null) {
|
|
133
|
+
this.trackInvocation(newInv);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return true;
|
|
138
|
+
};
|
|
139
|
+
this.reschedule = function(spec) {
|
|
140
|
+
let inv;
|
|
141
|
+
const invocationsToCancel = this.pendingInvocations.slice();
|
|
142
|
+
|
|
143
|
+
for (let j = 0; j < invocationsToCancel.length; j++) {
|
|
144
|
+
inv = invocationsToCancel[j];
|
|
145
|
+
|
|
146
|
+
cancelInvocation(inv);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
this.pendingInvocations = [];
|
|
150
|
+
|
|
151
|
+
if (this.schedule(spec)) {
|
|
152
|
+
this.setTriggeredJobs(0);
|
|
153
|
+
return true;
|
|
154
|
+
} else {
|
|
155
|
+
this.pendingInvocations = invocationsToCancel;
|
|
156
|
+
return false;
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
this.nextInvocation = function() {
|
|
160
|
+
if (!this.pendingInvocations.length) {
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
return this.pendingInvocations[0].fireDate;
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
Object.setPrototypeOf(Job.prototype, events.EventEmitter.prototype);
|
|
168
|
+
|
|
169
|
+
Job.prototype.invoke = function(fireDate) {
|
|
170
|
+
this.setTriggeredJobs(this.triggeredJobs() + 1);
|
|
171
|
+
return this.job(fireDate);
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
Job.prototype.runOnDate = function(date) {
|
|
175
|
+
return this.schedule(date);
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
Job.prototype.schedule = function(spec) {
|
|
179
|
+
const self = this;
|
|
180
|
+
let success = false;
|
|
181
|
+
let inv;
|
|
182
|
+
let start;
|
|
183
|
+
let end;
|
|
184
|
+
let tz;
|
|
185
|
+
|
|
186
|
+
// save passed-in value before 'spec' is replaced
|
|
187
|
+
if (typeof spec === 'object' && 'tz' in spec) {
|
|
188
|
+
tz = spec.tz;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (typeof spec === 'object' && spec.rule) {
|
|
192
|
+
start = spec.start || undefined;
|
|
193
|
+
end = spec.end || undefined;
|
|
194
|
+
spec = spec.rule;
|
|
195
|
+
|
|
196
|
+
if (start) {
|
|
197
|
+
if (!(start instanceof Date)) {
|
|
198
|
+
start = new Date(start);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
start = new CronDate(start, tz);
|
|
202
|
+
if (!isValidDate(start) || start.getTime() < Date.now()) {
|
|
203
|
+
start = undefined;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (end && !(end instanceof Date) && !isValidDate(end = new Date(end))) {
|
|
208
|
+
end = undefined;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (end) {
|
|
212
|
+
end = new CronDate(end, tz);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
try {
|
|
217
|
+
const res = cronParser.parseExpression(spec, {currentDate: start, tz: tz});
|
|
218
|
+
inv = scheduleNextRecurrence(res, self, start, end);
|
|
219
|
+
if (inv !== null) {
|
|
220
|
+
success = self.trackInvocation(inv);
|
|
221
|
+
}
|
|
222
|
+
} catch (err) {
|
|
223
|
+
const type = typeof spec;
|
|
224
|
+
if ((type === 'string') || (type === 'number')) {
|
|
225
|
+
spec = new Date(spec);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if ((spec instanceof Date) && (isValidDate(spec))) {
|
|
229
|
+
spec = new CronDate(spec);
|
|
230
|
+
self.isOneTimeJob = true;
|
|
231
|
+
if (spec.getTime() >= Date.now()) {
|
|
232
|
+
inv = new Invocation(self, spec);
|
|
233
|
+
scheduleInvocation(inv);
|
|
234
|
+
success = self.trackInvocation(inv);
|
|
235
|
+
}
|
|
236
|
+
} else if (type === 'object') {
|
|
237
|
+
self.isOneTimeJob = false;
|
|
238
|
+
if (!(spec instanceof RecurrenceRule)) {
|
|
239
|
+
const r = new RecurrenceRule();
|
|
240
|
+
if ('year' in spec) {
|
|
241
|
+
r.year = spec.year;
|
|
242
|
+
}
|
|
243
|
+
if ('month' in spec) {
|
|
244
|
+
r.month = spec.month;
|
|
245
|
+
}
|
|
246
|
+
if ('date' in spec) {
|
|
247
|
+
r.date = spec.date;
|
|
248
|
+
}
|
|
249
|
+
if ('dayOfWeek' in spec) {
|
|
250
|
+
r.dayOfWeek = spec.dayOfWeek;
|
|
251
|
+
}
|
|
252
|
+
if ('hour' in spec) {
|
|
253
|
+
r.hour = spec.hour;
|
|
254
|
+
}
|
|
255
|
+
if ('minute' in spec) {
|
|
256
|
+
r.minute = spec.minute;
|
|
257
|
+
}
|
|
258
|
+
if ('second' in spec) {
|
|
259
|
+
r.second = spec.second;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
spec = r;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
spec.tz = tz;
|
|
266
|
+
inv = scheduleNextRecurrence(spec, self, start, end);
|
|
267
|
+
if (inv !== null) {
|
|
268
|
+
success = self.trackInvocation(inv);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
scheduledJobs[this.name] = this;
|
|
274
|
+
return success;
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
function deleteScheduledJob(name) {
|
|
278
|
+
if (name) {
|
|
279
|
+
delete scheduledJobs[name];
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
module.exports = {
|
|
284
|
+
Job,
|
|
285
|
+
deleteScheduledJob,
|
|
286
|
+
scheduledJobs
|
|
287
|
+
}
|