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)
|
||
);
|
||
}
|