cron-parser
Version:
Node.js library for parsing crontab instructions
498 lines (497 loc) • 13.9 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.CronDate = exports.DAYS_IN_MONTH = exports.DateMathOp = exports.TimeUnit = void 0;
const luxon_1 = require("luxon");
var TimeUnit;
(function (TimeUnit) {
TimeUnit["Second"] = "Second";
TimeUnit["Minute"] = "Minute";
TimeUnit["Hour"] = "Hour";
TimeUnit["Day"] = "Day";
TimeUnit["Month"] = "Month";
TimeUnit["Year"] = "Year";
})(TimeUnit || (exports.TimeUnit = TimeUnit = {}));
var DateMathOp;
(function (DateMathOp) {
DateMathOp["Add"] = "Add";
DateMathOp["Subtract"] = "Subtract";
})(DateMathOp || (exports.DateMathOp = DateMathOp = {}));
exports.DAYS_IN_MONTH = Object.freeze([31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]);
/**
* CronDate class that wraps the Luxon DateTime object to provide
* a consistent API for working with dates and times in the context of cron.
*/
class CronDate {
#date;
#dstStart = null;
#dstEnd = null;
/**
* Maps the verb to the appropriate method
*/
#verbMap = {
add: {
[TimeUnit.Year]: this.addYear.bind(this),
[TimeUnit.Month]: this.addMonth.bind(this),
[TimeUnit.Day]: this.addDay.bind(this),
[TimeUnit.Hour]: this.addHour.bind(this),
[TimeUnit.Minute]: this.addMinute.bind(this),
[TimeUnit.Second]: this.addSecond.bind(this),
},
subtract: {
[TimeUnit.Year]: this.subtractYear.bind(this),
[TimeUnit.Month]: this.subtractMonth.bind(this),
[TimeUnit.Day]: this.subtractDay.bind(this),
[TimeUnit.Hour]: this.subtractHour.bind(this),
[TimeUnit.Minute]: this.subtractMinute.bind(this),
[TimeUnit.Second]: this.subtractSecond.bind(this),
},
};
/**
* Constructs a new CronDate instance.
* @param {CronDate | Date | number | string} [timestamp] - The timestamp to initialize the CronDate with.
* @param {string} [tz] - The timezone to use for the CronDate.
*/
constructor(timestamp, tz) {
const dateOpts = { zone: tz };
// Initialize the internal DateTime object based on the type of timestamp provided.
if (!timestamp) {
this.#date = luxon_1.DateTime.local();
}
else if (timestamp instanceof CronDate) {
this.#date = timestamp.#date;
this.#dstStart = timestamp.#dstStart;
this.#dstEnd = timestamp.#dstEnd;
}
else if (timestamp instanceof Date) {
this.#date = luxon_1.DateTime.fromJSDate(timestamp, dateOpts);
}
else if (typeof timestamp === 'number') {
this.#date = luxon_1.DateTime.fromMillis(timestamp, dateOpts);
}
else {
this.#date = luxon_1.DateTime.fromISO(timestamp, dateOpts);
this.#date.isValid || (this.#date = luxon_1.DateTime.fromRFC2822(timestamp, dateOpts));
this.#date.isValid || (this.#date = luxon_1.DateTime.fromSQL(timestamp, dateOpts));
this.#date.isValid || (this.#date = luxon_1.DateTime.fromFormat(timestamp, 'EEE, d MMM yyyy HH:mm:ss', dateOpts));
}
// Check for valid DateTime and throw an error if not valid.
if (!this.#date.isValid) {
throw new Error(`CronDate: unhandled timestamp: ${timestamp}`);
}
// Set the timezone if it is provided and different from the current zone.
if (tz && tz !== this.#date.zoneName) {
this.#date = this.#date.setZone(tz);
}
}
/**
* Determines if the given year is a leap year.
* @param {number} year - The year to check
* @returns {boolean} - True if the year is a leap year, false otherwise
* @private
*/
static #isLeapYear(year) {
return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;
}
/**
* Returns daylight savings start time.
* @returns {number | null}
*/
get dstStart() {
return this.#dstStart;
}
/**
* Sets daylight savings start time.
* @param {number | null} value
*/
set dstStart(value) {
this.#dstStart = value;
}
/**
* Returns daylight savings end time.
* @returns {number | null}
*/
get dstEnd() {
return this.#dstEnd;
}
/**
* Sets daylight savings end time.
* @param {number | null} value
*/
set dstEnd(value) {
this.#dstEnd = value;
}
/**
* Adds one year to the current CronDate.
*/
addYear() {
this.#date = this.#date.plus({ years: 1 });
}
/**
* Adds one month to the current CronDate.
*/
addMonth() {
this.#date = this.#date.plus({ months: 1 }).startOf('month');
}
/**
* Adds one day to the current CronDate.
*/
addDay() {
this.#date = this.#date.plus({ days: 1 }).startOf('day');
}
/**
* Adds one hour to the current CronDate.
*/
addHour() {
this.#date = this.#date.plus({ hours: 1 }).startOf('hour');
}
/**
* Adds one minute to the current CronDate.
*/
addMinute() {
this.#date = this.#date.plus({ minutes: 1 }).startOf('minute');
}
/**
* Adds one second to the current CronDate.
*/
addSecond() {
this.#date = this.#date.plus({ seconds: 1 });
}
/**
* Subtracts one year from the current CronDate.
*/
subtractYear() {
this.#date = this.#date.minus({ years: 1 });
}
/**
* Subtracts one month from the current CronDate.
* If the month is 1, it will subtract one year instead.
*/
subtractMonth() {
this.#date = this.#date.minus({ months: 1 }).endOf('month').startOf('second');
}
/**
* Subtracts one day from the current CronDate.
* If the day is 1, it will subtract one month instead.
*/
subtractDay() {
this.#date = this.#date.minus({ days: 1 }).endOf('day').startOf('second');
}
/**
* Subtracts one hour from the current CronDate.
* If the hour is 0, it will subtract one day instead.
*/
subtractHour() {
this.#date = this.#date.minus({ hours: 1 }).endOf('hour').startOf('second');
}
/**
* Subtracts one minute from the current CronDate.
* If the minute is 0, it will subtract one hour instead.
*/
subtractMinute() {
this.#date = this.#date.minus({ minutes: 1 }).endOf('minute').startOf('second');
}
/**
* Subtracts one second from the current CronDate.
* If the second is 0, it will subtract one minute instead.
*/
subtractSecond() {
this.#date = this.#date.minus({ seconds: 1 });
}
/**
* Adds a unit of time to the current CronDate.
* @param {TimeUnit} unit
*/
addUnit(unit) {
this.#verbMap.add[unit]();
}
/**
* Subtracts a unit of time from the current CronDate.
* @param {TimeUnit} unit
*/
subtractUnit(unit) {
this.#verbMap.subtract[unit]();
}
/**
* Handles a math operation.
* @param {DateMathOp} verb - {'add' | 'subtract'}
* @param {TimeUnit} unit - {'year' | 'month' | 'day' | 'hour' | 'minute' | 'second'}
*/
invokeDateOperation(verb, unit) {
if (verb === DateMathOp.Add) {
this.addUnit(unit);
return;
}
if (verb === DateMathOp.Subtract) {
this.subtractUnit(unit);
return;
}
/* istanbul ignore next - this would only happen if an end user call the handleMathOp with an invalid verb */
throw new Error(`Invalid verb: ${verb}`);
}
/**
* Returns the day.
* @returns {number}
*/
getDate() {
return this.#date.day;
}
/**
* Returns the year.
* @returns {number}
*/
getFullYear() {
return this.#date.year;
}
/**
* Returns the day of the week.
* @returns {number}
*/
getDay() {
const weekday = this.#date.weekday;
return weekday === 7 ? 0 : weekday;
}
/**
* Returns the month.
* @returns {number}
*/
getMonth() {
return this.#date.month - 1;
}
/**
* Returns the hour.
* @returns {number}
*/
getHours() {
return this.#date.hour;
}
/**
* Returns the minutes.
* @returns {number}
*/
getMinutes() {
return this.#date.minute;
}
/**
* Returns the seconds.
* @returns {number}
*/
getSeconds() {
return this.#date.second;
}
/**
* Returns the milliseconds.
* @returns {number}
*/
getMilliseconds() {
return this.#date.millisecond;
}
/**
* Returns the time.
* @returns {number}
*/
getTime() {
return this.#date.valueOf();
}
/**
* Returns the UTC day.
* @returns {number}
*/
getUTCDate() {
return this.#getUTC().day;
}
/**
* Returns the UTC year.
* @returns {number}
*/
getUTCFullYear() {
return this.#getUTC().year;
}
/**
* Returns the UTC day of the week.
* @returns {number}
*/
getUTCDay() {
const weekday = this.#getUTC().weekday;
return weekday === 7 ? 0 : weekday;
}
/**
* Returns the UTC month.
* @returns {number}
*/
getUTCMonth() {
return this.#getUTC().month - 1;
}
/**
* Returns the UTC hour.
* @returns {number}
*/
getUTCHours() {
return this.#getUTC().hour;
}
/**
* Returns the UTC minutes.
* @returns {number}
*/
getUTCMinutes() {
return this.#getUTC().minute;
}
/**
* Returns the UTC seconds.
* @returns {number}
*/
getUTCSeconds() {
return this.#getUTC().second;
}
/**
* Returns the UTC milliseconds.
* @returns {string | null}
*/
toISOString() {
return this.#date.toUTC().toISO();
}
/**
* Returns the date as a JSON string.
* @returns {string | null}
*/
toJSON() {
return this.#date.toJSON();
}
/**
* Sets the day.
* @param d
*/
setDate(d) {
this.#date = this.#date.set({ day: d });
}
/**
* Sets the year.
* @param y
*/
setFullYear(y) {
this.#date = this.#date.set({ year: y });
}
/**
* Sets the day of the week.
* @param d
*/
setDay(d) {
this.#date = this.#date.set({ weekday: d });
}
/**
* Sets the month.
* @param m
*/
setMonth(m) {
this.#date = this.#date.set({ month: m + 1 });
}
/**
* Sets the hour.
* @param h
*/
setHours(h) {
this.#date = this.#date.set({ hour: h });
}
/**
* Sets the minutes.
* @param m
*/
setMinutes(m) {
this.#date = this.#date.set({ minute: m });
}
/**
* Sets the seconds.
* @param s
*/
setSeconds(s) {
this.#date = this.#date.set({ second: s });
}
/**
* Sets the milliseconds.
* @param s
*/
setMilliseconds(s) {
this.#date = this.#date.set({ millisecond: s });
}
/**
* Returns the date as a string.
* @returns {string}
*/
toString() {
return this.toDate().toString();
}
/**
* Returns the date as a Date object.
* @returns {Date}
*/
toDate() {
return this.#date.toJSDate();
}
/**
* Returns true if the day is the last day of the month.
* @returns {boolean}
*/
isLastDayOfMonth() {
const { day, month } = this.#date;
// Special handling for February in leap years
if (month === 2) {
const isLeap = CronDate.#isLeapYear(this.#date.year);
return day === exports.DAYS_IN_MONTH[month - 1] - (isLeap ? 0 : 1);
}
// For other months, check against the static map
return day === exports.DAYS_IN_MONTH[month - 1];
}
/**
* Returns true if the day is the last weekday of the month.
* @returns {boolean}
*/
isLastWeekdayOfMonth() {
const { day, month } = this.#date;
// Get the last day of the current month
let lastDay;
if (month === 2) {
// Special handling for February
lastDay = exports.DAYS_IN_MONTH[month - 1] - (CronDate.#isLeapYear(this.#date.year) ? 0 : 1);
}
else {
lastDay = exports.DAYS_IN_MONTH[month - 1];
}
// Check if the current day is within 7 days of the end of the month
return day > lastDay - 7;
}
/**
* Primarily for internal use.
* @param {DateMathOp} op - The operation to perform.
* @param {TimeUnit} unit - The unit of time to use.
* @param {number} [hoursLength] - The length of the hours. Required when unit is not month or day.
*/
applyDateOperation(op, unit, hoursLength) {
if (unit === TimeUnit.Month || unit === TimeUnit.Day) {
this.invokeDateOperation(op, unit);
return;
}
const previousHour = this.getHours();
this.invokeDateOperation(op, unit);
const currentHour = this.getHours();
const diff = currentHour - previousHour;
if (diff === 2) {
if (hoursLength !== 24) {
this.dstStart = currentHour;
}
}
else if (diff === 0 && this.getMinutes() === 0 && this.getSeconds() === 0) {
if (hoursLength !== 24) {
this.dstEnd = currentHour;
}
}
}
/**
* Returns the UTC date.
* @private
* @returns {DateTime}
*/
#getUTC() {
return this.#date.toUTC();
}
}
exports.CronDate = CronDate;
exports.default = CronDate;