Source code
Revision control
Copy as Markdown
Other Tools
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
/**
* Base monitor schedule.
*/
export class Schedule {
/**
* @param {string} type - "interval", "daily", or "weekly"
* @param {object} options - Schedule options, already normalized
*/
constructor(type, options) {
this.type = type;
Object.assign(this, options);
}
/**
* @param {object} schedule - Serialized schedule data.
* @returns {Schedule}
*/
static fromJSON(schedule) {
if (!schedule || typeof schedule !== "object") {
throw new Error("Monitor schedule is invalid.");
}
// if the schedule already has a getNextRunTime method, assume it's already a Schedule instance.
if (typeof schedule.getNextRunTime === "function") {
return schedule;
}
// create the appropriate subclass instance
switch (schedule.type) {
case "interval":
return IntervalSchedule.fromJSON(schedule);
case "daily":
return DailySchedule.fromJSON(schedule);
case "weekly":
return WeeklySchedule.fromJSON(schedule);
default:
throw new Error("Monitor schedule type is invalid.");
}
}
}
/**
* Monitor schedule that runs after a fixed interval.
*/
export class IntervalSchedule extends Schedule {
/**
* @param {number} hours - Interval in hours
*/
constructor(hours) {
if (!Number.isFinite(hours) || hours <= 0) {
throw new Error("Monitor interval schedule is invalid.");
}
super("interval", { hours });
}
/**
* @param {object} schedule - Serialized interval schedule data.
* @returns {IntervalSchedule}
*/
static fromJSON(schedule) {
return new IntervalSchedule(Number(schedule.hours));
}
getNextRunTime(lastRunTime) {
const now = new Date();
const lastRun = lastRunTime ? new Date(lastRunTime) : now;
return new Date(lastRun.getTime() + this.hours * 60 * 60 * 1000);
}
}
/**
* Monitor schedule that runs once per day.
*/
export class DailySchedule extends Schedule {
/**
* @param {number} hour - Hour of the day (0-23)
* @param {number} minute - Minute of the hour (0-59)
*/
constructor(hour, minute) {
validateClockHour(hour);
validateClockMinute(minute);
super("daily", { hour, minute });
}
/**
* @param {object} schedule - Serialized daily schedule data.
* @returns {DailySchedule}
*/
static fromJSON(schedule) {
return new DailySchedule(Number(schedule.hour), Number(schedule.minute));
}
getNextRunTime(lastRunTime) {
const now = new Date();
const lastRun = lastRunTime ? new Date(lastRunTime) : now;
const next = new Date(lastRun);
next.setHours(this.hour, this.minute, 0, 0);
// Only skip to the next day when today's target time has already passed.
if (next <= lastRun) {
next.setDate(next.getDate() + 1);
}
return next;
}
}
/**
* Monitor schedule that runs once per week.
*/
export class WeeklySchedule extends Schedule {
/**
* @param {number} weekday - Day of the week (0=Sunday, 6=Saturday)
* @param {number} hour - Hour of the day (0-23)
* @param {number} minute - Minute of the hour (0-59)
*/
constructor(weekday, hour, minute) {
validateWeekday(weekday);
validateClockHour(hour);
validateClockMinute(minute);
super("weekly", { weekday, hour, minute });
}
/**
* @param {object} schedule - Serialized weekly schedule data.
* @returns {WeeklySchedule}
*/
static fromJSON(schedule) {
return new WeeklySchedule(
Number(schedule.weekday),
Number(schedule.hour),
Number(schedule.minute)
);
}
/**
* @param {string} lastRunTime - ISO string of the last run time
* @returns {Date} - The next run time as a Date object
*/
getNextRunTime(lastRunTime) {
const now = new Date();
const lastRun = lastRunTime ? new Date(lastRunTime) : now;
const next = new Date(lastRun);
next.setHours(this.hour, this.minute, 0, 0);
let daysAhead = (this.weekday - next.getDay() + 7) % 7;
// When today is the target weekday, only skip a full week if the target
// time has already passed.
if (daysAhead === 0 && next <= lastRun) {
daysAhead = 7;
}
next.setDate(next.getDate() + daysAhead);
return next;
}
}
// Helper functions for validating schedule parameters for clock times
function validateClockHour(hour) {
if (!Number.isInteger(hour) || hour < 0 || hour > 23) {
throw new Error(`Monitor hour ${hour} is invalid.`);
}
}
function validateClockMinute(minute) {
if (!Number.isInteger(minute) || minute < 0 || minute > 59) {
throw new Error(`Monitor minute ${minute} is invalid.`);
}
}
function validateWeekday(weekday) {
if (!Number.isInteger(weekday) || weekday < 0 || weekday > 6) {
throw new Error(`Monitor weekday ${weekday} is invalid.`);
}
}