haixunMaster/lib/automation/cron-match.ts

65 lines
2.1 KiB
TypeScript
Raw Normal View History

2026-06-21 12:50:31 +00:00
// 極簡 5 欄位 cron 比對minute hour day-of-month month day-of-week。
// 支援星號、步進(星號斜線n)、列表(a,b,c)、範圍(a-b)、單一數字。
// 給 worker 每分鐘輪詢時判斷規則是否「到點」。
function fieldMatches(field: string, value: number, min: number, max: number): boolean {
if (field === "*") return true;
for (const part of field.split(",")) {
const token = part.trim();
if (token === "") continue;
// step: */n 或 a-b/n 或 */n
const stepMatch = token.match(/^(\*|\d+(?:-\d+)?)\/(\d+)$/);
if (stepMatch) {
const step = parseInt(stepMatch[2], 10);
if (step <= 0) continue;
let rangeStart = min;
let rangeEnd = max;
if (stepMatch[1] !== "*") {
const rangeParts = stepMatch[1].split("-");
rangeStart = parseInt(rangeParts[0], 10);
rangeEnd = rangeParts[1] !== undefined ? parseInt(rangeParts[1], 10) : max;
}
if (value < rangeStart || value > rangeEnd) continue;
if ((value - rangeStart) % step === 0) return true;
continue;
}
// range: a-b
const rangeMatch = token.match(/^(\d+)-(\d+)$/);
if (rangeMatch) {
const start = parseInt(rangeMatch[1], 10);
const end = parseInt(rangeMatch[2], 10);
if (value >= start && value <= end) return true;
continue;
}
// single
if (/^\d+$/.test(token) && parseInt(token, 10) === value) return true;
}
return false;
}
export function isValidCron(expr: string): boolean {
const fields = expr.trim().split(/\s+/);
return fields.length === 5;
}
export function cronMatches(expr: string, date: Date = new Date()): boolean {
const fields = expr.trim().split(/\s+/);
if (fields.length !== 5) return false;
const [minute, hour, dom, month, dow] = fields;
const normalizedDow = date.getDay(); // 0-6 (Sun=0)
return (
fieldMatches(minute, date.getMinutes(), 0, 59) &&
fieldMatches(hour, date.getHours(), 0, 23) &&
fieldMatches(dom, date.getDate(), 1, 31) &&
fieldMatches(month, date.getMonth() + 1, 1, 12) &&
fieldMatches(dow, normalizedDow, 0, 6)
);
}