Systemd OnCalendar Converter
Turn a crontab schedule into systemd OnCalendar syntax with a ready-to-install .timer unit — entirely on this device.
The systemd schedule appears here.
| Run | In UTC | UTC | Note |
|---|---|---|---|
| 1 | — | — | |
| 2 | — | — | |
| 3 | — | — | |
| 4 | — | — | |
| 5 | — | — | |
| 6 | — | — | |
| 7 | — | — | |
| 8 | — | — | |
| 9 | — | — | |
| 10 | — | — |
systemd fires within a 1-minute AccuracySec= window by default; RandomizedDelaySec= adds a random extra delay (default 0).
Press ? for shortcuts
Do it offline:$ systemd-analyze calendar "Mon..Fri *-*-* 09:00:00"
How to use it
- Paste the crontab schedule — five fields, six with leading seconds, or a macro like @weekly; any command after the fields moves onto the ExecStart= line.
- Check the OnCalendar= card and its plain reading; notes flag each point where cron and systemd disagree.
- Set the unit name and the Persistent= switch, pick a preview zone, and scan the next elapses with UTC alongside.
- Copy the OnCalendar= value, or download the .timer and .service pair and install it with systemctl.
Turn a crontab schedule into systemd's OnCalendar= syntax without guessing at the grammar. Paste the cron line — a trailing command is fine — and get the calendar expression, a plain-English reading, upcoming run times, and a ready-to-install .timer and .service pair. Already holding an OnCalendar= value? The page reads it back the same way. All of it computed on this device.
Examples
0 9 * * 1-5Mon..Fri *-*-* 09:00:00The weekday range becomes names — systemd's weekday part has no numbers — and the *-*-* date leaves year, month and day open. Seconds are always spelled out, so a five-field cron line lands on second 00. The value doubles as the page's CLI line for systemd-analyze.
*/15 * * * **-*-* *:00/15:00A star never takes a step in systemd, so cron's */15 anchors at a base value instead: minute 00/15 fires at 00, 15, 30 and 45. The reading stays Every 15 minutes.
0 0 1 * 1*-*-01 00:00:00
Mon *-*-* 00:00:00Both day fields are restricted, and cron fires when either one matches. No single calendar expression says that, so the converter emits a pair — a timer accepts many OnCalendar= lines, and their triggers accumulate. From 2026-08-07 the union previews Mondays August 10, 17, 24 and 31, then Tuesday, September 1.
Mon *-05~07/1Mon *-05~07/1 00:00:00Read mode, on grammar cron cannot write: ~ counts days from the month's end, so 05~07/1 walks May's last seven days and Mon keeps their Monday — systemd.time(7) glosses this as the last Monday in May. Previews land on 2027-05-31, 2028-05-29 and 2029-05-28.
Every example on this page is executed against the same engine that powers the tool, on every build.
Gotchas worth knowing
systemd's day fields mean AND, cron's mean OR
An OnCalendar= value naming both a weekday and a day-of-month matches only when both agree: Mon *-*-13 fires solely on Mondays that are a 13th. Vixie cron reads the same pair as either-or — which is why that shape converts to the two-line union above, never one lookalike expression.
Two calendars disagree about @weekly
Cron's @weekly means Sunday at midnight; systemd's weekly shortcut means Monday at midnight. The converter therefore expands @weekly to Sun *-*-* 00:00:00 and never emits the shortcut word.
Grammar cron has no words for
OnCalendar= adds seconds (*:*:30), year fields (2027-01-01), weekday ranges by name (Mon..Fri), and ~ offsets counted from the month's end — *-02~03 is February's third-to-last day. Quartz's L and W stay in that dialect. The extra reach is why conversion here runs cron to OnCalendar= only, never back.
Persistent= and the one-minute accuracy window
Two systemd.timer(5) defaults change behavior against cron. Persistent=true stores the last trigger time on disk, so a run missed while the machine was off fires once at the next start — plain cron has no catch-up. And AccuracySec= defaults to 1min, letting wake-ups land anywhere in that minute; tighten it when the second matters.
Common errors
Found 7 fields — that is Quartz cron (seconds and year), not the classic 5-field form.- Quartz lines carry a leading seconds field and often a trailing year, and their day rules differ from classic cron too. Trim the schedule to five classic fields — a six-field paste is read as seconds-first cron.
The month value 13 is outside 1–12.- OnCalendar= dates run year-month-day, so the second dash-separated number is the month — cron's day-before-month habit puts a 13 there. January 13th is *-01-13; the error anchors the token, and jump-to-error selects it.
"Tues" is not a weekday — use three-letter (Mon) or full (Monday) English names.- systemd accepts exactly two spellings per day and nothing in between: Tue 12:00 and Tuesday 12:00 both parse, Tues never will. Case is free — tuesday works.
Input is larger than the 1,024-character limit for this tool. Check long expressions on a Linux machine with: systemd-analyze calendar 'Mon..Fri *-*-* 09:00:00'- A refusal, never a truncation — half a schedule could quietly mean a different one. Real expressions are short; an overflow here is usually an entire crontab, and whole files belong to the crontab validator.
FAQ
Can every cron schedule become one OnCalendar= line?
Almost — two shapes cannot. A line restricting day-of-month and day-of-week together means either-or in cron, which no single calendar expression encodes; this page emits two OnCalendar= lines, whose accumulated events reproduce the union exactly. @reboot is the other: it has no calendar at all, so the unit switches to OnBootSec=1min — a fixed delay after boot, where cron ran the job whenever its daemon started.
How do I install the generated .timer and .service files?
Place both in /etc/systemd/system/ under one base name, then run systemctl daemon-reload followed by systemctl enable --now my-job.timer. Verify with systemctl list-timers, which prints the next elapse systemd itself computed. The .timer decides when, the .service says what — and only the timer gets enabled.
Which timezone does OnCalendar= use?
The machine's local time, unless the expression itself ends in a zone: OnCalendar=03:00 Europe/Berlin keeps Berlin wall time wherever the host lives. A converted crontab line carries no zone, so it fires on host local time just as it did under cron. Previews here follow the same rule — an in-expression zone overrides the picker, with a note saying so.
Keyboard shortcuts
- ⌘/Ctrl + Enter
- Convert again immediately.
- ⌘/Ctrl + S
- Download the .timer unit file.
- ⌘/Ctrl + C
- Copy the OnCalendar= value when nothing is selected.
- ⌘/Ctrl + Shift + C
- Copy the OnCalendar= value even over an active selection.
- t
- Cycle the color theme.
- ? or ⌘/Ctrl + /
- Show the binding list.
- Esc
- Close the topmost dialog; the input stays put.