UNPKG

agenda

Version:

Light weight job scheduler for Node.js

192 lines (177 loc) 5.93 kB
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; };