webservices/node_modules/cronosjs/dist-web/index.js

823 lines
33 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.

const sortAsc = (a, b) => a - b;
function flatMap(arr, mapper) {
return arr.reduce((acc, val, i) => {
acc.push(...mapper(val, i, arr));
return acc;
}, []);
}
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;
var WarningType;
(function (WarningType) {
WarningType["IncrementLargerThanRange"] = "IncrementLargerThanRange";
})(WarningType || (WarningType = {}));
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('*');
class SecondsOrMinutesField extends Field {
constructor() {
super(...arguments);
this.first = 0;
this.last = 59;
}
}
class HoursField extends Field {
constructor() {
super(...arguments);
this.first = 0;
this.last = 23;
}
}
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);
}
}
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);
}
}
class MonthsField extends Field {
constructor(field) {
super(field.replace(monthReplacementRegex, match => {
return monthReplacements.indexOf(match) + 1 + '';
}));
this.first = 1;
this.last = 12;
}
}
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;
}
}
class CronosDate {
constructor(year, month = 1, day = 1, hour = 0, minute = 0, second = 0) {
this.year = year;
this.month = month;
this.day = day;
this.hour = hour;
this.minute = minute;
this.second = second;
}
static fromDate(date, timezone) {
if (!timezone) {
return new CronosDate(date.getFullYear(), date.getMonth() + 1, date.getDate(), date.getHours(), date.getMinutes(), date.getSeconds());
}
return timezone['nativeDateToCronosDate'](date);
}
toDate(timezone) {
if (!timezone) {
return new Date(this.year, this.month - 1, this.day, this.hour, this.minute, this.second);
}
return timezone['cronosDateToNativeDate'](this);
}
static fromUTCTimestamp(timestamp) {
const date = new Date(timestamp);
return new CronosDate(date.getUTCFullYear(), date.getUTCMonth() + 1, date.getUTCDate(), date.getUTCHours(), date.getUTCMinutes(), date.getUTCSeconds());
}
toUTCTimestamp() {
return Date.UTC(this.year, this.month - 1, this.day, this.hour, this.minute, this.second);
}
copyWith({ year = this.year, month = this.month, day = this.day, hour = this.hour, minute = this.minute, second = this.second } = {}) {
return new CronosDate(year, month, day, hour, minute, second);
}
}
// Adapted from Intl.DateTimeFormat timezone handling in https://github.com/moment/luxon
const ZoneCache = new Map();
class CronosTimezone {
constructor(IANANameOrOffset) {
if (typeof IANANameOrOffset === 'number') {
if (IANANameOrOffset > 840 || IANANameOrOffset < -840)
throw new Error('Invalid offset');
this.fixedOffset = IANANameOrOffset;
return this;
}
const offsetMatch = IANANameOrOffset.match(/^([+-]?)(0[1-9]|1[0-4])(?::?([0-5][0-9]))?$/);
if (offsetMatch) {
this.fixedOffset = (offsetMatch[1] === '-' ? -1 : 1) * ((parseInt(offsetMatch[2], 10) * 60) + (parseInt(offsetMatch[3], 10) || 0));
return this;
}
if (ZoneCache.has(IANANameOrOffset)) {
return ZoneCache.get(IANANameOrOffset);
}
try {
this.dateTimeFormat = new Intl.DateTimeFormat("en-US", {
hour12: false,
timeZone: IANANameOrOffset,
year: "numeric",
month: "2-digit",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
second: "2-digit"
});
}
catch (err) {
throw new Error('Invalid IANA name or offset');
}
this.zoneName = IANANameOrOffset;
const currentYear = new Date().getUTCFullYear();
this.winterOffset = this.offset(Date.UTC(currentYear, 0, 1));
this.summerOffset = this.offset(Date.UTC(currentYear, 5, 1));
ZoneCache.set(IANANameOrOffset, this);
}
toString() {
if (this.fixedOffset) {
const absOffset = Math.abs(this.fixedOffset);
return [
this.fixedOffset < 0 ? '-' : '+',
Math.floor(absOffset / 60).toString().padStart(2, '0'),
(absOffset % 60).toString().padStart(2, '0')
].join('');
}
return this.zoneName;
}
offset(ts) {
if (!this.dateTimeFormat)
return this.fixedOffset || 0;
const date = new Date(ts);
const { year, month, day, hour, minute, second } = this.nativeDateToCronosDate(date);
const asUTC = Date.UTC(year, month - 1, day, hour, minute, second), asTS = ts - (ts % 1000);
return (asUTC - asTS) / 60000;
}
nativeDateToCronosDate(date) {
if (!this.dateTimeFormat) {
return CronosDate['fromUTCTimestamp'](date.getTime() + (this.fixedOffset || 0) * 60000);
}
return this.dateTimeFormat['formatToParts']
? partsOffset(this.dateTimeFormat, date)
: hackyOffset(this.dateTimeFormat, date);
}
cronosDateToNativeDate(date) {
if (!this.dateTimeFormat) {
return new Date(date['toUTCTimestamp']() - (this.fixedOffset || 0) * 60000);
}
const provisionalOffset = ((date.month > 3 || date.month < 11) ? this.summerOffset : this.winterOffset) || 0;
const UTCTimestamp = date['toUTCTimestamp']();
// Find the right offset a given local time.
// Our UTC time is just a guess because our offset is just a guess
let utcGuess = UTCTimestamp - provisionalOffset * 60000;
// Test whether the zone matches the offset for this ts
const o2 = this.offset(utcGuess);
// If so, offset didn't change and we're done
if (provisionalOffset === o2)
return new Date(utcGuess);
// If not, change the ts by the difference in the offset
utcGuess -= (o2 - provisionalOffset) * 60000;
// If that gives us the local time we want, we're done
const o3 = this.offset(utcGuess);
if (o2 === o3)
return new Date(utcGuess);
// If it's different, we're in a hole time. The offset has changed, but the we don't adjust the time
return new Date(UTCTimestamp - Math.min(o2, o3) * 60000);
}
}
function hackyOffset(dtf, date) {
const formatted = dtf.format(date).replace(/\u200E/g, ""), parsed = formatted.match(/(\d+)\/(\d+)\/(\d+),? (\d+):(\d+):(\d+)/), [, month, day, year, hour, minute, second] = (parsed !== null && parsed !== void 0 ? parsed : []).map(n => parseInt(n, 10));
return new CronosDate(year, month, day, hour % 24, minute, second);
}
function partsOffset(dtf, date) {
const formatted = dtf.formatToParts(date);
return new CronosDate(parseInt(formatted[4].value, 10), parseInt(formatted[0].value, 10), parseInt(formatted[2].value, 10), parseInt(formatted[6].value, 10) % 24, parseInt(formatted[8].value, 10), parseInt(formatted[10].value, 10));
}
const hourinms = 60 * 60 * 1000;
const findFirstFrom = (from, list) => list.findIndex(n => n >= from);
class CronosExpression {
constructor(cronString, seconds, minutes, hours, days, months, years) {
this.cronString = cronString;
this.seconds = seconds;
this.minutes = minutes;
this.hours = hours;
this.days = days;
this.months = months;
this.years = years;
this.skipRepeatedHour = true;
this.missingHour = 'insert';
this._warnings = null;
}
static parse(cronstring, options = {}) {
var _a;
const parsedFields = _parse(cronstring);
if (options.strict) {
let warnings = flatMap(parsedFields, field => field.warnings);
if (typeof options.strict === 'object') {
warnings = warnings
.filter(warning => !!options.strict[warning.type]);
}
if (warnings.length > 0) {
throw new Error(`Strict mode: Parsing failed with ${warnings.length} warnings`);
}
}
const expr = new CronosExpression(cronstring, parsedFields[0].values, parsedFields[1].values, parsedFields[2].values, parsedFields[3].values, parsedFields[4].values, parsedFields[5]);
expr.timezone = options.timezone instanceof CronosTimezone ? options.timezone :
(options.timezone !== undefined ? new CronosTimezone(options.timezone) : undefined);
expr.skipRepeatedHour = options.skipRepeatedHour !== undefined ? options.skipRepeatedHour : expr.skipRepeatedHour;
expr.missingHour = (_a = options.missingHour) !== null && _a !== void 0 ? _a : expr.missingHour;
return expr;
}
get warnings() {
if (!this._warnings) {
const parsedFields = _parse(this.cronString);
this._warnings = flatMap(parsedFields, field => field.warnings);
}
return this._warnings;
}
toString() {
var _a, _b;
const showTzOpts = !this.timezone || !!this.timezone.zoneName;
const timezone = Object.entries({
tz: (_b = (_a = this.timezone) === null || _a === void 0 ? void 0 : _a.toString()) !== null && _b !== void 0 ? _b : 'Local',
skipRepeatedHour: showTzOpts && this.skipRepeatedHour.toString(),
missingHour: showTzOpts && this.missingHour,
}).map(([key, val]) => val && key + ': ' + val).filter(s => s).join(', ');
return `${this.cronString} (${timezone})`;
}
nextDate(afterDate = new Date()) {
var _a;
const fromCronosDate = CronosDate.fromDate(afterDate, this.timezone);
if (((_a = this.timezone) === null || _a === void 0 ? void 0 : _a.fixedOffset) !== undefined) {
return this._next(fromCronosDate).date;
}
const fromTimestamp = afterDate.getTime(), fromLocalTimestamp = fromCronosDate['toUTCTimestamp'](), prevHourLocalTimestamp = CronosDate.fromDate(new Date(fromTimestamp - hourinms), this.timezone)['toUTCTimestamp'](), nextHourLocalTimestamp = CronosDate.fromDate(new Date(fromTimestamp + hourinms), this.timezone)['toUTCTimestamp'](), nextHourRepeated = nextHourLocalTimestamp - fromLocalTimestamp === 0, thisHourRepeated = fromLocalTimestamp - prevHourLocalTimestamp === 0, thisHourMissing = fromLocalTimestamp - prevHourLocalTimestamp === hourinms * 2;
if (this.skipRepeatedHour && thisHourRepeated) {
return this._next(fromCronosDate.copyWith({ minute: 59, second: 60 }), false).date;
}
if (this.missingHour === 'offset' && thisHourMissing) {
const nextDate = this._next(fromCronosDate.copyWith({ hour: fromCronosDate.hour - 1 })).date;
if (!nextDate || nextDate.getTime() > fromTimestamp)
return nextDate;
}
let { date: nextDate, cronosDate: nextCronosDate } = this._next(fromCronosDate);
if (this.missingHour !== 'offset' && nextCronosDate && nextDate) {
const nextDateNextHourTimestamp = nextCronosDate.copyWith({ hour: nextCronosDate.hour + 1 }).toDate(this.timezone).getTime();
if (nextDateNextHourTimestamp === nextDate.getTime()) {
if (this.missingHour === 'insert') {
return nextCronosDate.copyWith({ minute: 0, second: 0 }).toDate(this.timezone);
}
// this.missingHour === 'skip'
return this._next(nextCronosDate.copyWith({ minute: 59, second: 59 })).date;
}
}
if (!this.skipRepeatedHour) {
if (nextHourRepeated && (!nextDate || (nextDate.getTime() > fromTimestamp + hourinms))) {
nextDate = this._next(fromCronosDate.copyWith({ minute: 0, second: 0 }), false).date;
}
if (nextDate && nextDate < afterDate) {
nextDate = new Date(nextDate.getTime() + hourinms);
}
}
return nextDate;
}
_next(date, after = true) {
const nextDate = this._nextYear(after ? date.copyWith({ second: date.second + 1 }) : date);
return {
cronosDate: nextDate,
date: nextDate ? nextDate.toDate(this.timezone) : null
};
}
nextNDates(afterDate = new Date(), n = 5) {
const dates = [];
let lastDate = afterDate;
for (let i = 0; i < n; i++) {
const date = this.nextDate(lastDate);
if (!date)
break;
lastDate = date;
dates.push(date);
}
return dates;
}
_nextYear(fromDate) {
let year = fromDate.year;
let nextDate = null;
while (!nextDate) {
year = this.years.nextYear(year);
if (year === null)
return null;
nextDate = this._nextMonth((year === fromDate.year) ? fromDate : new CronosDate(year));
year++;
}
return nextDate;
}
_nextMonth(fromDate) {
let nextMonthIndex = findFirstFrom(fromDate.month, this.months);
let nextDate = null;
while (!nextDate) {
const nextMonth = this.months[nextMonthIndex];
if (nextMonth === undefined)
return null;
nextDate = this._nextDay((nextMonth === fromDate.month) ? fromDate : new CronosDate(fromDate.year, nextMonth));
nextMonthIndex++;
}
return nextDate;
}
_nextDay(fromDate) {
const days = this.days.getDays(fromDate.year, fromDate.month);
let nextDayIndex = findFirstFrom(fromDate.day, days);
let nextDate = null;
while (!nextDate) {
const nextDay = days[nextDayIndex];
if (nextDay === undefined)
return null;
nextDate = this._nextHour((nextDay === fromDate.day) ? fromDate : new CronosDate(fromDate.year, fromDate.month, nextDay));
nextDayIndex++;
}
return nextDate;
}
_nextHour(fromDate) {
let nextHourIndex = findFirstFrom(fromDate.hour, this.hours);
let nextDate = null;
while (!nextDate) {
const nextHour = this.hours[nextHourIndex];
if (nextHour === undefined)
return null;
nextDate = this._nextMinute((nextHour === fromDate.hour) ? fromDate :
new CronosDate(fromDate.year, fromDate.month, fromDate.day, nextHour));
nextHourIndex++;
}
return nextDate;
}
_nextMinute(fromDate) {
let nextMinuteIndex = findFirstFrom(fromDate.minute, this.minutes);
let nextDate = null;
while (!nextDate) {
const nextMinute = this.minutes[nextMinuteIndex];
if (nextMinute === undefined)
return null;
nextDate = this._nextSecond((nextMinute === fromDate.minute) ? fromDate :
new CronosDate(fromDate.year, fromDate.month, fromDate.day, fromDate.hour, nextMinute));
nextMinuteIndex++;
}
return nextDate;
}
_nextSecond(fromDate) {
const nextSecondIndex = findFirstFrom(fromDate.second, this.seconds), nextSecond = this.seconds[nextSecondIndex];
if (nextSecond === undefined)
return null;
return fromDate.copyWith({ second: nextSecond });
}
}
const maxTimeout = Math.pow(2, 31) - 1;
const scheduledTasks = [];
let runningTimer = null;
function addTask(task) {
if (task['_timestamp'] !== undefined) {
const insertIndex = scheduledTasks.findIndex(t => t['_timestamp'] < task['_timestamp']);
if (insertIndex >= 0)
scheduledTasks.splice(insertIndex, 0, task);
else
scheduledTasks.push(task);
}
}
function removeTask(task) {
const removeIndex = scheduledTasks.indexOf(task);
if (removeIndex >= 0)
scheduledTasks.splice(removeIndex, 1);
if (scheduledTasks.length === 0 && runningTimer) {
clearTimeout(runningTimer);
runningTimer = null;
}
}
function runScheduledTasks(skipRun = false) {
if (runningTimer)
clearTimeout(runningTimer);
const now = Date.now();
const removeIndex = scheduledTasks.findIndex(task => task['_timestamp'] <= now);
const tasksToRun = removeIndex >= 0 ? scheduledTasks.splice(removeIndex) : [];
for (let task of tasksToRun) {
if (!skipRun)
task['_runTask']();
if (task.isRunning) {
task['_updateTimestamp']();
addTask(task);
}
}
const nextTask = scheduledTasks[scheduledTasks.length - 1];
if (nextTask) {
runningTimer = setTimeout(runScheduledTasks, Math.min(nextTask['_timestamp'] - Date.now(), maxTimeout));
}
else
runningTimer = null;
}
function refreshSchedulerTimer() {
for (const task of scheduledTasks) {
task['_updateTimestamp']();
if (!task.isRunning)
removeTask(task);
}
scheduledTasks.sort((a, b) => b['_timestamp'] - a['_timestamp']);
runScheduledTasks(true);
}
class DateArraySequence {
constructor(dateLikes) {
this._dates = dateLikes.map(dateLike => {
const date = new Date(dateLike);
if (isNaN(date.getTime()))
throw new Error('Invalid date');
return date;
}).sort((a, b) => a.getTime() - b.getTime());
}
nextDate(afterDate) {
const nextIndex = this._dates.findIndex(d => d > afterDate);
return nextIndex === -1 ? null : this._dates[nextIndex];
}
}
class CronosTask {
constructor(sequenceOrDates) {
this._listeners = {
'started': new Set(),
'stopped': new Set(),
'run': new Set(),
'ended': new Set(),
};
if (Array.isArray(sequenceOrDates))
this._sequence = new DateArraySequence(sequenceOrDates);
else if (typeof sequenceOrDates === 'string' ||
typeof sequenceOrDates === 'number' ||
sequenceOrDates instanceof Date)
this._sequence = new DateArraySequence([sequenceOrDates]);
else
this._sequence = sequenceOrDates;
}
start() {
if (!this.isRunning) {
this._updateTimestamp();
addTask(this);
runScheduledTasks();
if (this.isRunning)
this._emit('started');
}
return this;
}
stop() {
if (this.isRunning) {
this._timestamp = undefined;
removeTask(this);
this._emit('stopped');
}
return this;
}
get nextRun() {
return this.isRunning ? new Date(this._timestamp) : undefined;
}
get isRunning() {
return this._timestamp !== undefined;
}
_runTask() {
this._emit('run', this._timestamp);
}
_updateTimestamp() {
const nextDate = this._sequence.nextDate(new Date());
this._timestamp = nextDate ? nextDate.getTime() : undefined;
if (!this.isRunning)
this._emit('ended');
}
on(event, listener) {
this._listeners[event].add(listener);
return this;
}
off(event, listener) {
this._listeners[event].delete(listener);
return this;
}
_emit(event, ...args) {
this._listeners[event].forEach((listener) => {
listener.call(this, ...args);
});
}
}
function scheduleTask(cronString, task, options) {
const expression = CronosExpression.parse(cronString, options);
return new CronosTask(expression)
.on('run', task)
.start();
}
function validate(cronString, options) {
try {
CronosExpression.parse(cronString, options);
}
catch {
return false;
}
return true;
}
export { CronosExpression, CronosTask, CronosTimezone, refreshSchedulerTimer, scheduleTask, validate };
//# sourceMappingURL=index.js.map