65 lines
2.1 KiB
TypeScript
65 lines
2.1 KiB
TypeScript
|
|
// 極簡 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)
|
|||
|
|
);
|
|||
|
|
}
|