| Current Path : /home/deltalab/PMS/logistic-backend/node_modules/cron/lib/ |
| Current File : //home/deltalab/PMS/logistic-backend/node_modules/cron/lib/time.js |
const CONSTRAINTS = [
[0, 59],
[0, 59],
[0, 23],
[1, 31],
[0, 11],
[0, 6]
];
const MONTH_CONSTRAINTS = [
31,
29, // support leap year...not perfect
31,
30,
31,
30,
31,
31,
30,
31,
30,
31
];
const PARSE_DEFAULTS = ['0', '*', '*', '*', '*', '*'];
const ALIASES = {
jan: 0,
feb: 1,
mar: 2,
apr: 3,
may: 4,
jun: 5,
jul: 6,
aug: 7,
sep: 8,
oct: 9,
nov: 10,
dec: 11,
sun: 0,
mon: 1,
tue: 2,
wed: 3,
thu: 4,
fri: 5,
sat: 6
};
const TIME_UNITS = [
'second',
'minute',
'hour',
'dayOfMonth',
'month',
'dayOfWeek'
];
const TIME_UNITS_LEN = TIME_UNITS.length;
const PRESETS = {
'@yearly': '0 0 0 1 0 *',
'@monthly': '0 0 0 1 * *',
'@weekly': '0 0 0 * * 0',
'@daily': '0 0 0 * * *',
'@hourly': '0 0 * * * *',
'@minutely': '0 * * * * *',
'@secondly': '* * * * * *',
'@weekdays': '0 0 0 * * 1-5',
'@weekends': '0 0 0 * * 0,6'
};
const RE_WILDCARDS = /\*/g;
const RE_RANGE = /^(\d+)(?:-(\d+))?(?:\/(\d+))?$/g;
function CronTime(luxon) {
function CT(source, zone, utcOffset) {
this.source = source;
if (zone) {
const dt = luxon.DateTime.fromObject({ zone: zone });
if (dt.invalid) {
throw new Error('Invalid timezone.');
}
this.zone = zone;
}
if (typeof utcOffset !== 'undefined') {
this.utcOffset = utcOffset;
}
var that = this;
TIME_UNITS.map(timeUnit => {
that[timeUnit] = {};
});
if (this.source instanceof Date || this.source instanceof luxon.DateTime) {
if (this.source instanceof Date) {
this.source = luxon.DateTime.fromJSDate(this.source);
}
this.realDate = true;
} else {
this._parse(this.source);
this._verifyParse();
}
}
CT.prototype = {
/*
* Ensure that the syntax parsed correctly and correct the specified values if needed.
*/
_verifyParse: function () {
var months = Object.keys(this.month);
var dom = Object.keys(this.dayOfMonth);
var ok = false;
/* if a dayOfMonth is not found in all months, we only need to fix the last
wrong month to prevent infinite loop */
var lastWrongMonth = NaN;
for (var i = 0; i < months.length; i++) {
var m = months[i];
var con = MONTH_CONSTRAINTS[parseInt(m, 10)];
for (var j = 0; j < dom.length; j++) {
var day = dom[j];
if (day <= con) {
ok = true;
}
}
if (!ok) {
// save the month in order to be fixed if all months fails (infinite loop)
lastWrongMonth = m;
console.warn(`Month '${m}' is limited to '${con}' days.`);
}
}
// infinite loop detected (dayOfMonth is not found in all months)
if (!ok) {
var notOkCon = MONTH_CONSTRAINTS[parseInt(lastWrongMonth, 10)];
for (var k = 0; k < dom.length; k++) {
var notOkDay = dom[k];
if (notOkDay > notOkCon) {
delete this.dayOfMonth[notOkDay];
var fixedDay = Number(notOkDay) % notOkCon;
this.dayOfMonth[fixedDay] = true;
}
}
}
},
/**
* Calculate the "next" scheduled time
*/
sendAt: function (i) {
var date = this.realDate ? this.source : luxon.DateTime.local();
if (this.zone) {
date = date.setZone(this.zone);
}
if (typeof this.utcOffset !== 'undefined') {
let offset =
this.utcOffset >= 60 || this.utcOffset <= -60
? this.utcOffset / 60
: this.utcOffset;
offset = parseInt(offset);
let utcZone = 'UTC';
if (offset < 0) {
utcZone += offset;
} else if (offset > 0) {
utcZone += `+${offset}`;
}
date = date.setZone(utcZone);
if (date.invalid) {
throw new Error('ERROR: You specified an invalid UTC offset.');
}
}
if (this.realDate) {
if (luxon.DateTime.local() > date) {
throw new Error('WARNING: Date in past. Will never be fired.');
}
return date;
}
if (isNaN(i) || i < 0) {
// just get the next scheduled time
return this._getNextDateFrom(date);
} else {
// return the next schedule times
var dates = [];
for (; i > 0; i--) {
date = this._getNextDateFrom(date);
dates.push(date);
}
return dates;
}
},
/**
* Get the number of milliseconds in the future at which to fire our callbacks.
*/
getTimeout: function () {
return Math.max(-1, this.sendAt() - luxon.DateTime.local());
},
/**
* writes out a cron string
*/
toString: function () {
return this.toJSON().join(' ');
},
/**
* Json representation of the parsed cron syntax.
*/
toJSON: function () {
var self = this;
return TIME_UNITS.map(function (timeName) {
return self._wcOrAll(timeName);
});
},
/**
* Get next date matching the specified cron time.
*
* Algorithm:
* - Start with a start date and a parsed crontime.
* - Loop until 5 seconds have passed, or we found the next date.
* - Within the loop:
* - If it took longer than 5 seconds to select a date, throw an exception.
* - Find the next month to run at.
* - Find the next day of the month to run at.
* - Find the next day of the week to run at.
* - Find the next hour to run at.
* - Find the next minute to run at.
* - Find the next second to run at.
* - Check that the chosen time does not equal the current execution.
* - Return the selected date object.
*/
_getNextDateFrom: function (start, zone) {
if (start instanceof Date) {
start = luxon.DateTime.fromJSDate(start);
}
var date = start;
var firstDate = start.toMillis();
if (zone) {
date = date.setZone(zone);
}
if (!this.realDate) {
if (date.millisecond > 0) {
date = date.set({ millisecond: 0, second: date.second + 1 });
}
}
if (date.invalid) {
throw new Error('ERROR: You specified an invalid date.');
}
// it shouldn't take more than 5 seconds to find the next execution time
// being very generous with this. Throw error if it takes too long to find the next time to protect from
// infinite loop.
var timeout = Date.now() + 5000;
// determine next date
while (true) {
var diff = date - start;
// hard stop if the current date is after the expected execution
if (Date.now() > timeout) {
throw new Error(
`Something went wrong. cron reached maximum iterations.
Please open an issue (https://github.com/kelektiv/node-cron/issues/new) and provide the following string
Time Zone: ${zone || '""'} - Cron String: ${this} - UTC offset: ${date.format(
'Z'
)} - current Date: ${luxon.DateTime.local().toString()}`
);
}
if (
!(date.month - 1 in this.month) &&
Object.keys(this.month).length !== 12
) {
date = date.plus({ months: 1 });
date = date.set({ day: 1, hour: 0, minute: 0, second: 0 });
continue;
}
if (
!(date.day in this.dayOfMonth) &&
Object.keys(this.dayOfMonth).length !== 31 &&
!(
date.getWeekDay() in this.dayOfWeek &&
Object.keys(this.dayOfWeek).length !== 7
)
) {
date = date.plus({ days: 1 });
date = date.set({ hour: 0, minute: 0, second: 0 });
continue;
}
if (
!(date.getWeekDay() in this.dayOfWeek) &&
Object.keys(this.dayOfWeek).length !== 7 &&
!(
date.day in this.dayOfMonth &&
Object.keys(this.dayOfMonth).length !== 31
)
) {
date = date.plus({ days: 1 });
date = date.set({ hour: 0, minute: 0, second: 0 });
continue;
}
if (!(date.hour in this.hour) && Object.keys(this.hour).length !== 24) {
date = date.set({
hour: date.hour === 23 && diff > 86400000 ? 0 : date.hour + 1
});
date = date.set({ minute: 0, second: 0 });
continue;
}
if (
!(date.minute in this.minute) &&
Object.keys(this.minute).length !== 60
) {
date = date.set({
minute: date.minute === 59 && diff > 3600000 ? 0 : date.minute + 1
});
date = date.set({ second: 0 });
continue;
}
if (
!(date.second in this.second) &&
Object.keys(this.second).length !== 60
) {
date = date.set({
second: date.second === 59 && diff > 60000 ? 0 : date.second + 1
});
continue;
}
if (date.toMillis() === firstDate) {
date = date.set({ second: date.second + 1 });
continue;
}
break;
}
return date;
},
/**
* wildcard, or all params in array (for to string)
*/
_wcOrAll: function (type) {
if (this._hasAll(type)) {
return '*';
}
var all = [];
for (var time in this[type]) {
all.push(time);
}
return all.join(',');
},
_hasAll: function (type) {
var constraints = CONSTRAINTS[TIME_UNITS.indexOf(type)];
for (var i = constraints[0], n = constraints[1]; i < n; i++) {
if (!(i in this[type])) {
return false;
}
}
return true;
},
/*
* Parse the cron syntax into something useful for selecting the next execution time.
*
* Algorithm:
* - Replace preset
* - Replace aliases in the source.
* - Trim string and split for processing.
* - Loop over split options (ms -> month):
* - Get the value (or default) in the current position.
* - Parse the value.
*/
_parse: function (source) {
source = source.toLowerCase();
if (source in PRESETS) {
source = PRESETS[source];
}
source = source.replace(/[a-z]{1,3}/gi, alias => {
if (alias in ALIASES) {
return ALIASES[alias];
}
throw new Error(`Unknown alias: ${alias}`);
});
var units = source.trim().split(/\s+/);
// seconds are optional
if (units.length < TIME_UNITS_LEN - 1) {
throw new Error('Too few fields');
}
if (units.length > TIME_UNITS_LEN) {
throw new Error('Too many fields');
}
var unitsLen = units.length;
for (var i = 0; i < TIME_UNITS_LEN; i++) {
// If the split source string doesn't contain all digits,
// assume defaults for first n missing digits.
// This adds support for 5-digit standard cron syntax
var cur = units[i - (TIME_UNITS_LEN - unitsLen)] || PARSE_DEFAULTS[i];
this._parseField(cur, TIME_UNITS[i], CONSTRAINTS[i]);
}
},
/*
* Parse individual field from the cron syntax provided.
*
* Algorithm:
* - Split field by commas aand check for wildcards to ensure proper user.
* - Replace wildcard values with <low>-<high> boundaries.
* - Split field by commas and then iterate over ranges inside field.
* - If range matches pattern then map over matches using replace (to parse the range by the regex pattern)
* - Starting with the lower bounds of the range iterate by step up to the upper bounds and toggle the CronTime field value flag on.
*/
_parseField: function (value, type, constraints) {
var typeObj = this[type];
var pointer;
var low = constraints[0];
var high = constraints[1];
var fields = value.split(',');
fields.forEach(field => {
var wildcardIndex = field.indexOf('*');
if (wildcardIndex !== -1 && wildcardIndex !== 0) {
throw new Error(
`Field (${field}) has an invalid wildcard expression`
);
}
});
// * is a shortcut to [low-high] range for the field
value = value.replace(RE_WILDCARDS, `${low}-${high}`);
// commas separate information, so split based on those
var allRanges = value.split(',');
for (var i = 0; i < allRanges.length; i++) {
if (allRanges[i].match(RE_RANGE)) {
allRanges[i].replace(RE_RANGE, ($0, lower, upper, step) => {
lower = parseInt(lower, 10);
upper = parseInt(upper, 10) || undefined;
const wasStepDefined = !isNaN(parseInt(step, 10));
if (step === '0') {
throw new Error(`Field (${type}) has a step of zero`);
}
step = parseInt(step, 10) || 1;
if (upper && lower > upper) {
throw new Error(`Field (${type}) has an invalid range`);
}
const outOfRangeError =
lower < low ||
(upper && upper > high) ||
(!upper && lower > high);
if (outOfRangeError) {
throw new Error(`Field value (${value}) is out of range`);
}
// Positive integer higher than constraints[0]
lower = Math.min(Math.max(low, ~~Math.abs(lower)), high);
// Positive integer lower than constraints[1]
if (upper) {
upper = Math.min(high, ~~Math.abs(upper));
} else {
// If step is provided, the default upper range is the highest value
upper = wasStepDefined ? high : lower;
}
// Count from the lower barrier to the upper
pointer = lower;
do {
typeObj[pointer] = true; // mutates the field objects values inside CronTime
pointer += step;
} while (pointer <= upper);
});
} else {
throw new Error(`Field (${type}) cannot be parsed`);
}
}
}
};
return CT;
}
module.exports = CronTime;