export const SCHEDULE_MODES = { DAILY: 'DAILY', WEEKLY: 'WEEKLY', MONTHLY: 'MONTHLY', }; export const WEEK_DAYS = ['MON', 'TUE', 'WED', 'THU', 'FRI', 'SAT', 'SUN']; export const MONTH_DAY_OPTIONS = Array.from({ length: 31 }, (_, idx) => idx + 1); const DEFAULT_TIME = '10:00'; const pad = (value) => value.toString().padStart(2, '0'); const sanitizeWeekDays = (days) => { if (!Array.isArray(days) || days.length === 0) { return ['MON']; } const unique = [...new Set(days.filter((day) => WEEK_DAYS.includes(day)))]; return unique.length ? unique : ['MON']; }; const sanitizeMonthDays = (days) => { if (!Array.isArray(days) || days.length === 0) { return [1]; } const unique = [...new Set(days.map((day) => Number(day)).filter((day) => day >= 1 && day <= 31))]; return unique.length ? unique : [1]; }; const sanitizeTime = (time = DEFAULT_TIME) => { if (typeof time !== 'string') { return DEFAULT_TIME; } const [hour = '10', minute = '00'] = time.split(':'); const parsedHour = Math.min(23, Math.max(0, parseInt(hour, 10) || 0)); const parsedMinute = Math.min(59, Math.max(0, parseInt(minute, 10) || 0)); return `${pad(parsedHour)}:${pad(parsedMinute)}`; }; export const DEFAULT_SCHEDULE_STATE = { mode: SCHEDULE_MODES.DAILY, time: DEFAULT_TIME, weekDays: ['MON'], monthDays: [1], }; export const buildCronExpression = (state = DEFAULT_SCHEDULE_STATE) => { const mode = state?.mode || SCHEDULE_MODES.DAILY; const time = sanitizeTime(state?.time); const [hour, minute] = time.split(':'); if (mode === SCHEDULE_MODES.WEEKLY) { const days = sanitizeWeekDays(state?.weekDays).join(','); return `0 ${minute} ${hour} ? * ${days}`; } if (mode === SCHEDULE_MODES.MONTHLY) { const days = sanitizeMonthDays(state?.monthDays).sort((a, b) => a - b).join(','); return `0 ${minute} ${hour} ${days} * ?`; } return `0 ${minute} ${hour} * * ?`; }; export const DEFAULT_CRON_EXPRESSION = buildCronExpression(DEFAULT_SCHEDULE_STATE); const createParsedState = () => ({ ...DEFAULT_SCHEDULE_STATE, valid: false, }); export const parseCronExpression = (expr) => { const state = createParsedState(); if (!expr || typeof expr !== 'string') { return state; } const tokens = expr.trim().split(/\s+/); if (tokens.length < 6) { return state; } const [, minute, hour, dayOfMonth, , dayOfWeek] = tokens; const time = sanitizeTime(`${hour}:${minute}`); if (dayOfMonth === '*' && dayOfWeek === '?') { return { ...state, valid: true, mode: SCHEDULE_MODES.DAILY, time, }; } if (dayOfMonth === '?' && dayOfWeek && dayOfWeek !== '?') { const weekDays = dayOfWeek.split(',').filter((day) => WEEK_DAYS.includes(day)); return { ...state, valid: true, mode: SCHEDULE_MODES.WEEKLY, time, weekDays: sanitizeWeekDays(weekDays), }; } if (dayOfMonth && dayOfMonth !== '*' && dayOfWeek === '?') { const monthDays = dayOfMonth .split(',') .map((day) => parseInt(day, 10)) .filter((day) => !Number.isNaN(day)); return { ...state, valid: true, mode: SCHEDULE_MODES.MONTHLY, time, monthDays: sanitizeMonthDays(monthDays), }; } return state; }; export const getWeekdayLabel = (day, translate) => { const key = `page.guarantee.week.${day?.toLowerCase?.()}`; return translate ? translate(key) : day; }; const formatWeekdays = (days, translate) => { const normalized = sanitizeWeekDays(days); return normalized.map((day) => getWeekdayLabel(day, translate)).join(' / '); }; const formatMonthDays = (days, translate) => { const normalized = sanitizeMonthDays(days).sort((a, b) => a - b); return normalized .map((day) => (translate ? translate('page.guarantee.schedule.monthDay', { day }) : `${day}`)) .join(' / '); }; export const describeCronExpression = (expr, translate) => { if (!expr) { return ''; } const parsed = parseCronExpression(expr); const time = parsed.time || DEFAULT_TIME; if (!parsed.valid) { return expr; } switch (parsed.mode) { case SCHEDULE_MODES.WEEKLY: return translate ? translate('page.guarantee.schedule.description.weekly', { days: formatWeekdays(parsed.weekDays, translate), time, }) : `Weekly ${formatWeekdays(parsed.weekDays)} ${time}`; case SCHEDULE_MODES.MONTHLY: return translate ? translate('page.guarantee.schedule.description.monthly', { days: formatMonthDays(parsed.monthDays, translate), time, }) : `Monthly ${formatMonthDays(parsed.monthDays)} ${time}`; default: return translate ? translate('page.guarantee.schedule.description.daily', { time }) : `Daily ${time}`; } };