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