DEV Community

manja316
manja316

Posted on

I rebuilt crontab.guru with three features I needed every week

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.

1. Plain English → cron (the reverse direction)

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".

2. Next 10 actual run times

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:

while (out.length < 10) {
  if (matchesAllFields(d)) out.push(new Date(d));
  d.setMinutes(d.getMinutes() + 1);
}
Enter fullscreen mode Exit fullscreen mode

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.

Smart-skip fixes it:

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);
}
Enter fullscreen mode Exit fullscreen mode

@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.

3. Systemd timer equivalent

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:

[Timer]
OnCalendar=Mon..Fri *-*-* 09:00:00
Persistent=true
RandomizedDelaySec=30

[Install]
WantedBy=timers.target
Enter fullscreen mode Exit fullscreen mode

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).

What this is built on

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.

Edge cases I haven't handled

  • 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 numericMON-FRI,0,6 works in some implementations. Not here.

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.

Top comments (0)