UNPKG

@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
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; }