Cron Expression Explainer — Plain English, Next Run Times and Timezone Checker
Paste a cron expression and read it back in plain English, with the next 12 run times in
whatever timezone you actually care about. The catch nobody warns you about is that
“cron” is not one language. 0 0 * * 1 means Monday on
Linux and Sunday in Quartz. A five-field string is a syntax error to Spring. ?
is mandatory on AWS and meaningless on a server. So this tool parses against the platform you
pick — and then shows you what the same string would do on the other seven.
The five fields, and the sixth that catches people out
A classic crontab line is five fields separated by spaces, and they are always in this order:
| Position | Field | Range | Notes |
|---|---|---|---|
| 1 | minute | 0–59 | |
| 2 | hour | 0–23 | 24-hour clock, no AM/PM |
| 3 | day of month | 1–31 | see the OR trap below |
| 4 | month | 1–12 or JAN–DEC | |
| 5 | day of week | 0–7 or SUN–SAT | 0 and 7 are both Sunday |
Four characters do all the work. * means every value. , lists them (1,15). - is a range (9-17). / is a step, so */15 is every fifteenth. That is the entire language — everything else is a platform extension.
The sixth field is where it goes wrong. Quartz, Spring and Azure Functions put seconds first, so their expressions have six fields and everything shifts one place right. AWS EventBridge also uses six, but its extra field is a year on the end, not seconds at the front. Paste a five-field line into any of them and you get either a rejection or, worse, a schedule that runs sixty times more often than you meant. That is why this page asks which scheduler you are targeting before it tells you anything.
Day-of-month and day-of-week are an OR, not an AND
This is the single most expensive misunderstanding in cron, and it is genuinely counter-intuitive. In standard Vixie cron — Linux, Kubernetes, Jenkins, GitHub Actions — when both the day-of-month and day-of-week fields are restricted, the job runs when either one matches.
So 0 0 13 * 5 is not “midnight on Friday the 13th”. It is midnight on every 13th, and also midnight every Friday — about 60 runs a year instead of one or two. People discover this when a monthly report starts arriving weekly.
The rule only applies when both fields are restricted. If either is *, the other simply wins, which is why 0 0 * * 5 (every Friday) and 0 0 13 * * (every 13th) both behave exactly as you would expect.
There is no way to express a true AND in standard cron. The usual workaround is to schedule the broader of the two and test the date inside the job:
0 0 13 * * [ "$(date +\%u)" = "5" ] && /path/to/job
Quartz and AWS EventBridge dodge the problem entirely by making you write ? in whichever day field you are not using, so only one of them is ever active. This tool warns you the moment both fields are set on a dialect where the OR applies.
Timezones and daylight saving break more cron jobs than syntax errors do
Cron schedules wall-clock time, not elapsed time. It looks at the clock on the wall, and if the clock says the job is due, it runs. Twice a year that clock does something strange, and two predictable failures follow.
Spring forward: the run that never happens
When clocks jump from 02:00 to 03:00, the hour in between does not exist. A job scheduled for 30 2 * * * is simply skipped that day — no error, no log line, no alert. If that job was your nightly backup, you have a hole in your backups exactly once a year, and nothing tells you.
Fall back: the run that happens twice
When clocks go from 03:00 back to 02:00, the 02:00–03:00 hour is replayed. That same job runs twice, roughly an hour apart. If it is not idempotent — a billing run, an email send, a counter increment — you have just done it to every customer twice.
The run list on this page flags both cases explicitly, marking skipped runs and repeated hours against whichever timezone you select. The practical fix is to avoid scheduling anything between 01:00 and 03:00 local time, or to run in UTC, which has no daylight saving at all. Running in UTC means your “9am job” drifts an hour twice a year relative to office hours — usually the lesser evil, and always the more predictable one.
Where the timezone comes from also varies. A Linux crontab uses the server timezone, or a TZ= line at the top of the file. A Kubernetes CronJob is UTC unless you set spec.timeZone, which only exists from 1.27. AWS EventBridge cron() rules are UTC with no option at all. GitHub Actions is UTC, always.
The same string means different things on different platforms
Eight schedulers, eight sets of rules. These are the differences that actually change what runs when:
| Platform | Fields | Sunday is | Seconds | ? | L / W / # |
|---|---|---|---|---|---|
| Unix / crontab | 5 | 0 or 7 | no | no | no |
| Kubernetes CronJob | 5 | 0 or 7 | no | no | no |
| Quartz | 6 or 7 | 1 | yes, first | required | yes |
| Spring @Scheduled | 6 | 0 or 7 | yes, first | allowed | no |
| AWS EventBridge | 6 | 1 | no (year last) | required | yes |
| Azure NCRONTAB | 6 | 0 | yes, first | no | no |
| Jenkins | 5 | 0 or 7 | no | no | no (has H) |
| GitHub Actions | 5 | 0 or 7 | no | no | no |
The day numbering is the dangerous one, because a Quartz expression copied from a Linux crontab is still valid — it just runs a day early, every time, for ever. In Quartz and AWS, Sunday is 1 and Saturday is 7. Everywhere else Sunday is 0. So 1 means Monday on your server and Sunday in your Java scheduler, and nothing will ever tell you.
The comparison panel above the examples shows, for whatever you have typed, which of the eight accept it and which of those give it a different meaning. If you are migrating a schedule between platforms, that panel is the whole reason this page exists.
Steps, ranges and the ones that quietly misfire
*/n reads as “every n”, and mostly behaves — but only when n divides the field range evenly. */15 in the minute field gives you 0, 15, 30, 45 and then wraps cleanly. */7 gives you 0, 7, 14, 21, 28, 35, 42, 49, 56 — and then the next run is 0, which is four minutes later, not seven. Every hour has one short gap. The same applies to */7 in hours, */45 in minutes, and anything else that does not divide its range.
A step can also start somewhere other than zero: 5/10 means “from 5, every 10” — 5, 15, 25, 35, 45, 55. And a range can carry a step, so 0-30/5 is every five minutes for the first half hour only.
Days that do not exist
0 0 31 * * looks monthly but runs seven times a year, because April, June, September and November have no 31st. 0 0 30 2 * parses perfectly and can never run. If you want the last day of the month on Linux there is no L — the idiom is to run on the 1st and subtract a day, or to check inside the job with [ "$(date -d tomorrow +\%d)" = "01" ]. On Quartz and AWS you can simply write L.
This page flags all of these as you type, along with the frequency: if an expression fires 1,440 times a day it will tell you, because a job that runs every minute and sometimes takes 70 seconds will happily start overlapping copies of itself until the box falls over. flock, a lock file, or concurrencyPolicy: Forbid on Kubernetes.