@@ -34,7 +34,92 @@ export const Scheduler = {
3434}
3535
3636function computeNextRun(expression: string): Date {
37- // TODO: Parse cron expression and compute next run time
38- // Using a Rust cron parser exposed via FFI
39- return new Date(Date.now() + 60_000)
37+ // Parse cron expression and compute next run time
38+ // This uses a built-in TS cron parser fallback as the Rust FFI boundary
39+ // for deno_core ops is not yet fully initialized/exposed in the runtime context.
40+
41+ try {
42+ const parts = expression.trim().split(/\s+/);
43+ if (parts.length !== 5 && parts.length !== 6) {
44+ throw new Error('Invalid cron expression length');
45+ }
46+
47+ const now = new Date();
48+ // Start checking from the next minute
49+ let next = new Date(now.getFullYear(), now.getMonth(), now.getDate(), now.getHours(), now.getMinutes() + 1, 0, 0);
50+
51+ const matchField = (val: number, field: string): boolean => {
52+ if (field === '*') return true;
53+ if (field.includes(',')) {
54+ return field.split(',').some(part => matchField(val, part));
55+ }
56+ if (field.includes('/')) {
57+ const [range, step] = field.split('/');
58+ const stepNum = parseInt(step, 10);
59+ if (range === '*') return val % stepNum === 0;
60+ if (range.includes('-')) {
61+ const [start, end] = range.split('-');
62+ return val >= parseInt(start, 10) && val <= parseInt(end, 10) && (val - parseInt(start, 10)) % stepNum === 0;
63+ }
64+ return val % stepNum === 0;
65+ }
66+ if (field.includes('-')) {
67+ const [start, end] = field.split('-');
68+ return val >= parseInt(start, 10) && val <= parseInt(end, 10);
69+ }
70+ return val === parseInt(field, 10);
71+ };
72+
73+ const hasSec = parts.length === 6;
74+ const offset = hasSec ? 1 : 0;
75+ const minField = parts[0 + offset];
76+ const hourField = parts[1 + offset];
77+ const domField = parts[2 + offset];
78+ const monField = parts[3 + offset];
79+ const dowField = parts[4 + offset];
80+
81+ // Search forward up to 5 years
82+ for (let i = 0; i < 5 * 365 * 24 * 60; i++) {
83+ const monMatch = matchField(next.getMonth() + 1, monField);
84+ if (!monMatch) {
85+ next.setDate(1);
86+ next.setMonth(next.getMonth() + 1);
87+ next.setHours(0);
88+ next.setMinutes(0);
89+ continue;
90+ }
91+
92+ const domMatch = matchField(next.getDate(), domField);
93+ const dowMatch = matchField(next.getDay(), dowField);
94+
95+ const dayMatch = (domField === '*' || dowField === '*')
96+ ? (domMatch && dowMatch)
97+ : (domMatch || dowMatch);
98+
99+ if (!dayMatch) {
100+ next.setDate(next.getDate() + 1);
101+ next.setHours(0);
102+ next.setMinutes(0);
103+ continue;
104+ }
105+
106+ const hourMatch = matchField(next.getHours(), hourField);
107+ if (!hourMatch) {
108+ next.setHours(next.getHours() + 1);
109+ next.setMinutes(0);
110+ continue;
111+ }
112+
113+ const minMatch = matchField(next.getMinutes(), minField);
114+ if (minMatch) {
115+ return next;
116+ }
117+
118+ next.setMinutes(next.getMinutes() + 1);
119+ }
120+
121+ return new Date(Date.now() + 60_000);
122+ } catch (err) {
123+ return new Date(Date.now() + 60_000);
124+ }
40125}
0 commit comments