webservices/node_modules/cronosjs/dist-src/parser.js

358 lines
14 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { sortAsc, flatMap } from './utils';
const predefinedCronStrings = {
'@yearly': '0 0 0 1 1 * *',
'@annually': '0 0 0 1 1 * *',
'@monthly': '0 0 0 1 * * *',
'@weekly': '0 0 0 * * 0 *',
'@daily': '0 0 0 * * * *',
'@midnight': '0 0 0 * * * *',
'@hourly': '0 0 * * * * *',
};
const monthReplacements = ['jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', 'aug', 'sep', 'oct', 'nov', 'dec'];
const monthReplacementRegex = new RegExp(monthReplacements.join('|'), 'g');
const dayOfWeekReplacements = ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'];
const dayOfWeekReplacementRegex = new RegExp(dayOfWeekReplacements.join('|'), 'g');
/*
"The actual range of times supported by ECMAScript Date objects is slightly smaller:
exactly 100,000,000 days to 100,000,000 days measured relative to midnight at the
beginning of 01 January, 1970 UTC. This gives a range of 8,640,000,000,000,000
milliseconds to either side of 01 January, 1970 UTC."
http://ecma-international.org/ecma-262/5.1/#sec-15.9.1.1
new Date(8640000000000000) => 00:00:00 13th Sep 275760
Largest full year valid as JS date = 275759
*/
const maxValidYear = 275759;
export var WarningType;
(function (WarningType) {
WarningType["IncrementLargerThanRange"] = "IncrementLargerThanRange";
})(WarningType || (WarningType = {}));
export function _parse(cronstring) {
let expr = cronstring.trim().toLowerCase();
if (predefinedCronStrings[expr]) {
expr = predefinedCronStrings[expr];
}
const fields = expr.split(/\s+/g);
if (fields.length < 5 || fields.length > 7) {
throw new Error('Expression must have at least 5 fields, and no more than 7 fields');
}
switch (fields.length) {
case 5:
fields.unshift('0');
case 6:
fields.push('*');
}
return [
new SecondsOrMinutesField(fields[0]),
new SecondsOrMinutesField(fields[1]),
new HoursField(fields[2]),
new DaysField(fields[3], fields[5]),
new MonthsField(fields[4]),
new YearsField(fields[6])
];
}
function getIncrementLargerThanRangeWarnings(items, first, last) {
const warnings = [];
for (let item of items) {
let rangeLength;
if (item.step > 1 &&
item.step > (rangeLength = item.rangeLength(first, last))) {
warnings.push({
type: WarningType.IncrementLargerThanRange,
message: `Increment (${item.step}) is larger than range (${rangeLength}) for expression '${item.itemString}'`
});
}
}
return warnings;
}
class Field {
constructor(field) {
this.field = field;
}
parse() {
return this.field.split(',')
.map(item => FieldItem.parse(item, this.first, this.last, true));
}
get items() {
if (!this._items)
this._items = this.parse();
return this._items;
}
get values() {
return Field.getValues(this.items, this.first, this.last);
}
get warnings() {
return getIncrementLargerThanRangeWarnings(this.items, this.first, this.last);
}
static getValues(items, first, last) {
return Array.from(new Set(flatMap(items, item => item.values(first, last)))).sort(sortAsc);
}
}
class FieldItem {
constructor(itemString) {
this.itemString = itemString;
this.step = 1;
}
rangeLength(first, last) {
var _a, _b, _c, _d;
const start = (_b = (_a = this.range) === null || _a === void 0 ? void 0 : _a.from) !== null && _b !== void 0 ? _b : first, end = (_d = (_c = this.range) === null || _c === void 0 ? void 0 : _c.to) !== null && _d !== void 0 ? _d : last;
return (end < start) ? ((last - start) + (end - first) + 1) : (end - start);
}
values(first, last) {
const start = this.range ? this.range.from : first, rangeLength = this.rangeLength(first, last);
return Array(Math.floor(rangeLength / this.step) + 1)
.fill(0)
.map((_, i) => first + ((start - first + (this.step * i)) % (last - first + 1)));
}
get any() {
return this.range === undefined && this.step === 1;
}
get single() {
return !!this.range && this.range.from === this.range.to;
}
static parse(item, first, last, allowCyclicRange = false, transformer) {
var _a;
const fieldItem = new FieldItem(item);
const [match, all, startFrom, range, step] = ((_a = item.match(/^(?:(\*)|([0-9]+)|([0-9]+-[0-9]+))(?:\/([1-9][0-9]*))?$/)) !== null && _a !== void 0 ? _a : []);
if (!match)
throw new Error('Field item invalid format');
if (step) {
fieldItem.step = parseInt(step, 10);
}
if (startFrom) {
let start = parseInt(startFrom, 10);
start = transformer ? transformer(start) : start;
if (start < first || start > last)
throw new Error('Field item out of valid value range');
fieldItem.range = {
from: start,
to: step ? undefined : start
};
}
else if (range) {
const [rangeStart, rangeEnd] = range.split('-').map(x => {
const n = parseInt(x, 10);
return transformer ? transformer(n) : n;
});
if (rangeStart < first || rangeStart > last || rangeEnd < first || rangeEnd > last ||
(rangeEnd < rangeStart && !allowCyclicRange)) {
throw new Error('Field item range invalid, either value out of valid range or start greater than end in non wraparound field');
}
fieldItem.range = {
from: rangeStart,
to: rangeEnd
};
}
return fieldItem;
}
}
FieldItem.asterisk = new FieldItem('*');
export class SecondsOrMinutesField extends Field {
constructor() {
super(...arguments);
this.first = 0;
this.last = 59;
}
}
export class HoursField extends Field {
constructor() {
super(...arguments);
this.first = 0;
this.last = 23;
}
}
export class DaysField {
constructor(daysOfMonthField, daysOfWeekField) {
this.lastDay = false;
this.lastWeekday = false;
this.daysItems = [];
this.nearestWeekdayItems = [];
this.daysOfWeekItems = [];
this.lastDaysOfWeekItems = [];
this.nthDaysOfWeekItems = [];
for (let item of daysOfMonthField.split(',').map(s => s === '?' ? '*' : s)) {
if (item === 'l') {
this.lastDay = true;
}
else if (item === 'lw') {
this.lastWeekday = true;
}
else if (item.endsWith('w')) {
this.nearestWeekdayItems.push(FieldItem.parse(item.slice(0, -1), 1, 31));
}
else {
this.daysItems.push(FieldItem.parse(item, 1, 31));
}
}
const normalisedDaysOfWeekField = daysOfWeekField.replace(dayOfWeekReplacementRegex, match => dayOfWeekReplacements.indexOf(match) + '');
const parseDayOfWeek = (item) => FieldItem.parse(item, 0, 6, true, n => n === 7 ? 0 : n);
for (let item of normalisedDaysOfWeekField.split(',').map(s => s === '?' ? '*' : s)) {
const nthIndex = item.lastIndexOf('#');
if (item.endsWith('l')) {
this.lastDaysOfWeekItems.push(parseDayOfWeek(item.slice(0, -1)));
}
else if (nthIndex !== -1) {
const nth = item.slice(nthIndex + 1);
if (!/^[1-5]$/.test(nth))
throw new Error('Field item nth of month (#) invalid');
this.nthDaysOfWeekItems.push({
item: parseDayOfWeek(item.slice(0, nthIndex)),
nth: parseInt(nth, 10)
});
}
else {
this.daysOfWeekItems.push(parseDayOfWeek(item));
}
}
}
get values() {
return DaysFieldValues.fromField(this);
}
get warnings() {
const warnings = [], dayItems = [
...this.daysItems,
...this.nearestWeekdayItems,
], weekItems = [
...this.daysOfWeekItems,
...this.lastDaysOfWeekItems,
...this.nthDaysOfWeekItems.map(({ item }) => item),
];
warnings.push(...getIncrementLargerThanRangeWarnings(dayItems, 1, 31), ...getIncrementLargerThanRangeWarnings(weekItems, 0, 6));
return warnings;
}
get allDays() {
return (!this.lastDay &&
!this.lastWeekday &&
!this.nearestWeekdayItems.length &&
!this.lastDaysOfWeekItems.length &&
!this.nthDaysOfWeekItems.length &&
this.daysItems.length === 1 && this.daysItems[0].any &&
this.daysOfWeekItems.length === 1 && this.daysOfWeekItems[0].any);
}
}
export class DaysFieldValues {
constructor() {
this.lastDay = false;
this.lastWeekday = false;
this.days = [];
this.nearestWeekday = [];
this.daysOfWeek = [];
this.lastDaysOfWeek = [];
this.nthDaysOfWeek = [];
}
static fromField(field) {
const values = new DaysFieldValues();
const filterAnyItems = (items) => items.filter(item => !item.any);
values.lastDay = field.lastDay;
values.lastWeekday = field.lastWeekday;
values.days = Field.getValues(field.allDays ? [FieldItem.asterisk] : filterAnyItems(field.daysItems), 1, 31);
values.nearestWeekday = Field.getValues(field.nearestWeekdayItems, 1, 31);
values.daysOfWeek = Field.getValues(filterAnyItems(field.daysOfWeekItems), 0, 6);
values.lastDaysOfWeek = Field.getValues(field.lastDaysOfWeekItems, 0, 6);
const nthDaysHashes = new Set();
for (let item of field.nthDaysOfWeekItems) {
for (let n of item.item.values(0, 6)) {
let hash = n * 10 + item.nth;
if (!nthDaysHashes.has(hash)) {
nthDaysHashes.add(hash);
values.nthDaysOfWeek.push([n, item.nth]);
}
}
}
return values;
}
getDays(year, month) {
const days = new Set(this.days);
const lastDateOfMonth = new Date(year, month, 0).getDate();
const firstDayOfWeek = new Date(year, month - 1, 1).getDay();
const getNearestWeekday = (day) => {
if (day > lastDateOfMonth)
day = lastDateOfMonth;
const dayOfWeek = (day + firstDayOfWeek - 1) % 7;
let weekday = day + (dayOfWeek === 0 ? 1 : (dayOfWeek === 6 ? -1 : 0));
return weekday + (weekday < 1 ? 3 : (weekday > lastDateOfMonth ? -3 : 0));
};
if (this.lastDay) {
days.add(lastDateOfMonth);
}
if (this.lastWeekday) {
days.add(getNearestWeekday(lastDateOfMonth));
}
for (const day of this.nearestWeekday) {
days.add(getNearestWeekday(day));
}
if (this.daysOfWeek.length ||
this.lastDaysOfWeek.length ||
this.nthDaysOfWeek.length) {
const daysOfWeek = Array(7).fill(0).map(() => ([]));
for (let day = 1; day < 36; day++) {
daysOfWeek[(day + firstDayOfWeek - 1) % 7].push(day);
}
for (const dayOfWeek of this.daysOfWeek) {
for (const day of daysOfWeek[dayOfWeek]) {
days.add(day);
}
}
for (const dayOfWeek of this.lastDaysOfWeek) {
for (let i = daysOfWeek[dayOfWeek].length - 1; i >= 0; i--) {
if (daysOfWeek[dayOfWeek][i] <= lastDateOfMonth) {
days.add(daysOfWeek[dayOfWeek][i]);
break;
}
}
}
for (const [dayOfWeek, nthOfMonth] of this.nthDaysOfWeek) {
days.add(daysOfWeek[dayOfWeek][nthOfMonth - 1]);
}
}
return Array.from(days).filter(day => day <= lastDateOfMonth).sort(sortAsc);
}
}
export class MonthsField extends Field {
constructor(field) {
super(field.replace(monthReplacementRegex, match => {
return monthReplacements.indexOf(match) + 1 + '';
}));
this.first = 1;
this.last = 12;
}
}
export class YearsField extends Field {
constructor(field) {
super(field);
this.first = 1970;
this.last = 2099;
this.items;
}
parse() {
return this.field.split(',')
.map(item => FieldItem.parse(item, 0, maxValidYear));
}
get warnings() {
return getIncrementLargerThanRangeWarnings(this.items, this.first, maxValidYear);
}
nextYear(fromYear) {
var _a;
return (_a = this.items.reduce((years, item) => {
var _a, _b, _c, _d;
if (item.any)
years.push(fromYear);
else if (item.single) {
const year = item.range.from;
if (year >= fromYear)
years.push(year);
}
else {
const start = (_b = (_a = item.range) === null || _a === void 0 ? void 0 : _a.from) !== null && _b !== void 0 ? _b : this.first;
if (start > fromYear)
years.push(start);
else {
const nextYear = start + Math.ceil((fromYear - start) / item.step) * item.step;
if (nextYear <= ((_d = (_c = item.range) === null || _c === void 0 ? void 0 : _c.to) !== null && _d !== void 0 ? _d : maxValidYear))
years.push(nextYear);
}
}
return years;
}, []).sort(sortAsc)[0]) !== null && _a !== void 0 ? _a : null;
}
}