/
compute-next-run-at.ts
137 lines (122 loc) · 5.36 KB
/
compute-next-run-at.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
import * as parser from 'cron-parser';
import createDebugger from 'debug';
import humanInterval from 'human-interval';
import moment from 'moment-timezone';
// @ts-expect-error
import date from 'date.js';
import { Job } from '../job';
const debug = createDebugger('pulse:job');
export type ComputeNextRunAtMethod = () => Job;
/**
* Internal method used to compute next time a job should run and sets the proper values
* @name Job#computeNextRunAt
* @function
*/
export const computeNextRunAt: ComputeNextRunAtMethod = function (this: Job) {
const interval = this.attrs.repeatInterval;
const timezone = this.attrs.repeatTimezone;
const { repeatAt } = this.attrs;
const previousNextRunAt = this.attrs.nextRunAt || new Date();
this.attrs.nextRunAt = undefined;
const dateForTimezone = (date: Date): moment.Moment => {
const mdate: moment.Moment = moment(date);
if (timezone) {
mdate.tz(timezone);
}
return mdate;
};
/**
* Internal method that computes the interval
*/
const computeFromInterval = () => {
debug('[%s:%s] computing next run via interval [%s]', this.attrs.name, this.attrs._id, interval);
const dateNow = new Date();
let lastRun: Date = this.attrs.lastRunAt || dateNow;
let { startDate, endDate, skipDays } = this.attrs;
lastRun = dateForTimezone(lastRun).toDate();
const cronOptions: any = { currentDate: lastRun };
if (timezone) {
cronOptions.tz = timezone;
}
try {
let cronTime = parser.parseExpression(interval!, cronOptions);
let nextDate: Date | null = cronTime.next().toDate();
if (nextDate.getTime() === lastRun.getTime() || nextDate.getTime() <= previousNextRunAt.getTime()) {
// Handle cronTime giving back the same date for the next run time
cronOptions.currentDate = new Date(lastRun.getTime() + 1000);
cronTime = parser.parseExpression(interval!, cronOptions);
nextDate = cronTime.next().toDate();
}
// If start date is present, check if the nextDate should be larger or equal to startDate. If not set startDate as nextDate
if (startDate) {
startDate = moment.tz(moment(startDate).format('YYYY-MM-DD HH:mm'), timezone!).toDate();
if (startDate > nextDate) {
cronOptions.currentDate = startDate;
cronTime = parser.parseExpression(interval!, cronOptions);
nextDate = cronTime.next().toDate();
}
}
// If job has run in the past and skipDays is not null, add skipDays to nextDate
if (dateNow > lastRun && skipDays !== null) {
try {
nextDate = new Date(nextDate.getTime() + (humanInterval(skipDays) ?? 0));
} catch {}
}
// If endDate is less than the nextDate, set nextDate to null to stop the job from running further
if (endDate) {
const endDateDate: Date = moment.tz(moment(endDate).format('YYYY-MM-DD HH:mm'), timezone!).toDate();
if (nextDate > endDateDate) {
nextDate = null;
}
}
this.attrs.nextRunAt = nextDate;
debug('[%s:%s] nextRunAt set to [%s]', this.attrs.name, this.attrs._id, this.attrs.nextRunAt?.toISOString());
// Either `xo` linter or Node.js 8 stumble on this line if it isn't just ignored
} catch {
debug('[%s:%s] failed nextRunAt based on interval [%s]', this.attrs.name, this.attrs._id, interval);
// Nope, humanInterval then!
try {
if (!this.attrs.lastRunAt && humanInterval(interval)) {
this.attrs.nextRunAt = lastRun;
debug('[%s:%s] nextRunAt set to [%s]', this.attrs.name, this.attrs._id, this.attrs.nextRunAt.toISOString());
} else {
this.attrs.nextRunAt = new Date(lastRun.getTime() + (humanInterval(interval) ?? 0));
debug('[%s:%s] nextRunAt set to [%s]', this.attrs.name, this.attrs._id, this.attrs.nextRunAt.toISOString());
}
// Either `xo` linter or Node.js 8 stumble on this line if it isn't just ignored
} catch {}
} finally {
if (!this.attrs.nextRunAt?.getTime()) {
this.attrs.nextRunAt = undefined;
debug('[%s:%s] failed to calculate nextRunAt due to invalid repeat interval', this.attrs.name, this.attrs._id);
this.fail('failed to calculate nextRunAt due to invalid repeat interval');
}
}
};
/**
* Internal method to compute next run time from the repeat string
*/
const computeFromRepeatAt = () => {
const lastRun = this.attrs.lastRunAt || new Date();
const nextDate: Date = date(repeatAt);
// If you do not specify offset date for below test it will fail for ms
const offset = Date.now();
if (offset === date(repeatAt, offset).getTime()) {
this.attrs.nextRunAt = undefined;
debug('[%s:%s] failed to calculate repeatAt due to invalid format', this.attrs.name, this.attrs._id);
this.fail('failed to calculate repeatAt time due to invalid format');
} else if (nextDate.getTime() === lastRun.getTime()) {
this.attrs.nextRunAt = date('tomorrow at ', repeatAt);
debug('[%s:%s] nextRunAt set to [%s]', this.attrs.name, this.attrs._id, this.attrs.nextRunAt?.toISOString());
} else {
this.attrs.nextRunAt = date(repeatAt);
debug('[%s:%s] nextRunAt set to [%s]', this.attrs.name, this.attrs._id, this.attrs.nextRunAt?.toISOString());
}
};
if (interval) {
computeFromInterval();
} else if (repeatAt) {
computeFromRepeatAt();
}
return this;
};