Cron Dialect Comparison
Every cron-flavored scheduler started with Unix crontab and went its own direction. This guide is a side-by-side reference for the six dialects you're most likely to encounter — field counts, special characters, day-of-week numbering, default timezones, and the syntax differences that bite when you copy an expression from one system to another.
Updated
Quick overview
| Dialect | Fields | Seconds | Year | DoW range | Special chars | Default TZ |
|---|---|---|---|---|---|---|
| Unix | 5 | — | — | 0–6 (Sun=0) | — | system local |
| Kubernetes | 5 | — | — | 0–6 (Sun=0) | @hourly etc. | UTC |
| GitHub Actions | 5 | — | — | 0–6 (Sun=0) | — | UTC |
| Quartz | 6 or 7 | ✓ | ✓ (optional) | 1–7 (Sun=1) | ? L W # | JVM default |
| AWS EventBridge | 6 | — | ✓ | 1–7 (Sun=1) | ? L W # | UTC |
Spring @Scheduled | 6 | ✓ | — | 0–7 (Sun=0 or 7) | ? L W # | JVM default |
Field counts and what they mean
5-field cron (Unix, Kubernetes, GitHub Actions) — minute, hour, day-of-month, month, day-of-week. Minimum granularity is 1 minute.
6-field cron with seconds (Quartz, Spring) — second, minute, hour, day-of-month, month, day-of-week. Minimum granularity is 1 second.
6-field cron with year, no seconds (AWS EventBridge) — minute, hour, day-of-month, month, day-of-week, year. Minimum granularity is 1 minute.
7-field cron (Quartz with optional year) — second, minute, hour, day-of-month, month, day-of-week, year.
If you paste a 5-field expression into a Quartz scheduler, you’ll get a parse error — the seconds field is missing. If you paste a 6-field Quartz expression into Unix crontab, the first field will be interpreted as minute, not second, and the schedule will be wrong by orders of magnitude.
Day-of-week numbering
The single most common porting bug:
- Unix / Kubernetes / GitHub Actions: Sunday = 0, Saturday = 6
- Quartz / AWS EventBridge: Sunday = 1, Saturday = 7
- Spring
@Scheduled: accepts both 0 and 7 as Sunday
“Weekdays only” in Unix is 1-5 (Mon–Fri). In Quartz it’s 2-6. The MON-FRI name form works in all dialects and avoids the off-by-one ambiguity entirely — prefer it.
Special characters
Every dialect supports *, ,, -, /. Beyond that:
| Char | Meaning | Where it works |
|---|---|---|
? | No specific value (placeholder for the unused field of day-of-month / day-of-week) | Quartz, EventBridge (mandatory); Spring (optional) |
L | Last day of month (in DoM field); last day of week or “last X-day” (in DoW field) | Quartz, EventBridge, Spring |
W | Nearest weekday to a given day (e.g., 15W) | Quartz, EventBridge, Spring |
# | The nth occurrence of a weekday in a month (e.g., MON#1 = first Monday) | Quartz, EventBridge, Spring |
@hourly, @daily, @weekly, @monthly, @yearly | Named shortcuts | Linux crontab, Kubernetes (some), Spring (5.3+) |
@reboot | Once at system startup | Linux crontab only |
Quartz and EventBridge use ? as a mandatory placeholder — you cannot set both DoM and DoW to specific values. Pick one and put ? in the other.
Default timezones
This catches everyone at least once:
- Linux crontab uses the system’s local timezone. Override per-crontab with
CRON_TZ=UTCat the top of the file. - Kubernetes CronJob defaults to UTC. Set
spec.timeZonefor other zones (requires K8s 1.27+). - AWS EventBridge is UTC. No per-rule timezone.
- GitHub Actions is UTC. No per-workflow timezone.
- Spring
@Scheduleduses the JVM’s default timezone. Set thezoneannotation parameter (e.g.,zone = "America/New_York") for a specific zone. - Quartz uses the JVM default. Set the trigger’s
setTimeZone()explicitly.
If your job needs to run at a specific local time across DST transitions, pin the timezone explicitly. Don’t rely on the system default.
Same schedule, six dialects
“Every 5 minutes”:
Unix */5 * * * *
Kubernetes */5 * * * *
GitHub Actions */5 * * * *
Quartz 0 */5 * * * ?
AWS EventBridge */5 * * * ? *
Spring 0 */5 * * * *
“Every weekday at 9 AM”:
Unix 0 9 * * 1-5 (or 0 9 * * MON-FRI)
Kubernetes 0 9 * * 1-5
GitHub Actions 0 9 * * 1-5
Quartz 0 0 9 ? * MON-FRI
AWS EventBridge 0 9 ? * MON-FRI *
Spring 0 0 9 * * MON-FRI
“Last day of every month at midnight”:
Unix — (cannot express directly; use script logic or 0 0 28-31 * * with a check)
Quartz 0 0 0 L * ?
EventBridge 0 0 L * ? *
Spring 0 0 0 L * *
Porting expressions between dialects
A quick checklist when copying an expression from one system to another:
- Field count — add or drop the seconds field. Add or drop the year field.
- DoW numbering —
0↔1and6↔7, or convert to name form (MON,TUE, etc.). - Day-of-month vs day-of-week — if the target dialect requires
?, replace one of them. If the source uses?, the target may need*. - Special chars —
L,W,#won’t survive a port to Unix or Kubernetes. Either rewrite using*or skip those dialects entirely. - Timezone — even if the syntax ports, the runtime timezone may not. Pin explicitly in the target system.
The parser tool handles all of this — paste any expression, switch dialects with the dropdown, and you’ll see whether it remains valid (and what changes).
Frequently asked questions
Why are there so many cron dialects?
Which cron dialect should I learn first?
Can I write one cron expression that works everywhere?
Related
Every 5 Minutes
See `*/5 * * * *` expressed in all six dialects.
ToolCron Parser
Switch dialects and see how each interprets the same expression.
GuideSub-Minute Scheduling
The seconds field, dialect by dialect, plus each platform's real minimum.
GuideHow Cron Expressions Work
Field-by-field guide to the standard 5-field cron syntax.