How the cron dialects differ
Checked against each project's own documentation on ; sources at the end.
| crontab | systemd OnCalendar= | Quartz | AWS EventBridge | GitHub Actions | Kubernetes | |
|---|---|---|---|---|---|---|
| Fields | 5: min hour dom month dow | [weekday] Y-M-D H:M:S [zone] | 6-7: sec min hour dom month dow [year] | 6: min hour dom month dow year | 5 (POSIX) | 5 |
| Weekdays | 0-6, Sunday = 0 (7 also Sunday) | Mon..Sun names | 1-7, SUN = 1 | 1-7, SUN = 1 | 0-6 | 0-6 |
| Both day fields set | runs if either matches | weekday and date must match | not allowed: one must be ? | not allowed: one must be ? | POSIX: either | either |
| Last day, nth weekday | no | ~01, Mon *-*-01..07 | L, L-n, LW, nW, 6L, 2#1 | L, W, 6L, 2#1 | no | no |
| Seconds | no | yes | yes | no | no | no |
| Time zone | the server's (cronie: CRON_TZ=) | local, or a zone at the end | set by the application | rules UTC; Scheduler takes a zone | UTC, or timezone: | spec.timeZone (1.27+) |
| Shortest interval | 1 minute | sub-second | 1 second | 1 minute | 5 minutes | 1 minute |
| Macros | @daily, @hourly … | daily, weekly (= Monday), quarterly … | no | no (rate() instead) | not accepted | @daily, @hourly … |
The weekday shift that breaks Quartz and AWS conversions
The most common conversion bug is copying the day-of-week number. In crontab, GitHub Actions and Kubernetes, 1-5 is Monday to Friday. Quartz and AWS count from Sunday = 1, so the same 1-5 means Sunday to Thursday: the job quietly runs on the wrong days and nothing errors. This converter writes weekday names (MON-FRI) in Quartz and AWS output, which both accept and nobody misreads. Quartz also wants a leading seconds field, so crontab's 0 9 * * 1-5 becomes 0 0 9 ? * MON-FRI, not 0 9 * * 1-5 with a zero glued on.
OR versus AND: when both day fields are restricted
crontab has an old rule that surprises almost everyone: if the day-of-month and the day-of-week fields are both restricted, the job runs when either matches. 0 9 1-7 * 1 is not "the first Monday"; it is days 1 to 7 plus every Monday. systemd does the opposite and ANDs the weekday with the date, which is why Mon *-*-01..07 09:00:00 really is the first Monday. Quartz and AWS avoid the question by refusing to restrict both fields: one of them must be ?. So a crontab line that uses both fields becomes two OnCalendar= lines, two Quartz triggers or two AWS schedules, and a systemd "first Monday" becomes Quartz 2#1 but has no crontab form at all without a guard in the command.
| crontab | systemd | Quartz | AWS |
|---|---|---|---|
| */15 * * * * | *-*-* *:00/15:00 |
0 0/15 * * * ? |
cron(0/15 * * * ? *) |
| 0 9 * * 1-5 | Mon..Fri *-*-* 09:00:00 |
0 0 9 ? * MON-FRI |
cron(0 9 ? * MON-FRI *) |
| 30 2 1 * * | *-*-01 02:30:00 |
0 30 2 1 * ? |
cron(30 2 1 * ? *) |
| 0 0 1 1,4,7,10 * | *-01,04,07,10-01 00:00:00 |
0 0 0 1 1/3 ? |
cron(0 0 1 1/3 ? *) |
| 0 9 1-7 * 1 | *-*-01..07 09:00:00 Mon *-*-* 09:00:00 |
0 0 9 1-7 * ? 0 0 9 ? * MON |
cron(0 9 1-7 * ? *) cron(0 9 ? * MON *) |
Computed by this page's converter. The last row is the OR trap: two lines in every dialect except crontab.
Last day, nth weekday and the other things crontab can't say
Quartz and AWS have L (last day of the month), 6L (last Friday), 2#1 (first Monday) and 15W (the weekday nearest the 15th); Quartz adds L-3 and LW. systemd covers most of these with ~, which counts back from the end of the month: *-*~01 is the last day, and Fri *-*~07/1 is the last Friday (a Friday among the last seven days). Nearest-weekday has no systemd form. crontab, GitHub Actions and Kubernetes have none of them, so the converter gives the closest wide schedule and the shell guard that narrows it, and marks the result as approximate.
| Schedule | systemd | Quartz | AWS | crontab |
|---|---|---|---|---|
| Last day of the month at 23:00 | *-*~01 23:00:00 | 0 0 23 L * ? | cron(0 23 L * ? *) | 0 23 28-31 * * + guard |
| First Monday at 09:00 | Mon *-*-01..07 09:00:00 | 0 0 9 ? * 2#1 | cron(0 9 ? * 2#1 *) | 0 9 1-7 * * + guard |
| Last Friday at 17:00 | Fri *-*~07/1 17:00:00 | 0 0 17 ? * 6L | cron(0 17 ? * 6L *) | 0 17 22-31 * * + guard |
| Every 30 seconds | *-*-* *:*:00/30 | 0/30 * * * * ? | — | none |
Time zones and daylight saving time
The same expression can run at different instants depending on where it is read. crontab uses the server's local zone (cronie also reads CRON_TZ= at the top of the crontab). A systemd timer uses local time unless the spec ends in UTC or a zone name such as Asia/Kolkata. AWS EventBridge scheduled rules run in UTC, while EventBridge Scheduler takes a time zone, skips a time that doesn't exist on the spring-forward night and runs only once in the repeated autumn hour. GitHub Actions reads schedules in UTC unless the entry sets timezone:; in a skipped hour it moves the run to the next valid time. Kubernetes uses spec.timeZone (stable since 1.27) and rejects CRON_TZ= or TZ= inside the schedule string. If "exactly once a day" matters, schedule in UTC everywhere and convert the time yourself; India (IST, UTC+05:30) has no daylight saving, so IST schedules never shift.
Intervals and platform limits
GitHub Actions won't run a schedule more often than every five minutes, delays runs at busy moments (the top of the hour especially), only runs from the default branch and switches schedules off in public repositories after 60 days without activity. AWS cron() has no seconds and no rates under a minute. Quartz and systemd can fire every few seconds; a systemd timer needs AccuracySec=1s for that, because its default accuracy is one minute. crontab's finest unit is one minute; for every 30 seconds people add a second line that starts with sleep 30.
Check a conversion before you rely on it
- systemd: systemd-analyze calendar --iterations=5 "Mon..Fri *-*-* 09:00:00" prints the next elapse times.
- crontab: paste the line into the cron expression generator & explainer for the next 10 runs in any time zone.
- AWS: the EventBridge console shows the next 10 trigger dates when you create a rule or schedule.
- Compare those lists with the "Next runs" list above, which this page computes from the converted schedule.
A converted schedule is only half the job: a timer or CronJob that silently stops running looks exactly like one that works. A heartbeat monitor that expects a ping after each run catches both; see how to monitor cron jobs and the cron schedule examples.
FAQ
How do I convert a crontab line to a systemd timer?
Take the five schedule fields and write them as OnCalendar=: weekdays first, then year-month-day, then hour:minute:second, for example '0 9 * * 1-5' becomes 'Mon..Fri *-*-* 09:00:00'. If both day fields are restricted, cron runs when either matches, so the timer needs two OnCalendar= lines. Check the result with systemd-analyze calendar.
Why does Quartz say my day-of-week is wrong?
Quartz counts weekdays 1-7 starting with Sunday = 1, while crontab uses 0-6 with Sunday = 0, so '1-5' means Sunday to Thursday in Quartz. Use names like MON-FRI to avoid the shift, and put '?' in the day-of-month field.
Do AWS EventBridge cron expressions run in UTC?
Scheduled rules do. EventBridge Scheduler evaluates the expression in the time zone set on the schedule, adjusts for daylight saving time, skips a time that doesn't exist on the spring-forward night and runs only once in the repeated hour.
Can GitHub Actions run a job every minute?
No. GitHub's shortest schedule interval is every 5 minutes, scheduled runs can be delayed at busy times such as the top of the hour, and macros like @daily are not accepted. Schedules are read in UTC unless the entry sets a timezone.
Which cron features have no equivalent elsewhere?
Seconds exist only in Quartz and systemd. 'Last day of the month' (L), 'nth weekday' (#) and 'last Friday' (6L) exist in Quartz, AWS and systemd but not in crontab, GitHub Actions or Kubernetes, where you schedule a wider window and check the date in the command. Nearest-weekday (W) has no systemd form.
Sources (checked 2026-10-19)
- systemd.time(7), Calendar Events: weekday ANDed with the date, a/n and a..b/n repetition, ~ for days counted from the end of the month, shorthands like weekly = Mon 00:00.
- Quartz CronTrigger tutorial: 6 or 7 fields starting with seconds, day-of-week 1-7 or SUN-SAT, '?' in one day field, L, W, LW, L-n, nL and n#k.
- AWS EventBridge Scheduler: schedule types: cron(minutes hours day-of-month month day-of-week year), one day field must be '?', time zones, a non-existent spring-forward time is skipped.
- AWS EventBridge scheduled rules (legacy): same six fields, examples in UTC+0, no rates faster than one minute.
- GitHub Actions: the schedule event: POSIX 5-field cron, UTC unless timezone: is set, shortest interval 5 minutes, delays at busy times, no @macros, default branch only.
- Kubernetes: CronJob: 5-field schedule plus @macros, '?' means '*', spec.timeZone stable since 1.27, CRON_TZ/TZ in the schedule rejected.