Free tools · no login

⇆ Cron converter: crontab, systemd, Quartz, AWS

Read as systemd timer (OnCalendar=), '*-*~01 23:00:00' means: at 23:00, on the last day of the month. In crontab it is 0 23 28-31 * *; as a systemd timer OnCalendar=*-*~01 23:00:00; in Quartz 0 0 23 L * ?; on AWS cron(0 23 L * ? *). The notes say what changes in each dialect.

One schedule in six dialects. Paste a crontab line, an OnCalendar= value, a Quartz expression or an AWS cron(), and see the others, with a note wherever a dialect reads it differently.

Read as systemd timer (OnCalendar=)
*-*~01 23:00:00
At 23:00, on the last day of the month.

In every dialect

DialectExpressionNotes
Standard cron (crontab)
approximate
0 23 28-31 * *
Standard cron has no 'last day of the month'. This wakes up on days 28-31; put a guard in front of the command so it only runs on the right day: [ "$(date -d tomorrow +\%d)" = 01 ] (GNU date).
systemd timer (OnCalendar=)
OnCalendar=*-*~01 23:00:00
No timezone given, so the timer uses the machine's local time; append UTC or an IANA zone to pin it.
Quartz / Spring
0 0 23 L * ?
Quartz numbers the weekdays 1-7 starting with SUN=1 (cron uses 0-6 with Sunday=0), so names are used here.
AWS EventBridge cron()
cron(0 23 L * ? *)
EventBridge scheduled rules read this in UTC. EventBridge Scheduler evaluates it in the time zone you set on the schedule (--schedule-expression-timezone); on a spring-forward night a time that doesn't exist is skipped.
GitHub Actions
approximate
0 23 28-31 * *
Standard cron has no 'last day of the month'. This wakes up on days 28-31; put a guard in front of the command so it only runs on the right day: [ "$(date -d tomorrow +\%d)" = 01 ] (GNU date).
Read in UTC unless the schedule entry sets timezone: to an IANA zone. Runs can be delayed at busy times (the start of every hour especially), only run from the default branch, and stop after 60 days without activity in public repos.
Kubernetes CronJob
approximate
0 23 28-31 * *
Standard cron has no 'last day of the month'. This wakes up on days 28-31; put a guard in front of the command so it only runs on the right day: [ "$(date -d tomorrow +\%d)" = 01 ] (GNU date).
Set the zone with spec.timeZone (stable since Kubernetes 1.27); without it the kube-controller-manager's local time is used. CRON_TZ= or TZ= inside schedule is rejected with a validation error.
GitHub Actions
on:
  schedule:
    - cron: "0 23 28-31 * *"
      # timezone: "Asia/Kolkata"   # optional; UTC if omitted
Kubernetes CronJob (part of the spec)
spec:
  schedule: "0 23 28-31 * *"
  timeZone: "Etc/UTC"

Next 8 runs (UTC)

  1. Sat 2026-10-31 23:00:00 UTC
  2. Mon 2026-11-30 23:00:00 UTC
  3. Thu 2026-12-31 23:00:00 UTC
  4. Sun 2027-01-31 23:00:00 UTC
  5. Sun 2027-02-28 23:00:00 UTC
  6. Wed 2027-03-31 23:00:00 UTC
  7. Fri 2027-04-30 23:00:00 UTC
  8. Mon 2027-05-31 23:00:00 UTC

Computed here from the converted schedule, independently of any one dialect's library.

Link to this conversion:

How the cron dialects differ

Checked against each project's own documentation on ; sources at the end.

crontabsystemd OnCalendar=QuartzAWS EventBridgeGitHub ActionsKubernetes
Fields5: 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 year5 (POSIX)5
Weekdays0-6, Sunday = 0 (7 also Sunday)Mon..Sun names1-7, SUN = 11-7, SUN = 10-60-6
Both day fields setruns if either matchesweekday and date must matchnot allowed: one must be ?not allowed: one must be ?POSIX: eithereither
Last day, nth weekdayno~01, Mon *-*-01..07L, L-n, LW, nW, 6L, 2#1L, W, 6L, 2#1nono
Secondsnoyesyesnonono
Time zonethe server's (cronie: CRON_TZ=)local, or a zone at the endset by the applicationrules UTC; Scheduler takes a zoneUTC, or timezone:spec.timeZone (1.27+)
Shortest interval1 minutesub-second1 second1 minute5 minutes1 minute
Macros@daily, @hourly …daily, weekly (= Monday), quarterly …nono (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.

crontabsystemdQuartzAWS
*/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.

SchedulesystemdQuartzAWScrontab
Last day of the month at 23:00 *-*~01 23:00:000 0 23 L * ? cron(0 23 L * ? *)0 23 28-31 * * + guard
First Monday at 09:00 Mon *-*-01..07 09:00:000 0 9 ? * 2#1 cron(0 9 ? * 2#1 *)0 9 1-7 * * + guard
Last Friday at 17:00 Fri *-*~07/1 17:00:000 0 17 ? * 6L cron(0 17 ? * 6L *)0 17 22-31 * * + guard
Every 30 seconds *-*-* *:*:00/300/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)

How Vigil can watch this for you

Know when a converted schedule stops running

Vigil heartbeats understand cron expressions and time zones, so they know when each run is due. Missed, failed or stuck runs alert you on email, Slack, Telegram, Discord or a webhook.

Monitor a scheduled job free
Free plan, no card. Alerts on email, Telegram, Slack, Discord, Teams or any webhook.
More free tools: