The illusion of a standard
Cron is supposed to be standard. You write 0 0 * * 1-5 on a Linux box and it runs every weekday at midnight. Then you paste the same expression into AWS EventBridge and it rejects it.
The POSIX 5-field format is really just a starting point. Quartz added a seconds field. AWS added a year field. Both changed Sunday from 0 to 1. Kubernetes kept the original format but treats ? as a synonym for *. Every migration between these systems risks silent scheduling failures.
Here's how the major schedulers diverge, and how to translate between them.
Quartz Scheduler (Java / Spring Boot)
The Quartz Scheduler powers most enterprise Java applications and Spring Boot's @Scheduled. It differs from Unix cron in both time resolution and day-matching logic.
Structure: 6 or 7 fields
Format: Seconds Minutes Hours Day-of-Month Month Day-of-Week [Year]
- Seconds field: Quartz prepends a mandatory seconds field.
- DOW shift: Sunday is
1(not0or7as in POSIX). Saturday is7. - Mutual exclusion rule: To dodge the confusing POSIX OR-behavior, Quartz forces a strict mutual-exclusion constraint between Day-of-Month and Day-of-Week. One field must contain
?("no specific value"). Setting both to*throws a runtime initialization error.
Extra modifiers: Quartz supports several modifiers that standard cron lacks:
L(Last): e.g.,Lin the Day-of-Month field resolves to the last calendar day of the month.W(Nearest Weekday): e.g.,15Wexecutes on the nearest business day to the 15th.#(Nth Occurrence): e.g.,2#1represents the first Monday of the month.
Watch out for Tanzu: VMware Tanzu (formerly Pivotal) uses a hybrid Quartz model. It requires ? and uses 1-7 for days, but strips the seconds and years fields. A standard 6-field Quartz expression will crash here.
AWS EventBridge
Both EventBridge Rules and the newer EventBridge Scheduler use a strict six-field format.
Structure: 6 fields
Format: Minutes Hours Day-of-Month Month Day-of-Week Year
- Year field: A mandatory year value at the end.
- Mutual exclusion: Same
?constraint as Quartz between DOM and DOW. - Sunday is 1: Same shift as Quartz.
Example: To run "every weekday" in AWS EventBridge, use cron(0 0 ? * MON-FRI *).
Kubernetes CronJobs
The Kubernetes CronJob resource uses the Go robfig/cron parser under the hood.
Structure: 5 fields (standard POSIX)
Unlike Quartz and AWS, the default K8s parser does not support L, W, or # modifiers, and it does not enforce the ? constraint. In Kubernetes, ? is a synonym for *.
Pro-tip: Prior to Kubernetes v1.27, jobs ran relative to host time. Since v1.27, the .spec.timeZone field allows you to specify a valid IANA timezone to handle daylight saving shifts.
Node.js: node-cron vs. cron
The behavior depends on which NPM package you pull.
node-cron: Uses a 5- or 6-field pattern where an optional seconds field is prepended. Sunday is0or7.cron(kelektiv/node-cron): Uses a mandatory 6-field schedule with second-level precision. It integrates well with IANA timezones via Luxon and includes execution wrappers to prevent job pileups if previous callbacks are still running.
Translation Cheatsheet
Here's how identical schedules translate across the three main dialects:
"Every day at midnight"
- POSIX:
0 0 * * * - Quartz:
0 0 0 * * ? - AWS:
cron(0 0 * * ? *)
"First day of the month at midnight"
- POSIX:
0 0 1 * * - Quartz:
0 0 0 1 * ? - AWS:
cron(0 0 1 * ? *)
"Every Monday at midnight"
- POSIX:
0 0 * * 1 - Quartz:
0 0 0 ? * MON - AWS:
cron(0 0 ? * MON *)
Translating between these formats by hand gets old fast. The Cron Expression Generator builds schedules and exports them in the exact dialect your infrastructure expects.