cc-haha/src/utils/cronFrequency.ts

120 lines
3.5 KiB
TypeScript

/**
* Converts between UI-friendly frequency settings and cron expressions.
* Used by the ScheduledTaskWizard for human-readable schedule configuration.
*/
/** Supported frequency values in the scheduled task UI. */
export type Frequency = 'manual' | 'hourly' | 'daily' | 'weekdays' | 'weekly'
export const FREQUENCY_OPTIONS: { label: string; value: Frequency }[] = [
{ label: 'Manual', value: 'manual' },
{ label: 'Hourly', value: 'hourly' },
{ label: 'Daily', value: 'daily' },
{ label: 'Weekdays', value: 'weekdays' },
{ label: 'Weekly', value: 'weekly' },
]
/** Validate "HH:MM" or "H:MM" time string. Returns [hour, minute] or null. */
function parseTime(time: string): { hour: number; minute: number } | null {
if (!time || typeof time !== 'string') return null
const [h, m] = time.trim().split(':')
const hour = parseInt(h ?? '', 10)
const minute = parseInt(m ?? '', 10)
if (!Number.isFinite(hour) || !Number.isFinite(minute)) return null
if (hour < 0 || hour > 23) return null
if (minute < 0 || minute > 59) return null
return { hour, minute }
}
/**
* Convert a frequency + time to a 5-field cron expression.
* Time format: "HH:MM" (24-hour). Defaults to "09:00" if not provided.
*
* - manual: no cron (returns empty string — task runs on demand)
* - hourly: "0 * * * *"
* - daily + "09:00": "0 9 * * *"
* - weekdays + "09:00": "0 9 * * 1-5"
* - weekly + "09:00": "0 9 * * 1" (Monday)
*/
export function frequencyToCron(frequency: Frequency, time?: string): string {
if (frequency === 'manual') return ''
const parsed = parseTime(time ?? '09:00')
if (!parsed) return '' // malformed time → no-op
const { hour, minute } = parsed
switch (frequency) {
case 'hourly':
return `${minute} * * * *`
case 'daily':
return `${minute} ${hour} * * *`
case 'weekdays':
return `${minute} ${hour} * * 1-5`
case 'weekly':
return `${minute} ${hour} * * 1`
}
}
/**
* Best-effort reverse: guess the frequency + time from a cron expression.
* Returns the closest match; complex crons fall back to { frequency: 'manual' }.
*/
export function cronToFrequency(cron: string): {
frequency: Frequency
time?: string
} {
if (!cron || cron.trim() === '') return { frequency: 'manual' }
const parts = cron.trim().split(/\s+/)
if (parts.length !== 5) return { frequency: 'manual' }
const [minute, hour, dom, month, dow] = parts
// Hourly: "N * * * *"
if (hour === '*' && dom === '*' && month === '*' && dow === '*') {
return { frequency: 'hourly' }
}
// Check if it's a specific hour+minute pattern
if (
dom === '*' &&
month === '*' &&
isNumber(minute!) &&
isNumber(hour!)
) {
const h = parseInt(hour!, 10)
const m = parseInt(minute!, 10)
// Guard against out-of-range values (e.g. malformed cron in existing data)
if (h < 0 || h > 23 || m < 0 || m > 59) {
return { frequency: 'manual' }
}
const timeStr = `${pad(h)}:${pad(m)}`
// Weekdays: "M H * * 1-5"
if (dow === '1-5') {
return { frequency: 'weekdays', time: timeStr }
}
// Weekly: "M H * * 1"
if (dow === '1') {
return { frequency: 'weekly', time: timeStr }
}
// Daily: "M H * * *"
if (dow === '*') {
return { frequency: 'daily', time: timeStr }
}
}
// Can't map to a simple frequency
return { frequency: 'manual' }
}
function isNumber(s: string): boolean {
return /^\d+$/.test(s)
}
function pad(n: number): string {
return String(n).padStart(2, '0')
}