Cron Expression Parser / Explainer
Explain any cron expression in plain English — per-field validation and the next 10 runs in any timezone, with DST flags — entirely on this device.
History
Valid expressions you run are kept here — on this device only.
The plain-English description appears here.
| # | UTC | UTC time | DST |
|---|---|---|---|
| Upcoming runs for a valid expression appear here. | |||
—Press ? for shortcuts
Do it offline:$ systemd-analyze calendar --iterations=3 'Mon..Fri *-*-* 09:00:00'
How to use it
- Paste or type the expression — five fields, six when the first is seconds, or a macro like @daily; JAN and MON style names work in any case.
- Read the plain-English sentence under the input and the badge naming the dialect; errors point at the exact token that failed.
- Pick a timezone for the run table — your device's zone is marked — and check the next ten runs, each with UTC, local wall time and a DST flag.
- Copy the crontab line or the canonical expression, download the whole breakdown as JSON, or share the state as a link.
A cron expression tells you nothing until you decode it. Paste one — from a crontab, a code review, or an AI answer you want to verify — and this page reads it back in plain English, validates every field, and lists the next ten run times in the timezone you choose, with UTC beside local and daylight-saving days flagged. Everything happens on this device.
How it works
Reading is positional: minute, hour, day of month, month, day of week — one field each, any whitespace between them. A field accepts a star, a single number, a comma list, a range, or a stepped form such as */10, and month or weekday names may stand wherever their numbers would. A sixth leading field means seconds — the badge says so — while a plain five-field line fires at second zero.
Before anything is explained, the expression is normalized: names become numbers, a weekday 7 becomes 0, macros unfold into their five fields with a note, and redundant spellings collapse — */1 is a star, and a list whose terms overlap is folded into one. The canonical line printed under the input is exactly what the description and the run table are computed from, so the three can never disagree.
Run times come from stepping the schedule forward from the moment you run it, evaluated on the wall clock of the zone you picked. Cron has no timezone field of its own — a crontab fires on its host's clock — so the picker answers the practical question: when does this line land if the server keeps São Paulo time, or UTC, or yours.
Twice a year most zones bend the clock, and schedules bend with it. Where a zone springs forward, one wall time simply does not occur that morning; where it falls back, one recurs. Rows on such days carry a gap or overlap flag, and the table prints what the arithmetic actually yields — a skipped time surfaces at the shifted instant, a repeated one is listed at its first occurrence.
Some expressions are grammatically perfect and can still never fire — day 30 of February clears every per-field bound. Instead of an empty table, the iterator scans up to eight years ahead; if nothing matches in that window, you get a plain error saying so. Rare-but-real schedules survive the same scan honestly: a February 29 job shows the two leap days the window holds rather than ten rows.
Examples
*/5 * * * *reads as Every 5 minutes
run 1 2026-08-04T00:05:00Z (UTC)
run 2 2026-08-04T00:10:00Z (UTC)
run 3 2026-08-04T00:15:00Z (UTC)The classic five-minute heartbeat. From the pinned start of 2026-08-04T00:00:30Z the first row is 00:05 — runs are strictly after the starting instant, so a boundary that has just passed is never listed.
0 9 * * 1-5reads as At 09:00, Monday through Friday
run 1 2026-08-04T09:00:00-03:00 (America/Sao_Paulo)
run 2 2026-08-05T09:00:00-03:00 (America/Sao_Paulo)
run 3 2026-08-06T09:00:00-03:00 (America/Sao_Paulo)São Paulo keeps UTC−03:00 with no clock changes, so every row carries a −03:00 offset: the 09:00 wall time in the local column is 12:00 UTC. The weekday range 1-5 lands the first three runs on Tuesday, Wednesday and Thursday.
0 0 29 2 *reads as At 00:00, on day 29 of the month, only in February
run 1 2028-02-29T00:00:00Z (UTC)
run 2 2032-02-29T00:00:00Z (UTC)Ten runs were requested; two exist. From an August 2026 start the eight-year window contains exactly two 29ths of February, and the first is 2028-02-29 — a shorter table is the honest answer, not an error.
Every example on this page is executed against the same engine that powers the tool, on every build.
Gotchas worth knowing
Gap days and overlap days, flagged per row
On a spring-forward morning the skipped wall time never happens — a 01:30 job cannot run at 01:30 that day, and its row shows the shifted instant with a gap flag. On the fall-back morning the clock passes the same time twice; the row is flagged overlap and lists the first pass.
Sunday is 0 — and also 7
Both spellings are accepted; the canonical line always prints 0. A weekday range that touches 7 folds across the week boundary, so 5-7 becomes the list 0,5,6 — Friday, Saturday, Sunday.
MON, mon and Mon are the same day
Month and weekday names are case-blind and normalize to numbers: 0 9 * jan-MAR Mon canonicalizes to 0 9 * 1-3 1. Numbers are what the daemon compares, so numbers are what the canonical line shows.
Tabs and newlines separate like spaces
Any run of whitespace splits fields, so a line pasted out of a tab-aligned crontab — or one that picked up a line break in a chat window — parses the same as a single-spaced one.
A step can outrun its field
*/90 in the minute field is legal but only ever matches minute 0, and a warning names the single value that survives. If you meant every 90 minutes: no one cron line can say that — it takes two, one for each half of the three-hour cycle.
@reboot is a trigger, not a schedule
It fires once when the daemon starts, so there is no calendar to preview: the description is fixed, the run table stays empty, and a note explains why. The other macros — @hourly through @yearly — expand to ordinary five-field lines.
Restricting both day fields widens the schedule
With day of month and day of week both set, vixie cron runs when either one matches. 0 0 13 * 5 fires every Friday and every 13th — the run table shows the union, and a warning chip points it out before production does.
Common errors
Expected 5 fields, found 4 — did you omit the minute field?- Field counting happens before any field is read: with one column missing there is no safe guess about which one you dropped, and in practice the lost column is almost always the leading minute. Restore it and the rest line up again.
Field 3 (day of month): '32' is outside 1–31- Every field has fixed bounds and the diagnostic names the one you crossed. The offending token is anchored in place — a jump-to-error control selects it, so the fix lands exactly where the problem is.
Field 2 (hour): "*/0" uses step 0, which would never advance — use a step of 1 or more.- A step divides a field into strides, and a stride of zero never moves forward. */1 is every hour written verbosely; the canonical line folds it back to a plain star.
Field 5 (day of week): "5L" is Quartz syntax — L, W, # and ? do not exist in classic cron.- 5L — the last Friday of the month — is a Quartz idea with no classic equivalent, so dropping the L quietly would schedule something different. The dialect badge offers a one-click hand-off that carries your expression to the Quartz page unchanged.
This expression never matches a real date.- Raised only after the iterator proves it: a day-and-month pair like February 30 passes every per-field check yet matches no calendar day in the eight-year scan. Test the day-of-month against the shortest month you listed.
Day-of-month and day-of-week are both restricted — cron runs when EITHER matches, which surprises most people.- A warning, not an error — the schedule is valid, and the table already shows the union of the two day fields. If you wanted both conditions at once, keep one day field as a star and enforce the other inside the job.
Input is larger than the 1,024 character limit for this tool. For a quick next-run preview use the CLI: systemd-analyze calendar --iterations=3 'Mon..Fri *-*-* 09:00:00'- A refusal, never a truncation — a partly-read schedule could silently mean something else. Real expressions are tens of characters, so an overrun this size usually means a whole file landed in the field. The suggested command previews upcoming events from a terminal, in systemd's calendar syntax rather than cron's.
FAQ
What is a cron job?
A command that the cron daemon runs for you on a repeating schedule. Each line of a crontab holds a schedule — the five-field expression this page reads — followed by the command to start. The daemon wakes every minute, compares the clock against each line, and launches whatever matches.
What does * * * * * mean?
Every field unrestricted: the job runs every minute, 1,440 times a day. Paste it and the read-back answers “Every minute” — useful as a smoke test, expensive as a schedule, which is why most people really want */5 or wider.
Is day-of-week 0 or 7 for Sunday?
POSIX says 0–6 with Sunday at 0; the crontab(5) grammar most Linux systems ship also takes 7 for Sunday. This page accepts both and always prints 0 in the canonical line — the one spelling every implementation agrees on.
Why would a nightly job run twice — or not at all — on one day of the year?
Because clocks jump. When daylight saving starts, times inside the skipped hour do not occur that day; when it ends, times inside the repeated hour come around twice. Pick your zone and the run table flags exactly those days as gap or overlap, so you can move the job out of the risky hour.
Can I verify an AI-generated cron expression without running it?
That is this page's job: paste the suggestion and compare the plain-English read-back and the next ten concrete run times against what you asked for. Everything is computed in your browser — zero bytes are uploaded — so nothing needs deploying to find out what a line really does.
Does this page understand Quartz or AWS EventBridge cron?
It recognizes them and says so: a ? L W or # token, a seventh field, or a cron(…) wrapper raises a badge with a one-click hand-off to the matching tool, expression carried along. Parsing here stays classic — five fields, optional seconds, and the macros.
Keyboard shortcuts
- ⌘/Ctrl + Enter
- Recompute the run table from a fresh current moment.
- ⌘/Ctrl + S
- Download the breakdown as a .json file.
- ⌘/Ctrl + C
- Copy the JSON breakdown when no text is selected.
- ⌘/Ctrl + Shift + C
- Copy the JSON breakdown even over an active selection.
- t
- Switch to the next theme.
- ? or ⌘/Ctrl + /
- Open the keyboard-shortcut reference.
- Esc
- Close the topmost overlay and return focus.