alchemymvc 1.3.16 → 1.3.17
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/lib/app/datasource/mongo_datasource.js +51 -29
- package/lib/app/element/time_ago.js +19 -36
- package/lib/app/helper/alchemy_helper.js +2 -2
- package/lib/app/helper/cron.js +1502 -0
- package/lib/app/helper_model/document.js +1 -0
- package/lib/app/model/alchemy_task_history_model.js +109 -0
- package/lib/app/model/alchemy_task_model.js +141 -18
- package/lib/bootstrap.js +3 -0
- package/lib/class/conduit.js +36 -12
- package/lib/class/model.js +24 -0
- package/lib/class/schema_client.js +8 -11
- package/lib/class/session.js +14 -3
- package/lib/class/task.js +297 -145
- package/lib/class/task_service.js +933 -0
- package/lib/core/base.js +23 -1
- package/lib/core/middleware.js +2 -2
- package/lib/init/alchemy.js +82 -4
- package/package.json +2 -2
|
@@ -0,0 +1,1502 @@
|
|
|
1
|
+
const VAL_L = 'l';
|
|
2
|
+
const VAL_W = 'w';
|
|
3
|
+
const VAL_LW = 'lw';
|
|
4
|
+
const VAL_Q = '?';
|
|
5
|
+
const VAL_HASH = '#';
|
|
6
|
+
const VAL_STAR = '*';
|
|
7
|
+
const VAL_DASH = '-';
|
|
8
|
+
const VAL_SLASH = '/';
|
|
9
|
+
|
|
10
|
+
const FIELDS = ['second', 'minute', 'hour', 'day_of_month', 'month', 'day_of_week', 'year'];
|
|
11
|
+
|
|
12
|
+
const FIELD_INFO = {
|
|
13
|
+
second: {min: 0, max: 59},
|
|
14
|
+
minute: {min: 0, max: 59},
|
|
15
|
+
hour: {min: 0, max: 23},
|
|
16
|
+
day: {min: 1, max: 31},
|
|
17
|
+
day_of_month: {min: 1, max: 31},
|
|
18
|
+
month: {
|
|
19
|
+
min: 1,
|
|
20
|
+
max: 12,
|
|
21
|
+
alias: {
|
|
22
|
+
jan: 1,
|
|
23
|
+
feb: 2,
|
|
24
|
+
mar: 3,
|
|
25
|
+
apr: 4,
|
|
26
|
+
may: 5,
|
|
27
|
+
jun: 6,
|
|
28
|
+
jul: 7,
|
|
29
|
+
aug: 8,
|
|
30
|
+
sep: 9,
|
|
31
|
+
oct: 10,
|
|
32
|
+
nov: 11,
|
|
33
|
+
dec: 12,
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
day_of_week: {
|
|
37
|
+
min: 0,
|
|
38
|
+
max: 7,
|
|
39
|
+
alias: {
|
|
40
|
+
7: 0,
|
|
41
|
+
sun: 0,
|
|
42
|
+
mon: 1,
|
|
43
|
+
tue: 2,
|
|
44
|
+
wed: 3,
|
|
45
|
+
thu: 4,
|
|
46
|
+
fri: 5,
|
|
47
|
+
sat: 6,
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
year: {min: 1970, max: 2099},
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* The Cron class:
|
|
55
|
+
* Represents a Cron frequency
|
|
56
|
+
*
|
|
57
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
58
|
+
* @since 1.3.17
|
|
59
|
+
* @version 1.3.17
|
|
60
|
+
*/
|
|
61
|
+
const Cron = Function.inherits('Alchemy.Base', 'Alchemy.Cron', function Cron(input) {
|
|
62
|
+
|
|
63
|
+
// The original input
|
|
64
|
+
this.input = input;
|
|
65
|
+
|
|
66
|
+
// The parsed expressions
|
|
67
|
+
this.expressions = [];
|
|
68
|
+
|
|
69
|
+
// Options
|
|
70
|
+
this.options = {};
|
|
71
|
+
|
|
72
|
+
// The current expression
|
|
73
|
+
this.current_expression = null;
|
|
74
|
+
|
|
75
|
+
if (input) {
|
|
76
|
+
this.parse(input);
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Predefined expressions
|
|
82
|
+
*
|
|
83
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
84
|
+
* @since 1.3.17
|
|
85
|
+
* @version 1.3.17
|
|
86
|
+
*/
|
|
87
|
+
Cron.setStatic('PREDEFINED_EXPRESSIONS', {
|
|
88
|
+
'@yearly' : '0 0 1 1 ?',
|
|
89
|
+
'@monthly' : '0 0 1 * ?',
|
|
90
|
+
'@weekly' : '0 0 ? * 0',
|
|
91
|
+
'@daily' : '0 0 * * ?',
|
|
92
|
+
'@hourly' : '0 * * * ?',
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Undry this value
|
|
97
|
+
*
|
|
98
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
99
|
+
* @since 1.3.17
|
|
100
|
+
* @version 1.3.17
|
|
101
|
+
*
|
|
102
|
+
* @param {Object} data
|
|
103
|
+
*
|
|
104
|
+
* @return {Cron}
|
|
105
|
+
*/
|
|
106
|
+
Cron.setStatic(function unDry(data) {
|
|
107
|
+
let result = new Cron(data.input, data.options);
|
|
108
|
+
return result;
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Serialize this cron instance
|
|
113
|
+
*
|
|
114
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
115
|
+
* @since 1.3.17
|
|
116
|
+
* @version 1.3.17
|
|
117
|
+
*/
|
|
118
|
+
Cron.setMethod(function toDry() {
|
|
119
|
+
return {
|
|
120
|
+
value: {
|
|
121
|
+
input : this.input,
|
|
122
|
+
options : this.options,
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Parse the input
|
|
129
|
+
*
|
|
130
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
131
|
+
* @since 1.3.17
|
|
132
|
+
* @version 1.3.17
|
|
133
|
+
*/
|
|
134
|
+
Cron.setMethod(function parse(input) {
|
|
135
|
+
|
|
136
|
+
if (!input) {
|
|
137
|
+
throw new Error('The Cron expression cannot be empty');
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Remember the input
|
|
141
|
+
this.input = input;
|
|
142
|
+
|
|
143
|
+
// Parse the expressions
|
|
144
|
+
this.expressions = splitAndCleanup(input, '|').map(expression => this.parseSingleExpression(expression, this.options));
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Parse a single expression
|
|
149
|
+
*
|
|
150
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
151
|
+
* @since 1.3.17
|
|
152
|
+
* @version 1.3.17
|
|
153
|
+
*
|
|
154
|
+
* @param {String} expression
|
|
155
|
+
*
|
|
156
|
+
* @return {Cron.Expression}
|
|
157
|
+
*/
|
|
158
|
+
Cron.setMethod(function parseSingleExpression(expression) {
|
|
159
|
+
let result = new CronExpression(expression);
|
|
160
|
+
return result;
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Get the next valid date this cron job will run at
|
|
165
|
+
*
|
|
166
|
+
* @author Santhosh Kumar <brsanthu@gmail.com>
|
|
167
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
168
|
+
* @since 1.3.17
|
|
169
|
+
* @version 1.3.17
|
|
170
|
+
*
|
|
171
|
+
* @param {Date} current_date
|
|
172
|
+
*
|
|
173
|
+
* @return {Date}
|
|
174
|
+
*/
|
|
175
|
+
Cron.setMethod(function getNextDate(current_date) {
|
|
176
|
+
|
|
177
|
+
if (!current_date) {
|
|
178
|
+
current_date = new Date();
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
let result,
|
|
182
|
+
test;
|
|
183
|
+
|
|
184
|
+
for (let expression of this.expressions) {
|
|
185
|
+
test = expression.getNextDate(current_date, true);
|
|
186
|
+
|
|
187
|
+
if (!result) {
|
|
188
|
+
result = test;
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (!test || test > result) {
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
result = test;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return result;
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Does the given date match the current cron expression?
|
|
204
|
+
* Seconds will be ignored!
|
|
205
|
+
*
|
|
206
|
+
* @author Santhosh Kumar <brsanthu@gmail.com>
|
|
207
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
208
|
+
* @since 1.3.17
|
|
209
|
+
* @version 1.3.17
|
|
210
|
+
*
|
|
211
|
+
* @param {Date} date
|
|
212
|
+
*
|
|
213
|
+
* @return {Date}
|
|
214
|
+
*/
|
|
215
|
+
Cron.setMethod(function matches(date) {
|
|
216
|
+
|
|
217
|
+
if (!date) {
|
|
218
|
+
date = new Date();
|
|
219
|
+
} else {
|
|
220
|
+
date = date.clone();
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Go to the start of the minute (set seconds & ms to 0)
|
|
224
|
+
date.startOf('minute');
|
|
225
|
+
|
|
226
|
+
let wanted_time = date.getTime(),
|
|
227
|
+
test;
|
|
228
|
+
|
|
229
|
+
for (let expression of this.expressions) {
|
|
230
|
+
test = expression.getNextDate(date, false);
|
|
231
|
+
|
|
232
|
+
if (!test) {
|
|
233
|
+
continue;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (test.getTime() == wanted_time) {
|
|
237
|
+
return true;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
return false;
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* The CronExpression:
|
|
246
|
+
* This represents a single Cron frequency expression
|
|
247
|
+
*
|
|
248
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
249
|
+
* @since 1.3.17
|
|
250
|
+
* @version 1.3.17
|
|
251
|
+
*/
|
|
252
|
+
const CronExpression = Function.inherits('Alchemy.Base', 'Alchemy.Cron', function Expression(input) {
|
|
253
|
+
this.expression = input;
|
|
254
|
+
this.parse();
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Split the string, remove empty parts & deduplicate
|
|
259
|
+
*
|
|
260
|
+
* @author Santhosh Kumar <brsanthu@gmail.com>
|
|
261
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
262
|
+
* @since 1.3.17
|
|
263
|
+
* @version 1.3.17
|
|
264
|
+
*/
|
|
265
|
+
function splitAndCleanup(input, separator) {
|
|
266
|
+
|
|
267
|
+
let pieces = input.split(separator),
|
|
268
|
+
result = new Set(),
|
|
269
|
+
piece,
|
|
270
|
+
i;
|
|
271
|
+
|
|
272
|
+
for (i = 0; i < pieces.length; i++) {
|
|
273
|
+
piece = pieces[i].trim();
|
|
274
|
+
if (!piece) continue;
|
|
275
|
+
result.add(piece);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
return [...result];
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Parse a single expression
|
|
283
|
+
*
|
|
284
|
+
* @author Santhosh Kumar <brsanthu@gmail.com>
|
|
285
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
286
|
+
* @since 1.3.17
|
|
287
|
+
* @version 1.3.17
|
|
288
|
+
*/
|
|
289
|
+
CronExpression.setMethod(function parse() {
|
|
290
|
+
|
|
291
|
+
if (!this.expression) {
|
|
292
|
+
throw new Error('Cron expression cannot be empty');
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
let internal_expression = this.expression;
|
|
296
|
+
let has_seconds = this.has_seconds;
|
|
297
|
+
|
|
298
|
+
if (Cron.PREDEFINED_EXPRESSIONS[internal_expression]) {
|
|
299
|
+
internal_expression = Cron.PREDEFINED_EXPRESSIONS[expression];
|
|
300
|
+
has_seconds = false;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
const min_fields = has_seconds ? 5 : 4;
|
|
304
|
+
const max_fields = has_seconds ? 7 : 6;
|
|
305
|
+
|
|
306
|
+
const parts = internal_expression
|
|
307
|
+
.split(/\s+/)
|
|
308
|
+
.map((part) => part.trim())
|
|
309
|
+
.filter((part) => part);
|
|
310
|
+
|
|
311
|
+
if (parts.length < min_fields || parts.length > max_fields) {
|
|
312
|
+
let message = `Invalid cron expression [${this.expression}]. Expected [${min_fields} to ${max_fields}] fields but found [${parts.length}] fields.`;
|
|
313
|
+
throw new Error(message);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// If seconds is not specified, then defaults to 0th sec
|
|
317
|
+
if (!has_seconds) {
|
|
318
|
+
parts.unshift('0');
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// If day of week is not specified, will default to ?
|
|
322
|
+
if (parts.length === 5) {
|
|
323
|
+
parts.push(VAL_Q);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// If year is not specified, then default to *
|
|
327
|
+
if (parts.length === 6) {
|
|
328
|
+
parts.push(VAL_STAR);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
const field_parts = {};
|
|
332
|
+
for (let i = 0; i < FIELDS.length; i++) {
|
|
333
|
+
field_parts[FIELDS[i]] = parts[i];
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
const parsed = {};
|
|
337
|
+
for (const field of FIELDS) {
|
|
338
|
+
if (field === 'second' && !has_seconds) {
|
|
339
|
+
parsed[field] = {omit: true};
|
|
340
|
+
} else {
|
|
341
|
+
parsed[field] = this.parseField(field, field_parts[field]);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
this.current_expression = null;
|
|
346
|
+
|
|
347
|
+
this.parsed = parsed;
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* Parse a specific field of the current expression
|
|
352
|
+
*
|
|
353
|
+
* @author Santhosh Kumar <brsanthu@gmail.com>
|
|
354
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
355
|
+
* @since 1.3.17
|
|
356
|
+
* @version 1.3.17
|
|
357
|
+
*/
|
|
358
|
+
CronExpression.setMethod(function parseField(field, value) {
|
|
359
|
+
|
|
360
|
+
value = value.toLowerCase().trim();
|
|
361
|
+
|
|
362
|
+
if (value === VAL_STAR) {
|
|
363
|
+
return {all: true};
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
if (value === VAL_Q) {
|
|
367
|
+
return this.parseQ(field, value);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
const parts = splitAndCleanup(value, ',');
|
|
371
|
+
const parsed = {};
|
|
372
|
+
|
|
373
|
+
for (const part of parts) {
|
|
374
|
+
if (!part) {
|
|
375
|
+
continue;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
if (part.indexOf(VAL_SLASH) >= 0) {
|
|
379
|
+
parsed.steps = parsed.steps || [];
|
|
380
|
+
parsed.steps.push(this.parseStepRange(field, part));
|
|
381
|
+
} else if (part.indexOf(VAL_DASH) >= 0) {
|
|
382
|
+
parsed.ranges = parsed.ranges || [];
|
|
383
|
+
parsed.ranges.push(this.parseRange(field, part));
|
|
384
|
+
} else if (part.indexOf(VAL_HASH) >= 0) {
|
|
385
|
+
parsed.nthDays = parsed.nthDays || [];
|
|
386
|
+
parsed.nthDays.push(this.parseNth(field, part));
|
|
387
|
+
} else if (part === VAL_L) {
|
|
388
|
+
parsed.lastDay = this.parseL(field, part);
|
|
389
|
+
} else if (part === VAL_LW) {
|
|
390
|
+
parsed.lastWeekday = this.parseLW(field, part);
|
|
391
|
+
} else if (field === 'day_of_month' && part.indexOf(VAL_W) >= 0) {
|
|
392
|
+
parsed.nearestWeekdays = parsed.nearestWeekdays || [];
|
|
393
|
+
parsed.nearestWeekdays.push(this.parseNearestWeekday(field, part));
|
|
394
|
+
} else if (field === 'day_of_week' && part.endsWith(VAL_L)) {
|
|
395
|
+
parsed.lastDays = parsed.lastDays || [];
|
|
396
|
+
parsed.lastDays.push(this.parseLastDays(field, part));
|
|
397
|
+
} else {
|
|
398
|
+
parsed.values = parsed.values || [];
|
|
399
|
+
parsed.values.push(this.parseValue(field, part));
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
if (parsed.values) {
|
|
404
|
+
parsed.values = dedupe(parsed.values);
|
|
405
|
+
parsed.values.sort((a, b) => a - b);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
return parsed;
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
/**
|
|
412
|
+
* Parse a question mark value for the current field
|
|
413
|
+
*
|
|
414
|
+
* @author Santhosh Kumar <brsanthu@gmail.com>
|
|
415
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
416
|
+
* @since 1.3.17
|
|
417
|
+
* @version 1.3.17
|
|
418
|
+
*/
|
|
419
|
+
CronExpression.setMethod(function parseQ(field, value) {
|
|
420
|
+
|
|
421
|
+
if (field === 'day_of_week' || field === 'day_of_month') {
|
|
422
|
+
return {omit: true};
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
throw this.createInvalidExpressionError(
|
|
426
|
+
`Invalid Value [${value}] for field [${field}]. It can be specified only for [day_of_month or day_of_week] fields.`
|
|
427
|
+
);
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
/**
|
|
431
|
+
* Parse "L" syntax: the last day of the month
|
|
432
|
+
*
|
|
433
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
434
|
+
* @since 1.3.17
|
|
435
|
+
* @version 1.3.17
|
|
436
|
+
*/
|
|
437
|
+
CronExpression.setMethod(function parseL(field, value) {
|
|
438
|
+
|
|
439
|
+
if (field === 'day_of_week' || field === 'day_of_month') {
|
|
440
|
+
return true;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
throw this.createInvalidExpressionError(
|
|
444
|
+
`Invalid value for [${value}] for field [${field}]. It can be used only for [day_of_month or day_of_week] fields.`
|
|
445
|
+
);
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
/**
|
|
449
|
+
* Parse "LW" syntax, meaning: "last workday of the month"
|
|
450
|
+
*
|
|
451
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
452
|
+
* @since 1.3.17
|
|
453
|
+
* @version 1.3.17
|
|
454
|
+
*/
|
|
455
|
+
CronExpression.setMethod(function parseLW(field, value) {
|
|
456
|
+
|
|
457
|
+
if (field === 'day_of_month') {
|
|
458
|
+
return true;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
throw this.createInvalidExpressionError(
|
|
462
|
+
`Invalid value for [${value}] for field [${field}]. It can be used only for [day_of_month] fields.`
|
|
463
|
+
);
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
/**
|
|
467
|
+
* Parse the steps of a specific field value
|
|
468
|
+
* (Like /5)
|
|
469
|
+
*
|
|
470
|
+
* @author Santhosh Kumar <brsanthu@gmail.com>
|
|
471
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
472
|
+
* @since 1.3.17
|
|
473
|
+
* @version 1.3.17
|
|
474
|
+
*/
|
|
475
|
+
CronExpression.setMethod(function parseStepRange(field, value) {
|
|
476
|
+
|
|
477
|
+
const parts = value.split(VAL_SLASH);
|
|
478
|
+
if (parts.length != 2) {
|
|
479
|
+
throw this.createInvalidExpressionError(
|
|
480
|
+
`Invalid step range [${value}] for field [${field}]. Expected exactly 2 values separated by a / but got [${parts.length}] values.`
|
|
481
|
+
);
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
const info = FIELD_INFO[field];
|
|
485
|
+
const fromParts = parts[0].indexOf(VAL_DASH) >= 0 ? parts[0].split(VAL_DASH) : [parts[0]];
|
|
486
|
+
const from = fromParts[0] === VAL_STAR ? info.min : this.parseNumber(field, unalias(field, fromParts[0]));
|
|
487
|
+
const to = fromParts.length > 1 ? this.parseNumber(field, unalias(field, fromParts[1])) : info.max;
|
|
488
|
+
const step = this.parseNumber(field, unalias(field, parts[1]));
|
|
489
|
+
|
|
490
|
+
if (from < info.min) {
|
|
491
|
+
throw this.createInvalidExpressionError(
|
|
492
|
+
`Invalid step range [${value}] for field [${field}]. From value [${from}] out of range. It must be greater than or equals to [${info.min}]`
|
|
493
|
+
);
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
if (to > info.max) {
|
|
497
|
+
throw this.createInvalidExpressionError(
|
|
498
|
+
`Invalid step range [${value}] for field [${field}]. To value [${to}] out of range. It must be less than or equals to [${info.max}]`
|
|
499
|
+
);
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
if (step > info.max) {
|
|
503
|
+
throw this.createInvalidExpressionError(
|
|
504
|
+
`Invalid step range [${value}] for field [${field}]. Step value [${value}] out of range. It must be less than or equals to [${info.max}]`
|
|
505
|
+
);
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
return {from, to, step};
|
|
509
|
+
});
|
|
510
|
+
|
|
511
|
+
/**
|
|
512
|
+
* Parse a number for the current field.
|
|
513
|
+
* If it turns out to not be a valid number, an error will be thrown.
|
|
514
|
+
*
|
|
515
|
+
* @author Santhosh Kumar <brsanthu@gmail.com>
|
|
516
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
517
|
+
* @since 1.3.17
|
|
518
|
+
* @version 1.3.17
|
|
519
|
+
*/
|
|
520
|
+
CronExpression.setMethod(function parseNumber(field, value) {
|
|
521
|
+
const num = parseInt(unalias(field, value), 10);
|
|
522
|
+
|
|
523
|
+
if (Number.isNaN(num)) {
|
|
524
|
+
throw this.createInvalidExpressionError(`Invalid numeric value [${value}] in field [${field}].`);
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
return num;
|
|
528
|
+
});
|
|
529
|
+
|
|
530
|
+
/**
|
|
531
|
+
* Parse a range (like 1-5)
|
|
532
|
+
*
|
|
533
|
+
* @author Santhosh Kumar <brsanthu@gmail.com>
|
|
534
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
535
|
+
* @since 1.3.17
|
|
536
|
+
* @version 1.3.17
|
|
537
|
+
*/
|
|
538
|
+
CronExpression.setMethod(function parseRange(field, value) {
|
|
539
|
+
|
|
540
|
+
const parts = value.split(VAL_DASH);
|
|
541
|
+
|
|
542
|
+
if (parts.length != 2) {
|
|
543
|
+
throw this.createInvalidExpressionError(
|
|
544
|
+
`Invalid range [${value}] for field [${field}]. Range should have two values separated by a - but got [${parts.length}] values.`
|
|
545
|
+
);
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
const from = this.parseNumber(field, unalias(field, parts[0]));
|
|
549
|
+
let to = this.parseNumber(field, unalias(field, parts[1]));
|
|
550
|
+
|
|
551
|
+
// For day of week, sun will act as 0 or 7 depending on if it is in from or to
|
|
552
|
+
if (field == 'day_of_week') {
|
|
553
|
+
if (to === 0) {
|
|
554
|
+
to = 7;
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
if (from > to) {
|
|
559
|
+
throw this.createInvalidExpressionError(`Invalid range [${value}] for field [${field}]. From value must be less than to value.`);
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
const info = FIELD_INFO[field];
|
|
563
|
+
|
|
564
|
+
if (from < info.min || to > info.max) {
|
|
565
|
+
throw this.createInvalidExpressionError(
|
|
566
|
+
`Invalid range [${value}] for field [${field}]. From or to value is out of allowed min/max values. Allowed values are between [${info.min}-${info.max}].`
|
|
567
|
+
);
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
return {from, to};
|
|
571
|
+
});
|
|
572
|
+
|
|
573
|
+
/**
|
|
574
|
+
* Parse a value
|
|
575
|
+
*
|
|
576
|
+
* @author Santhosh Kumar <brsanthu@gmail.com>
|
|
577
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
578
|
+
* @since 1.3.17
|
|
579
|
+
* @version 1.3.17
|
|
580
|
+
*/
|
|
581
|
+
CronExpression.setMethod(function parseValue(field, value) {
|
|
582
|
+
|
|
583
|
+
const num = this.parseNumber(field, value);
|
|
584
|
+
const info = FIELD_INFO[field];
|
|
585
|
+
if (num < info.min) {
|
|
586
|
+
throw this.createInvalidExpressionError(
|
|
587
|
+
`Value [${value}] out of range for field [${field}]. It must be greater than or equals to [${info.min}].`
|
|
588
|
+
);
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
if (info.max && num > info.max) {
|
|
592
|
+
throw this.createInvalidExpressionError(
|
|
593
|
+
`Value [${value}] out of range for field [${field}]. It must be less than or equals to [${info.max}].`
|
|
594
|
+
);
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
return num;
|
|
598
|
+
});
|
|
599
|
+
|
|
600
|
+
/**
|
|
601
|
+
* Parse Nth day of week (like 5#3, the 3rd friday of the month)
|
|
602
|
+
*
|
|
603
|
+
* @author Santhosh Kumar <brsanthu@gmail.com>
|
|
604
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
605
|
+
* @since 1.3.17
|
|
606
|
+
* @version 1.3.17
|
|
607
|
+
*/
|
|
608
|
+
CronExpression.setMethod(function parseNth(field, value) {
|
|
609
|
+
|
|
610
|
+
if (field !== 'day_of_week') {
|
|
611
|
+
throw this.createInvalidExpressionError(
|
|
612
|
+
`Invalid value [${value}] for field [${field}]. Nth day can be used only in [day_of_week] field.`
|
|
613
|
+
);
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
const parts = value.split(VAL_HASH);
|
|
617
|
+
if (parts.length !== 2) {
|
|
618
|
+
throw this.createInvalidExpressionError(
|
|
619
|
+
`Invalid nth day value [${value}] for field [${field}]. It must be in [day_of_week#instance] format.`
|
|
620
|
+
);
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
const day_of_week = this.parseNumber(field, parts[0]);
|
|
624
|
+
const instance = this.parseNumber(undefined, parts[1]);
|
|
625
|
+
|
|
626
|
+
if (instance < 1 || instance > 5) {
|
|
627
|
+
throw this.createInvalidExpressionError(
|
|
628
|
+
`Invalid Day of Week instance value [${instance}] for field [${field}]. It must be between 1 and 5.`
|
|
629
|
+
);
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
return {
|
|
633
|
+
day_of_week,
|
|
634
|
+
instance: instance,
|
|
635
|
+
};
|
|
636
|
+
});
|
|
637
|
+
|
|
638
|
+
/**
|
|
639
|
+
* Parse
|
|
640
|
+
*
|
|
641
|
+
* @author Santhosh Kumar <brsanthu@gmail.com>
|
|
642
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
643
|
+
* @since 1.3.17
|
|
644
|
+
* @version 1.3.17
|
|
645
|
+
*/
|
|
646
|
+
CronExpression.setMethod(function parseNearestWeekday(field, value) {
|
|
647
|
+
if (field !== 'day_of_month') {
|
|
648
|
+
throw this.createInvalidExpressionError(
|
|
649
|
+
`Invalid value [${value}] for field [${field}]. Nearest weekday can be used only in [day_of_month] field.`
|
|
650
|
+
);
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
return this.parseNumber(field, value.split(VAL_W)[0]);
|
|
654
|
+
});
|
|
655
|
+
|
|
656
|
+
/**
|
|
657
|
+
* Parse "last days" expressions, like "sunl"
|
|
658
|
+
* which means "Last sunday of the month"
|
|
659
|
+
*
|
|
660
|
+
* @author Santhosh Kumar <brsanthu@gmail.com>
|
|
661
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
662
|
+
* @since 1.3.17
|
|
663
|
+
* @version 1.3.17
|
|
664
|
+
*/
|
|
665
|
+
CronExpression.setMethod(function parseLastDays(field, value) {
|
|
666
|
+
return this.parseNumber(field, value.split(VAL_L)[0]);
|
|
667
|
+
});
|
|
668
|
+
|
|
669
|
+
/**
|
|
670
|
+
* Get the next valid date this cron job will run at
|
|
671
|
+
*
|
|
672
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
673
|
+
* @since 1.3.17
|
|
674
|
+
* @version 1.3.17
|
|
675
|
+
*
|
|
676
|
+
* @param {Date} current_date
|
|
677
|
+
* @param {Boolean} add_one_second When false, the current date might be returned
|
|
678
|
+
*
|
|
679
|
+
* @return {Date}
|
|
680
|
+
*/
|
|
681
|
+
CronExpression.setMethod(function getNextDate(current_date, add_one_second = true) {
|
|
682
|
+
|
|
683
|
+
if (!current_date) {
|
|
684
|
+
current_date = new Date();
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
let result = current_date.clone();
|
|
688
|
+
|
|
689
|
+
if (add_one_second) {
|
|
690
|
+
result.add(1, 'second');
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
let last_ts = result.getTime(),
|
|
694
|
+
current_ts = last_ts;
|
|
695
|
+
|
|
696
|
+
do {
|
|
697
|
+
|
|
698
|
+
let modified = this._modifyDate(result);
|
|
699
|
+
|
|
700
|
+
if (modified) {
|
|
701
|
+
current_ts = result.getTime();
|
|
702
|
+
|
|
703
|
+
if (current_ts <= last_ts) {
|
|
704
|
+
return false;
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
last_ts = current_ts;
|
|
708
|
+
} else if (modified == null) {
|
|
709
|
+
break;
|
|
710
|
+
} else if (modified === false) {
|
|
711
|
+
// Failed to get a next date!
|
|
712
|
+
return false;
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
// Simple infinite loop fix
|
|
716
|
+
if (result.getFullYear() > 2099) {
|
|
717
|
+
return false;
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
} while (true);
|
|
721
|
+
|
|
722
|
+
return result;
|
|
723
|
+
});
|
|
724
|
+
|
|
725
|
+
/**
|
|
726
|
+
* Do only 1 modification. As soon as that happens, return.
|
|
727
|
+
*
|
|
728
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
729
|
+
* @since 1.3.17
|
|
730
|
+
* @version 1.3.17
|
|
731
|
+
*
|
|
732
|
+
* @param {Date} date
|
|
733
|
+
* @param {Object} config
|
|
734
|
+
* @param {String} unit
|
|
735
|
+
*/
|
|
736
|
+
CronExpression.setMethod(function _modifyDate(date) {
|
|
737
|
+
|
|
738
|
+
const parsed = this.parsed;
|
|
739
|
+
|
|
740
|
+
let result = this.modifyDate(date, parsed.second, 'second');
|
|
741
|
+
|
|
742
|
+
if (result != null) {
|
|
743
|
+
return result;
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
result = this.modifyDate(date, parsed.minute, 'minute');
|
|
747
|
+
|
|
748
|
+
if (result != null) {
|
|
749
|
+
return result;
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
result = this.modifyDate(date, parsed.hour, 'hour');
|
|
753
|
+
|
|
754
|
+
if (result != null) {
|
|
755
|
+
return result;
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
result = this.modifyDate(date, parsed.day_of_month, 'day_of_month');
|
|
759
|
+
|
|
760
|
+
if (result != null) {
|
|
761
|
+
return result;
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
result = this.modifyDate(date, parsed.day_of_week, 'day_of_week');
|
|
765
|
+
|
|
766
|
+
if (result != null) {
|
|
767
|
+
return result;
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
result = this.modifyDate(date, parsed.month, 'month');
|
|
771
|
+
|
|
772
|
+
if (result != null) {
|
|
773
|
+
return result;
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
result = this.modifyDate(date, parsed.year, 'year');
|
|
777
|
+
|
|
778
|
+
if (result != null) {
|
|
779
|
+
return result;
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
return null;
|
|
783
|
+
});
|
|
784
|
+
|
|
785
|
+
/**
|
|
786
|
+
* Potentially modify the given date (in place)
|
|
787
|
+
* using the field info & the given unit of time
|
|
788
|
+
*
|
|
789
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
790
|
+
* @since 1.3.17
|
|
791
|
+
* @version 1.3.17
|
|
792
|
+
*
|
|
793
|
+
* @param {Date} date
|
|
794
|
+
* @param {Object} config
|
|
795
|
+
* @param {String} unit
|
|
796
|
+
*/
|
|
797
|
+
CronExpression.setMethod(function modifyDate(date, config, unit) {
|
|
798
|
+
|
|
799
|
+
if (unit == 'day_of_month') {
|
|
800
|
+
unit = 'day';
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
if (config.all) {
|
|
804
|
+
return;
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
if (config.omit) {
|
|
808
|
+
|
|
809
|
+
if (unit == 'second' && date.getSeconds() > 0) {
|
|
810
|
+
date.add(1, 'minute');
|
|
811
|
+
date.startOf('minute');
|
|
812
|
+
return true;
|
|
813
|
+
} else {
|
|
814
|
+
return null;
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
if (config.values) {
|
|
819
|
+
return this.modifyDateUsingValues(date, config, unit);
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
if (config.ranges) {
|
|
823
|
+
return this.modifyDateUsingRanges(date, config, unit);
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
if (config.steps) {
|
|
827
|
+
return this.modifyDateUsingSteps(date, config, unit);
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
if (config.nthDays) {
|
|
831
|
+
return this.modifyDateUsingNthDays(date, config, unit);
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
if (config.lastDays) {
|
|
835
|
+
return this.modifyDateUsingLastDays(date, config, unit);
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
if (config.lastWeekday) {
|
|
839
|
+
return this.modifyDateUsingLastWeekday(date, config, unit);
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
if (config.lastDay) {
|
|
843
|
+
return this.modifyDateUsingLastDay(date, config, unit);
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
return null;
|
|
847
|
+
});
|
|
848
|
+
|
|
849
|
+
/**
|
|
850
|
+
* Potentially modify the given date (in place)
|
|
851
|
+
* using the given values of the parsed field expression.
|
|
852
|
+
*
|
|
853
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
854
|
+
* @since 1.3.17
|
|
855
|
+
* @version 1.3.17
|
|
856
|
+
*
|
|
857
|
+
* @param {Date} date The date to modify in-place
|
|
858
|
+
* @param {Object} config The parsed Cron field expression
|
|
859
|
+
* @param {String} unit The unit of time (day, month, second, hour, ...)
|
|
860
|
+
*
|
|
861
|
+
* @return {Boolean|null} True if the date was modified, false if it failed, null if it wasn't modified
|
|
862
|
+
*/
|
|
863
|
+
CronExpression.setMethod(function modifyDateUsingValues(date, config, unit) {
|
|
864
|
+
|
|
865
|
+
// Get all the allowed values for this unit
|
|
866
|
+
const allowed_values = config.values;
|
|
867
|
+
|
|
868
|
+
// Get the current amount of the given unit of the given date
|
|
869
|
+
let current_amount = this.getUnit(date, unit);
|
|
870
|
+
|
|
871
|
+
// If the current value is allowed, do nothing!
|
|
872
|
+
if (allowed_values.indexOf(current_amount) > -1) {
|
|
873
|
+
return;
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
// Get the max allowed amount of the current unit of the current date
|
|
877
|
+
// (Once this amount has been reached, the value should loop around)
|
|
878
|
+
let max_amount = this.getUnitMax(unit, date);
|
|
879
|
+
let min_amount = this.getUnitMin(unit, date);
|
|
880
|
+
|
|
881
|
+
// We're going to look for the smallest change to reach the next allowed value.
|
|
882
|
+
// Initial value is positive infinity, so any first change will be smaller.
|
|
883
|
+
let smallest_change = Infinity;
|
|
884
|
+
|
|
885
|
+
for (let value of allowed_values) {
|
|
886
|
+
|
|
887
|
+
let change = 0;
|
|
888
|
+
|
|
889
|
+
if (current_amount < value) {
|
|
890
|
+
// The next allowed value is in the future (without looping around)
|
|
891
|
+
// so this will be a simple change
|
|
892
|
+
change = value - current_amount;
|
|
893
|
+
} else if (current_amount > value) {
|
|
894
|
+
|
|
895
|
+
// The next allowed value is smaller than the current one,
|
|
896
|
+
// so this probably means we'll have to loop around
|
|
897
|
+
// (Only years can't loop around)
|
|
898
|
+
|
|
899
|
+
change = (max_amount - current_amount) + value;
|
|
900
|
+
|
|
901
|
+
// If the minimum amount is 0, we'll have to add 1 to the change
|
|
902
|
+
if (min_amount == 0) {
|
|
903
|
+
change += 1;
|
|
904
|
+
}
|
|
905
|
+
} else {
|
|
906
|
+
// Shouldn't happen, but ignore it anyway
|
|
907
|
+
continue;
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
// If the current calculated change is smaller than the previous one,
|
|
911
|
+
// it'll become the new smallest change
|
|
912
|
+
if (change < smallest_change) {
|
|
913
|
+
smallest_change = change;
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
// If the smalles change is not finite, no allowed values were found
|
|
918
|
+
if (!isFinite(smallest_change)) {
|
|
919
|
+
return false;
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
// Actually add the smallest change amount to the date
|
|
923
|
+
this.addUnitToDate(date, unit, smallest_change);
|
|
924
|
+
|
|
925
|
+
return true;
|
|
926
|
+
});
|
|
927
|
+
|
|
928
|
+
/**
|
|
929
|
+
* Potentially modify the given date (in place)
|
|
930
|
+
* using the given ranges of the field config
|
|
931
|
+
*
|
|
932
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
933
|
+
* @since 1.3.17
|
|
934
|
+
* @version 1.3.17
|
|
935
|
+
*
|
|
936
|
+
* @param {Date} date
|
|
937
|
+
* @param {Object} config
|
|
938
|
+
* @param {String} unit
|
|
939
|
+
*/
|
|
940
|
+
CronExpression.setMethod(function modifyDateUsingRanges(date, config, unit) {
|
|
941
|
+
|
|
942
|
+
// Get the current amount of the given unit of the given date
|
|
943
|
+
let current_amount = this.getUnit(date, unit);
|
|
944
|
+
|
|
945
|
+
// Get the max amount of the current date
|
|
946
|
+
// (Once this amount has been reached, the value will loop around)
|
|
947
|
+
let max_amount = this.getUnitMax(unit, date);
|
|
948
|
+
let min_amount = this.getUnitMin(unit, date);
|
|
949
|
+
|
|
950
|
+
// First see if the current unit is allowed by any of the ranges
|
|
951
|
+
for (let range of config.ranges) {
|
|
952
|
+
if (current_amount >= range.from && current_amount <= range.to) {
|
|
953
|
+
// It's allowed!
|
|
954
|
+
return null;
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
// If not, we need to calculate the change to reach the next allowed value.
|
|
959
|
+
// We'll do this for each range. The smallest change will be added to the date.
|
|
960
|
+
let smallest_change = Infinity;
|
|
961
|
+
|
|
962
|
+
for (let range of config.ranges) {
|
|
963
|
+
|
|
964
|
+
let change = 0;
|
|
965
|
+
|
|
966
|
+
if (current_amount < range.from) {
|
|
967
|
+
change = range.from - current_amount;
|
|
968
|
+
} else if (current_amount > range.to) {
|
|
969
|
+
|
|
970
|
+
change = (max_amount - current_amount) + range.from;
|
|
971
|
+
|
|
972
|
+
// If the minimum amount is 0, we'll have to add 1 to the change
|
|
973
|
+
if (min_amount == 0) {
|
|
974
|
+
change += 1;
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
} else {
|
|
978
|
+
return false;
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
if (change < smallest_change) {
|
|
982
|
+
smallest_change = change;
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
this.addUnitToDate(date, unit, smallest_change);
|
|
987
|
+
|
|
988
|
+
return true;
|
|
989
|
+
});
|
|
990
|
+
|
|
991
|
+
|
|
992
|
+
/**
|
|
993
|
+
* Potentially modify the given date (in place)
|
|
994
|
+
* using the given steps of the field config.
|
|
995
|
+
* Steps can be like "10-30/5"
|
|
996
|
+
*
|
|
997
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
998
|
+
* @since 1.3.17
|
|
999
|
+
* @version 1.3.17
|
|
1000
|
+
*
|
|
1001
|
+
* @param {Date} date
|
|
1002
|
+
* @param {Object} config
|
|
1003
|
+
* @param {String} unit
|
|
1004
|
+
*/
|
|
1005
|
+
CronExpression.setMethod(function modifyDateUsingSteps(date, config, unit) {
|
|
1006
|
+
|
|
1007
|
+
// Get the current amount of the given unit of the given date
|
|
1008
|
+
let current_amount = this.getUnit(date, unit);
|
|
1009
|
+
|
|
1010
|
+
// Get the max amount of the current date
|
|
1011
|
+
// (Once this amount has been reached, the value will loop around)
|
|
1012
|
+
let max_amount = this.getUnitMax(unit, date);
|
|
1013
|
+
let min_amount = this.getUnitMin(unit, date);
|
|
1014
|
+
|
|
1015
|
+
// First see if the current unit is allowed by any of the steps
|
|
1016
|
+
for (let step of config.steps) {
|
|
1017
|
+
|
|
1018
|
+
let to = step.to;
|
|
1019
|
+
|
|
1020
|
+
if (to == 7 && unit == 'day_of_week') {
|
|
1021
|
+
to = 6;
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
if (current_amount >= step.from && current_amount <= to) {
|
|
1025
|
+
if ((current_amount - step.from) % step.step == 0) {
|
|
1026
|
+
// It's allowed!
|
|
1027
|
+
return null;
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
// If not, we need to calculate the change to reach the next allowed value.
|
|
1033
|
+
// We'll do this for each step. The smallest change will be added to the date.
|
|
1034
|
+
let smallest_change = Infinity;
|
|
1035
|
+
|
|
1036
|
+
for (let step of config.steps) {
|
|
1037
|
+
|
|
1038
|
+
let change = 0;
|
|
1039
|
+
|
|
1040
|
+
let to = step.to;
|
|
1041
|
+
|
|
1042
|
+
if (to == 7 && unit == 'day_of_week') {
|
|
1043
|
+
to = 6;
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
if (current_amount < step.from) {
|
|
1047
|
+
change = step.from - current_amount;
|
|
1048
|
+
} else if (current_amount >= to) {
|
|
1049
|
+
|
|
1050
|
+
change = (max_amount - current_amount) + step.from;
|
|
1051
|
+
|
|
1052
|
+
// If the minimum amount is 0, we'll have to add 1 to the change
|
|
1053
|
+
if (min_amount == 0) {
|
|
1054
|
+
change += 1;
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
} else {
|
|
1058
|
+
change = step.step - ((current_amount - step.from) % step.step);
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
if (change < smallest_change) {
|
|
1062
|
+
smallest_change = change;
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
this.addUnitToDate(date, unit, smallest_change);
|
|
1067
|
+
|
|
1068
|
+
return true;
|
|
1069
|
+
});
|
|
1070
|
+
|
|
1071
|
+
/**
|
|
1072
|
+
* Modify dates using Nth days
|
|
1073
|
+
*
|
|
1074
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
1075
|
+
* @since 1.3.17
|
|
1076
|
+
* @version 1.3.17
|
|
1077
|
+
*
|
|
1078
|
+
* @param {Date} date The date to modify in-place
|
|
1079
|
+
* @param {Object} config The parsed Cron field expression
|
|
1080
|
+
* @param {String} unit The unit of time. Should always be day_of_week
|
|
1081
|
+
*
|
|
1082
|
+
* @return {Boolean|null} True if the date was modified, false if it failed, null if it wasn't modified
|
|
1083
|
+
*/
|
|
1084
|
+
CronExpression.setMethod(function modifyDateUsingNthDays(date, config, unit) {
|
|
1085
|
+
|
|
1086
|
+
// Get the current amount of the given unit of the given date
|
|
1087
|
+
let current_dow = this.getUnit(date, 'day_of_week');
|
|
1088
|
+
|
|
1089
|
+
// Calculate the current instance of the current day
|
|
1090
|
+
let current_instance = Math.ceil(date.getDate() / 7);
|
|
1091
|
+
|
|
1092
|
+
// First see if the current unit is allowed by any of the steps
|
|
1093
|
+
for (let entry of config.nthDays) {
|
|
1094
|
+
|
|
1095
|
+
if (current_dow != entry.day_of_week) {
|
|
1096
|
+
continue;
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
// If the instance also matches, it's already good!
|
|
1100
|
+
if (current_instance == entry.instance) {
|
|
1101
|
+
return null;
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
// Get the max amount of the current date
|
|
1106
|
+
// (Once this amount has been reached, the value will loop around)
|
|
1107
|
+
let max_amount = 6;
|
|
1108
|
+
|
|
1109
|
+
// If not, we need to calculate the change to reach the next allowed value.
|
|
1110
|
+
// We'll do this for each step. The smallest change will be added to the date.
|
|
1111
|
+
let smallest_change = Infinity;
|
|
1112
|
+
|
|
1113
|
+
for (let entry of config.nthDays) {
|
|
1114
|
+
|
|
1115
|
+
let change = 0;
|
|
1116
|
+
|
|
1117
|
+
// First: let's calculate the needed change to get the correct day_of_week
|
|
1118
|
+
// (It might even already be correct and stay at 0!)
|
|
1119
|
+
if (current_dow <= entry.day_of_week) {
|
|
1120
|
+
change = entry.day_of_week - current_dow;
|
|
1121
|
+
} else if (current_dow > entry.day_of_week) {
|
|
1122
|
+
change = (max_amount - current_dow) + entry.day_of_week + 1;
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
// Now let's calculate the change to get the correct instance
|
|
1126
|
+
let test = date.clone().add(change, 'days'),
|
|
1127
|
+
instance = Math.ceil(test.getDate() / 7);
|
|
1128
|
+
|
|
1129
|
+
if (instance != entry.instance) {
|
|
1130
|
+
if (instance > entry.instance) {
|
|
1131
|
+
// The newly calculated date is getting further away from the wanted instance,
|
|
1132
|
+
// so we just have to keep on going. The next iteration will take care of that
|
|
1133
|
+
|
|
1134
|
+
// @TODO: maybe make it go to the start of the next month?
|
|
1135
|
+
|
|
1136
|
+
// Make sure it goes ahead by at least 1 more day
|
|
1137
|
+
change += 1;
|
|
1138
|
+
} else {
|
|
1139
|
+
change += (7 * (entry.instance - instance));
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
if (change > 0 && change < smallest_change) {
|
|
1144
|
+
smallest_change = change;
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1148
|
+
if (!isFinite(smallest_change)) {
|
|
1149
|
+
return false;
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
this.addUnitToDate(date, 'day_of_week', smallest_change);
|
|
1153
|
+
|
|
1154
|
+
return true;
|
|
1155
|
+
});
|
|
1156
|
+
|
|
1157
|
+
/**
|
|
1158
|
+
* Modify dates using Last-days
|
|
1159
|
+
*
|
|
1160
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
1161
|
+
* @since 1.3.17
|
|
1162
|
+
* @version 1.3.17
|
|
1163
|
+
*
|
|
1164
|
+
* @param {Date} date The date to modify in-place
|
|
1165
|
+
* @param {Object} config The parsed Cron field expression
|
|
1166
|
+
* @param {String} unit The unit of time. Should always be day_of_week
|
|
1167
|
+
*
|
|
1168
|
+
* @return {Boolean|null} True if the date was modified, false if it failed, null if it wasn't modified
|
|
1169
|
+
*/
|
|
1170
|
+
CronExpression.setMethod(function modifyDateUsingLastDays(date, config, unit) {
|
|
1171
|
+
|
|
1172
|
+
let current_date = date.getDate();
|
|
1173
|
+
let smallest_change = Infinity;
|
|
1174
|
+
|
|
1175
|
+
// First see if the current unit is allowed by any of the steps
|
|
1176
|
+
for (let day_of_week of config.lastDays) {
|
|
1177
|
+
|
|
1178
|
+
let wanted_date = this.getDayOfLastWantedDayOfWeek(date, day_of_week);
|
|
1179
|
+
|
|
1180
|
+
if (current_date == wanted_date) {
|
|
1181
|
+
return null;
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
let change;
|
|
1185
|
+
|
|
1186
|
+
if (wanted_date > current_date) {
|
|
1187
|
+
change = wanted_date - current_date;
|
|
1188
|
+
} else {
|
|
1189
|
+
continue;
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
if (change != null && change < smallest_change) {
|
|
1193
|
+
smallest_change = change;
|
|
1194
|
+
}
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
if (!isFinite(smallest_change)) {
|
|
1198
|
+
smallest_change = 1;
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
this.addUnitToDate(date, 'day_of_week', smallest_change);
|
|
1202
|
+
|
|
1203
|
+
return true;
|
|
1204
|
+
});
|
|
1205
|
+
|
|
1206
|
+
/**
|
|
1207
|
+
* Modify dates using Last-weekday
|
|
1208
|
+
*
|
|
1209
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
1210
|
+
* @since 1.3.17
|
|
1211
|
+
* @version 1.3.17
|
|
1212
|
+
*
|
|
1213
|
+
* @param {Date} date The date to modify in-place
|
|
1214
|
+
* @param {Object} config The parsed Cron field expression
|
|
1215
|
+
* @param {String} unit The unit of time. Should always be day_of_week
|
|
1216
|
+
*
|
|
1217
|
+
* @return {Boolean|null} True if the date was modified, false if it failed, null if it wasn't modified
|
|
1218
|
+
*/
|
|
1219
|
+
CronExpression.setMethod(function modifyDateUsingLastWeekday(date, config, unit) {
|
|
1220
|
+
|
|
1221
|
+
let last_weekday = this.getDayOfLastWeekday(date),
|
|
1222
|
+
current_day = date.getDate();
|
|
1223
|
+
|
|
1224
|
+
if (last_weekday == current_day) {
|
|
1225
|
+
return null;
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1228
|
+
this.addUnitToDate(date, 'day', last_weekday - current_day);
|
|
1229
|
+
|
|
1230
|
+
return true;
|
|
1231
|
+
});
|
|
1232
|
+
|
|
1233
|
+
/**
|
|
1234
|
+
* Modify dates using Last-day
|
|
1235
|
+
*
|
|
1236
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
1237
|
+
* @since 1.3.17
|
|
1238
|
+
* @version 1.3.17
|
|
1239
|
+
*
|
|
1240
|
+
* @param {Date} date The date to modify in-place
|
|
1241
|
+
* @param {Object} config The parsed Cron field expression
|
|
1242
|
+
* @param {String} unit The unit of time. Should always be day_of_week
|
|
1243
|
+
*
|
|
1244
|
+
* @return {Boolean|null} True if the date was modified, false if it failed, null if it wasn't modified
|
|
1245
|
+
*/
|
|
1246
|
+
CronExpression.setMethod(function modifyDateUsingLastDay(date, config, unit) {
|
|
1247
|
+
|
|
1248
|
+
let last_day = this.getUnitMax('day', date),
|
|
1249
|
+
current_day = date.getDate();
|
|
1250
|
+
|
|
1251
|
+
if (last_day == current_day) {
|
|
1252
|
+
return null;
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1255
|
+
this.addUnitToDate(date, 'day', last_day - current_day);
|
|
1256
|
+
|
|
1257
|
+
return true;
|
|
1258
|
+
});
|
|
1259
|
+
|
|
1260
|
+
/**
|
|
1261
|
+
* Add the given amount of units to the date.
|
|
1262
|
+
*
|
|
1263
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
1264
|
+
* @since 1.3.17
|
|
1265
|
+
* @version 1.3.17
|
|
1266
|
+
*
|
|
1267
|
+
* @param {Date} date
|
|
1268
|
+
* @param {Object} config
|
|
1269
|
+
* @param {String} unit
|
|
1270
|
+
*/
|
|
1271
|
+
CronExpression.setMethod(function addUnitToDate(date, unit, amount) {
|
|
1272
|
+
|
|
1273
|
+
if (unit == 'day_of_month') {
|
|
1274
|
+
unit = 'day';
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
if (unit == 'day_of_week') {
|
|
1278
|
+
unit = 'day';
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1281
|
+
// Now add the smallest change to the date
|
|
1282
|
+
date.add(amount, unit);
|
|
1283
|
+
|
|
1284
|
+
// And go to the start of this unit
|
|
1285
|
+
date.startOf(unit);
|
|
1286
|
+
});
|
|
1287
|
+
|
|
1288
|
+
|
|
1289
|
+
/**
|
|
1290
|
+
* Get the date of the last weekday of the current month
|
|
1291
|
+
*
|
|
1292
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
1293
|
+
* @since 1.3.17
|
|
1294
|
+
* @version 1.3.17
|
|
1295
|
+
*
|
|
1296
|
+
* @param {Date} date
|
|
1297
|
+
*
|
|
1298
|
+
* @return {Number} The date of the last weekday
|
|
1299
|
+
*/
|
|
1300
|
+
CronExpression.setMethod(function getDayOfLastWeekday(date) {
|
|
1301
|
+
|
|
1302
|
+
let last_date = this.getUnitMax('day', date);
|
|
1303
|
+
|
|
1304
|
+
// Create a new date that is the last day of the month of the current month
|
|
1305
|
+
let end_of_month = new Date(date.getFullYear(), date.getMonth(), last_date, 23, 59, 0);
|
|
1306
|
+
|
|
1307
|
+
// Day of week in JavaScript Date are from 0-6
|
|
1308
|
+
// Sunday is 0, Monday is 1, ..., and Saturday is 6
|
|
1309
|
+
let last_day = end_of_month.getDay();
|
|
1310
|
+
|
|
1311
|
+
if (last_day === 6) {
|
|
1312
|
+
// If it's Saturday (6), go back 1 day
|
|
1313
|
+
last_date -= 1;
|
|
1314
|
+
} else if (last_day === 0) {
|
|
1315
|
+
// If it's Sunday (0), go back 2 days
|
|
1316
|
+
last_date -= 2;
|
|
1317
|
+
}
|
|
1318
|
+
|
|
1319
|
+
return last_date;
|
|
1320
|
+
});
|
|
1321
|
+
|
|
1322
|
+
/**
|
|
1323
|
+
* Get the date of the last type of weekday of the current month
|
|
1324
|
+
*
|
|
1325
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
1326
|
+
* @since 1.3.17
|
|
1327
|
+
* @version 1.3.17
|
|
1328
|
+
*
|
|
1329
|
+
* @param {Date} date
|
|
1330
|
+
* @param {Number} day_of_week
|
|
1331
|
+
*
|
|
1332
|
+
* @return {Number} The date of the last weekday
|
|
1333
|
+
*/
|
|
1334
|
+
CronExpression.setMethod(function getDayOfLastWantedDayOfWeek(date, wanted_day_of_week) {
|
|
1335
|
+
|
|
1336
|
+
let last_date = this.getUnitMax('day', date);
|
|
1337
|
+
|
|
1338
|
+
// Create a new date that is the last day of the month of the current month
|
|
1339
|
+
let end_of_month = new Date(date.getFullYear(), date.getMonth(), last_date, 23, 59, 0);
|
|
1340
|
+
|
|
1341
|
+
// Get the last day_of_week of the month
|
|
1342
|
+
let last_day_of_week = end_of_month.getDay();
|
|
1343
|
+
|
|
1344
|
+
// Calculate difference
|
|
1345
|
+
let diff = last_day_of_week - wanted_day_of_week;
|
|
1346
|
+
|
|
1347
|
+
if (diff < 0) {
|
|
1348
|
+
diff += 7;
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1351
|
+
// Calculate the date of the last wanted day_of_week
|
|
1352
|
+
let result = end_of_month.getDate() - diff;
|
|
1353
|
+
|
|
1354
|
+
return result;
|
|
1355
|
+
});
|
|
1356
|
+
|
|
1357
|
+
/**
|
|
1358
|
+
* Get a specific unit of the given date
|
|
1359
|
+
*
|
|
1360
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
1361
|
+
* @since 1.3.17
|
|
1362
|
+
* @version 1.3.17
|
|
1363
|
+
*
|
|
1364
|
+
* @param {Date} date
|
|
1365
|
+
* @param {String} unit
|
|
1366
|
+
*/
|
|
1367
|
+
CronExpression.setMethod(function getUnit(date, unit) {
|
|
1368
|
+
|
|
1369
|
+
switch (unit) {
|
|
1370
|
+
case 'year':
|
|
1371
|
+
return date.getFullYear();
|
|
1372
|
+
case 'month':
|
|
1373
|
+
// In our cron system, months start at 1.
|
|
1374
|
+
// Because they start at 0 in JavaScript, we add 1.
|
|
1375
|
+
return date.getMonth() + 1;
|
|
1376
|
+
case 'day_of_month':
|
|
1377
|
+
case 'day':
|
|
1378
|
+
return date.getDate();
|
|
1379
|
+
case 'hour':
|
|
1380
|
+
return date.getHours();
|
|
1381
|
+
case 'minute':
|
|
1382
|
+
return date.getMinutes();
|
|
1383
|
+
case 'second':
|
|
1384
|
+
return date.getSeconds();
|
|
1385
|
+
case 'day_of_week':
|
|
1386
|
+
return date.getDay();
|
|
1387
|
+
}
|
|
1388
|
+
});
|
|
1389
|
+
|
|
1390
|
+
/**
|
|
1391
|
+
* Get the maximum allowed value of the given unit
|
|
1392
|
+
*
|
|
1393
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
1394
|
+
* @since 1.3.17
|
|
1395
|
+
* @version 1.3.17
|
|
1396
|
+
*
|
|
1397
|
+
* @param {String} unit
|
|
1398
|
+
*/
|
|
1399
|
+
CronExpression.setMethod(function getUnitMax(unit, of_date) {
|
|
1400
|
+
|
|
1401
|
+
// The `FIELD_INFO` object says the max amount of days in a week is 7,
|
|
1402
|
+
// but the `Date` object says it's 6, and that's what's important here.
|
|
1403
|
+
if (unit == 'day_of_week') {
|
|
1404
|
+
return 6;
|
|
1405
|
+
}
|
|
1406
|
+
|
|
1407
|
+
// The max amount of dates in a month depend on the month
|
|
1408
|
+
if (of_date && (unit == 'day_of_month' || unit == 'day')) {
|
|
1409
|
+
let month = of_date.getMonth() + 1;
|
|
1410
|
+
|
|
1411
|
+
let days_in_month;
|
|
1412
|
+
|
|
1413
|
+
switch (month) {
|
|
1414
|
+
case 2:
|
|
1415
|
+
days_in_month = 28;
|
|
1416
|
+
break;
|
|
1417
|
+
|
|
1418
|
+
case 4:
|
|
1419
|
+
case 6:
|
|
1420
|
+
case 9:
|
|
1421
|
+
case 11:
|
|
1422
|
+
days_in_month = 30;
|
|
1423
|
+
break;
|
|
1424
|
+
|
|
1425
|
+
default:
|
|
1426
|
+
days_in_month = 31;
|
|
1427
|
+
}
|
|
1428
|
+
|
|
1429
|
+
return days_in_month;
|
|
1430
|
+
}
|
|
1431
|
+
|
|
1432
|
+
const info = FIELD_INFO[unit];
|
|
1433
|
+
|
|
1434
|
+
return info?.max;
|
|
1435
|
+
});
|
|
1436
|
+
|
|
1437
|
+
/**
|
|
1438
|
+
* Get the minimum allowed value of the given unit
|
|
1439
|
+
*
|
|
1440
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
1441
|
+
* @since 1.3.17
|
|
1442
|
+
* @version 1.3.17
|
|
1443
|
+
*
|
|
1444
|
+
* @param {String} unit
|
|
1445
|
+
*/
|
|
1446
|
+
CronExpression.setMethod(function getUnitMin(unit, of_date) {
|
|
1447
|
+
const info = FIELD_INFO[unit];
|
|
1448
|
+
return info?.min;
|
|
1449
|
+
});
|
|
1450
|
+
|
|
1451
|
+
/**
|
|
1452
|
+
* Convert any aliases to their expected value
|
|
1453
|
+
*
|
|
1454
|
+
* @author Santhosh Kumar <brsanthu@gmail.com>
|
|
1455
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
1456
|
+
* @since 1.3.17
|
|
1457
|
+
* @version 1.3.17
|
|
1458
|
+
*/
|
|
1459
|
+
function unalias(field, value) {
|
|
1460
|
+
|
|
1461
|
+
if (!field) {
|
|
1462
|
+
return value;
|
|
1463
|
+
}
|
|
1464
|
+
|
|
1465
|
+
const info = FIELD_INFO[field];
|
|
1466
|
+
const unaliased = (info.alias || {})[value];
|
|
1467
|
+
return unaliased === undefined ? value : unaliased.toString();
|
|
1468
|
+
}
|
|
1469
|
+
|
|
1470
|
+
/**
|
|
1471
|
+
* Create a new error for the current expression
|
|
1472
|
+
*
|
|
1473
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
1474
|
+
* @since 1.3.17
|
|
1475
|
+
* @version 1.3.17
|
|
1476
|
+
*/
|
|
1477
|
+
CronExpression.setMethod(function createInvalidExpressionError(msg) {
|
|
1478
|
+
return new Error(`Invalid cron expression [${this.expression}]. ${msg}`);
|
|
1479
|
+
});
|
|
1480
|
+
|
|
1481
|
+
/**
|
|
1482
|
+
* Deduplicate an array
|
|
1483
|
+
*
|
|
1484
|
+
* @author Santhosh Kumar <brsanthu@gmail.com>
|
|
1485
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
1486
|
+
* @since 1.3.17
|
|
1487
|
+
* @version 1.3.17
|
|
1488
|
+
*/
|
|
1489
|
+
function dedupe(inArray, keySupplier = (it) => it) {
|
|
1490
|
+
const seen = new Set();
|
|
1491
|
+
const deduped = [];
|
|
1492
|
+
|
|
1493
|
+
inArray.forEach((x) => {
|
|
1494
|
+
const keyValue = keySupplier(x);
|
|
1495
|
+
if (!seen.has(keyValue)) {
|
|
1496
|
+
seen.add(keyValue);
|
|
1497
|
+
deduped.push(x);
|
|
1498
|
+
}
|
|
1499
|
+
});
|
|
1500
|
+
|
|
1501
|
+
return deduped;
|
|
1502
|
+
}
|