191 lines
5.8 KiB
TypeScript
191 lines
5.8 KiB
TypeScript
import { Job } from ".";
|
|
import * as parser from "cron-parser";
|
|
import humanInterval from "human-interval";
|
|
import createDebugger from "debug";
|
|
import moment from "moment-timezone";
|
|
// @ts-expect-error
|
|
import date from "date.js";
|
|
|
|
const debug = createDebugger("agenda:job");
|
|
|
|
/**
|
|
* Internal method used to compute next time a job should run and sets the proper values
|
|
* @name Job#computeNextRunAt
|
|
* @function
|
|
*/
|
|
export const computeNextRunAt = function (this: Job): 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;
|
|
};
|