Tools: Complete Guide to I rebuilt crontab.guru with three features I needed every week

Tools: Complete Guide to I rebuilt crontab.guru with three features I needed every week

1. Plain English → cron (the reverse direction)

2. Next 10 actual run times

3. Systemd timer equivalent

What this is built on

Edge cases I haven't handled crontab.guru is one of the best small dev tools on the internet. It explains a cron expression in plain English instantly. I've used it for ~8 years. But every time I came back, I wanted three things it doesn't do. So I built them. Live at cron.protodex.io — same dark-mode style, single page, no signup, runs offline. Here's what's different. crontab.guru is cron → English. Once you've written cron three times, you don't need the explanation. You need help going the other way: "I want this to run every weekday morning at 9 — what's the cron?" I added a generator. Type "every 15 minutes during business hours" → get back */15 9-17 * * 1-5. It's not full natural language (I haven't gone to NLP), but the common patterns work: "every X minutes", "every X hours", "daily at HH:MM", "weekly on DAY at HH:MM", "monthly on day N". When you're debugging "did my cron actually fire?" you want to see: "next run is in 47 minutes, then 4 hours from now, then..." Most cron tools show the expression but not the upcoming firings. The naive approach — increment a Date by one minute, test each — works but dies on sparse schedules: That works for */5 * * * * (every 5 min) but dies on @yearly — you'd walk 525,600 minutes (one year) just to find the second run. Slow on V8, times out in browser. @yearly now resolves in 3ms instead of timing out. Also correctly handles leap-year crons like 0 0 29 2 * — returns Feb 29 of 2028, 2032, 2036, 2040, 2044. Most modern Linux is systemd-first. Whenever I converted a cron job into a managed service, I had to mentally translate 0 9 * * 1-5 into: The tool generates the full [Timer] block side-by-side with the cron explanation. With the sensible defaults baked in: Persistent=true (catch up on missed runs after reboot), RandomizedDelaySec=30 (avoid thundering herds on tight-fleet deploys). Pure vanilla JS. No framework. No bundler. ~6KB minified. Source structure is two files: index.html (which has all the JS inline) and the shared protodex stylesheet. Hosted on Cloudflare Pages free tier. Works offline once the page loads — there's no API call at runtime, the entire parser + describer + next-run calculator + systemd generator runs in your browser. Patches welcome if anyone hits these. Try it: cron.protodex.io This is part of protodex.io — a small set of free tools focused on dev utilities and Indian compliance. Templates let you quickly answer FAQs or store snippets for re-use. Hide child comments as well For further actions, you may consider blocking this person and/or reporting abuse

Code Block

Copy

while (out.length < 10) { if (matchesAllFields(d)) out.push(new Date(d)); d.setMinutes(d.getMinutes() + 1); } while (out.length < 10) { if (matchesAllFields(d)) out.push(new Date(d)); d.setMinutes(d.getMinutes() + 1); } while (out.length < 10) { if (matchesAllFields(d)) out.push(new Date(d)); d.setMinutes(d.getMinutes() + 1); } while (out.length < 10) { if (!matchesField(c.month, d.getMonth()+1)) { d.setMonth(d.getMonth() + 1, 1); // jump to next month d.setHours(0, 0, 0, 0); continue; } if (!matchesField(c.dom, d.getDate())) { d.setDate(d.getDate() + 1); // jump to next day d.setHours(0, 0, 0, 0); continue; } // ... same for hour, minute out.push(new Date(d)); d.setMinutes(d.getMinutes() + 1); } while (out.length < 10) { if (!matchesField(c.month, d.getMonth()+1)) { d.setMonth(d.getMonth() + 1, 1); // jump to next month d.setHours(0, 0, 0, 0); continue; } if (!matchesField(c.dom, d.getDate())) { d.setDate(d.getDate() + 1); // jump to next day d.setHours(0, 0, 0, 0); continue; } // ... same for hour, minute out.push(new Date(d)); d.setMinutes(d.getMinutes() + 1); } while (out.length < 10) { if (!matchesField(c.month, d.getMonth()+1)) { d.setMonth(d.getMonth() + 1, 1); // jump to next month d.setHours(0, 0, 0, 0); continue; } if (!matchesField(c.dom, d.getDate())) { d.setDate(d.getDate() + 1); // jump to next day d.setHours(0, 0, 0, 0); continue; } // ... same for hour, minute out.push(new Date(d)); d.setMinutes(d.getMinutes() + 1); } [Timer] OnCalendar=Mon..Fri *-*-* 09:00:00 Persistent=true RandomizedDelaySec=30 [Install] WantedBy=timers.target [Timer] OnCalendar=Mon..Fri *-*-* 09:00:00 Persistent=true RandomizedDelaySec=30 [Install] WantedBy=timers.target [Timer] OnCalendar=Mon..Fri *-*-* 09:00:00 Persistent=true RandomizedDelaySec=30 [Install] WantedBy=timers.target - 6-field Quartz format (with leading seconds) — only standard 5-field cron - Year field — some forks (e.g. Synology) accept a 6th year field. Not supported. - Named days with mixed numeric — MON-FRI,0,6 works in some implementations. Not here.