116 lines · 4.3 KB
/** Minimal 5-field POSIX cron (UTC) as used by `on.schedule`: lists, ranges, steps and month/day names. */

const FIELD_RANGES: Array<[number, number]> = [
  [0, 59],
  [0, 23],
  [1, 31],
  [1, 12],
  [0, 6],
];
const MONTHS = ["jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", "nov", "dec"];
const DAYS = ["sun", "mon", "tue", "wed", "thu", "fri", "sat"];
const DAY_FIELD = 2;
const MONTH_FIELD = 3;
const WEEKDAY_FIELD = 4;
const SUNDAY_ALIAS = 7;
const FIELD_COUNT = 5;
const MINUTE_MS = 60_000;
const DAY_MS = 24 * 60 * MINUTE_MS;
const MAX_SEARCH_DAYS = 366 * 4;

export type CronSchedule = {
  minutes: number[];
  hours: number[];
  days: Set<number>;
  months: Set<number>;
  weekdays: Set<number>;
  dayRestricted: boolean;
  weekdayRestricted: boolean;
};

function value(token: string, field: number): number {
  const lower = token.toLowerCase();
  if (field === MONTH_FIELD && MONTHS.includes(lower)) return MONTHS.indexOf(lower) + 1;
  if (field === WEEKDAY_FIELD && DAYS.includes(lower)) return DAYS.indexOf(lower);
  if (!/^\d+$/.test(token)) throw new Error(`invalid cron value '${token}'`);
  const parsed = Number(token);
  return field === WEEKDAY_FIELD && parsed === SUNDAY_ALIAS ? 0 : parsed;
}

function parseField(source: string, field: number): number[] {