@adaptabletools/adaptable
Version:
Powerful AG Grid extension which provides advanced, cutting-edge functionality to meet all DataGrid requirements
137 lines (136 loc) • 4.53 kB
JavaScript
const MINUTE_RANGE = [0, 59];
const HOUR_RANGE = [0, 23];
const DAY_OF_MONTH_RANGE = [1, 31];
const MONTH_RANGE = [1, 12];
const FIELD_LABELS = ['minute', 'hour', 'day-of-month', 'month', 'day-of-week'];
export function parseCronExpression(expression) {
if (!expression || typeof expression !== 'string') {
throw new Error('Cron expression is empty');
}
const parts = expression.trim().split(/\s+/);
if (parts.length !== 5) {
throw new Error(`Expected 5 cron fields (minute hour day-of-month month day-of-week) but got ${parts.length}`);
}
const minute = parseField(parts[0], MINUTE_RANGE, FIELD_LABELS[0]);
const hour = parseField(parts[1], HOUR_RANGE, FIELD_LABELS[1]);
const dayOfMonth = parseField(parts[2], DAY_OF_MONTH_RANGE, FIELD_LABELS[2]);
const month = parseField(parts[3], MONTH_RANGE, FIELD_LABELS[3]);
const dayOfWeek = parseDayOfWeekField(parts[4]);
return {
minute,
hour,
dayOfMonth,
month,
dayOfWeek,
dayOfMonthIsStar: parts[2] === '*',
dayOfWeekIsStar: parts[4] === '*',
};
}
export function isCronExpressionValid(expression) {
try {
parseCronExpression(expression);
return true;
}
catch {
return false;
}
}
export function getNextCronOccurrence(expression, fromDate = new Date()) {
const cron = parseCronExpression(expression);
const next = new Date(fromDate.getTime());
next.setSeconds(0, 0);
next.setMinutes(next.getMinutes() + 1);
const horizon = new Date(next.getTime());
horizon.setFullYear(horizon.getFullYear() + 4);
while (next < horizon) {
if (!cron.month.has(next.getMonth() + 1)) {
next.setDate(1);
next.setHours(0, 0, 0, 0);
next.setMonth(next.getMonth() + 1);
continue;
}
if (!matchesDay(cron, next)) {
next.setHours(0, 0, 0, 0);
next.setDate(next.getDate() + 1);
continue;
}
if (!cron.hour.has(next.getHours())) {
next.setMinutes(0, 0, 0);
next.setHours(next.getHours() + 1);
continue;
}
if (!cron.minute.has(next.getMinutes())) {
next.setSeconds(0, 0);
next.setMinutes(next.getMinutes() + 1);
continue;
}
return next;
}
return null;
}
function matchesDay(cron, date) {
const domMatches = cron.dayOfMonth.has(date.getDate());
const dowMatches = cron.dayOfWeek.has(date.getDay());
if (cron.dayOfMonthIsStar && cron.dayOfWeekIsStar) {
return true;
}
if (cron.dayOfMonthIsStar) {
return dowMatches;
}
if (cron.dayOfWeekIsStar) {
return domMatches;
}
return domMatches || dowMatches;
}
function parseField(field, range, label) {
const [min, max] = range;
const values = new Set();
const segments = field.split(',');
for (const raw of segments) {
const segment = raw.trim();
if (segment === '') {
throw new Error(`Invalid ${label} field "${field}"`);
}
const [rangePart, stepPart] = segment.split('/');
const step = stepPart === undefined ? 1 : Number(stepPart);
if (!Number.isInteger(step) || step <= 0) {
throw new Error(`Invalid step value in ${label} field "${segment}"`);
}
let from;
let to;
if (rangePart === '*') {
from = min;
to = max;
}
else if (rangePart.includes('-')) {
const [fromStr, toStr] = rangePart.split('-');
from = Number(fromStr);
to = Number(toStr);
}
else {
from = Number(rangePart);
to = from;
}
if (!Number.isInteger(from) || !Number.isInteger(to)) {
throw new Error(`Invalid ${label} field "${segment}"`);
}
if (from < min || to > max || from > to) {
throw new Error(`Out-of-range ${label} field "${segment}" (expected ${min}-${max})`);
}
for (let v = from; v <= to; v += step) {
values.add(v);
}
}
if (values.size === 0) {
throw new Error(`Empty ${label} field`);
}
return values;
}
function parseDayOfWeekField(field) {
const extended = parseField(field, [0, 7], 'day-of-week');
const normalised = new Set();
for (const value of extended) {
normalised.add(value === 7 ? 0 : value);
}
return normalised;
}