Explain cron expressions in plain English. Preview next run times. Catch common gotchas (Feb 31, DOM+DOW overlap, accidental every-minute runs) before they fire. Handles @daily/@hourly/@yearly aliases and named weekdays/months (MON-FRI, JAN,JUL).
Free forever gift from vøiddo.
$ cronwtf "0 9 * * 1-5"
0 9 * * 1-5
→ At 9:00 AM, on weekdays
Next 5 runs:
2026-04-23 09:00:00 in 10h
2026-04-24 09:00:00 in 1d
2026-04-27 09:00:00 in 4d
2026-04-28 09:00:00 in 5d
2026-04-29 09:00:00 in 6d
Nobody remembers cron syntax. 0 */2 * * * — is that every 2 hours or every 2 minutes? Does 0 0 15 * 1 run on the 15th OR on Mondays? (Answer: both — cron uses OR for DOM+DOW, and that's caused outages.) Does 0 0 31 2 * ever run? (Answer: never — Feb doesn't have a 31st.)
cronwtf is the quick sanity check you run before pasting into your crontab:
- explains the expression in one readable sentence,
- previews the next N runs in your local timezone,
- warns about the 4 most common foot-guns: impossible dates, DOM+DOW overlap, every-minute runs, leap-year-only rules,
- diffs two expressions field-by-field so you can see what changed in a PR,
- lists 15 common patterns if you just want the recipe,
- estimates frequency over 30 days (runs/day, runs/week, runs/month).
npm install -g @v0idd0/cronwtfOr one-shot with npx:
npx -y @v0idd0/cronwtf "@hourly"# Plain explain + next 5 runs
cronwtf "0 9 * * 1-5"
# Aliases work
cronwtf @daily
cronwtf @hourly -n 3
# Named months/weekdays
cronwtf "0 9 * * MON-FRI"
cronwtf "0 0 1 JAN,JUL *"
# Catch gotchas (exit code 2 on impossible dates)
cronwtf "0 0 31 2 *"
# Machine-readable output
cronwtf "*/15 * * * *" --json
# Frequency estimate
cronwtf "0 9 * * 1-5" --freq
# Field-level diff between two expressions
cronwtf diff "0 9 * * *" "0 10 * * 1-5"
# 15 common patterns with their English meanings
cronwtf patterns
# CI validator: exit 0 (valid) / 1 (invalid) / 2 (valid but will-never-fire)
cronwtf "$CRON_EXPR" --validate && echo OK
# Times in UTC instead of local
cronwtf @daily --utc| Pattern | Warning |
|---|---|
0 0 31 2 * |
ERROR — Day 31 does not exist in February. Never fires. |
0 0 15 * 1 |
WARN — DOM and DOW set. Cron runs when either matches (OR, not AND). |
* * * * * |
INFO — Fires every minute. Usually you want */5 or 0 * * * *. |
0 0 29 2 * |
INFO — Feb 29 only exists in leap years. ~4-year gaps. |
These are the top crash causes in real cron outages. Catching them at paste-time is cheaper than at 3 AM.
| Flag | Description |
|---|---|
-n, --next <N> |
Next N run times (default 5) |
-j, --json |
Machine-readable output |
-v, --validate |
Exit only; code 0=valid, 1=invalid, 2=never-fires |
--freq |
Runs/day, /week, /month over next 30 days |
--no-warnings |
Skip gotcha detection |
--utc |
Print times in UTC instead of local |
-h, --help |
Show help |
--version |
Print version |
| Command | Description |
|---|---|
cronwtf diff <a> <b> |
Show field-level differences between two expressions |
cronwtf patterns |
List 15 common cron recipes with English meanings |
| Syntax | Example | Meaning |
|---|---|---|
* |
* * * * * |
Any value |
*/N |
*/15 * * * * |
Every N units |
A-B |
0 9 * * 1-5 |
Range |
A,B,C |
0 12 * * 1,3,5 |
List |
A-B/N |
0 9-17/2 * * 1-5 |
Range with step |
@yearly |
— | 0 0 1 1 * |
@annually |
— | 0 0 1 1 * |
@monthly |
— | 0 0 1 * * |
@weekly |
— | 0 0 * * 0 |
@daily / @midnight |
— | 0 0 * * * |
@hourly |
— | 0 * * * * |
| Named months | JAN, FEB, ..., DEC |
1–12 |
| Named weekdays | SUN, MON, ..., SAT |
0–6 |
const {
parseCron, // "0 9 * * 1-5" → "At 9:00 AM, on weekdays"
getNextRuns, // (expr, count) → Date[]
isValidCron, // (expr) → boolean
analyzeWarnings, // (expr) → Warning[] (DOM_AND_DOW, IMPOSSIBLE_DATE, etc.)
diffExpressions, // (a, b) → [{field, a, b}, ...]
describeFrequency,// (expr, windowDays) → {runsInWindow, runsPerDay, ...}
normalizeAliases, // "@daily" → "0 0 * * *"
COMMON_PATTERNS, // Array of {expr, meaning}
} = require('@v0idd0/cronwtf/src/parser');
// In a PR-bot:
for (const expr of pullRequest.changedCronExpressions) {
const warnings = analyzeWarnings(expr);
if (warnings.some(w => w.level === 'error')) {
github.comment(`⚠ ${expr} — ${warnings[0].message}`);
}
}| Code | Meaning |
|---|---|
0 |
Valid, no errors |
1 |
Invalid syntax |
2 |
Syntactically valid but will never fire (impossible-date warning) |
Useful in CI:
- run: cronwtf "$CRON_EXPR" --validatevøiddo builds sharp, free-forever CLIs for devs who are tired of paywalls:
@v0idd0/jsonyo— JSON that yells at you@v0idd0/tokcount— token counter for 60+ LLMs@v0idd0/ctxstuff— stuff a repo into an LLM context@v0idd0/promptdiff— diff two prompts@v0idd0/httpwut— HTTP debugger@v0idd0/gitstats— local git analytics@v0idd0/licenseme— LICENSE generator + detector@v0idd0/envguard— .env validator + secret scanner@v0idd0/depcheck— offline CVE scanner + unused-deps@v0idd0/logparse— structured log parser + aggregator
Full catalog: voiddo.com/tools.
MIT © vøiddo — free forever, no asterisks.
- Docs: https://voiddo.com/tools/cronwtf/
- Source: https://github.com/voidd0/cronwtf
- npm: https://npmjs.com/package/@v0idd0/cronwtf
- Studio: https://voiddo.com
- Issues: https://github.com/voidd0/cronwtf/issues
- Support: support@voiddo.com