alchemymvc 1.3.15 → 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.
@@ -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
+ }