Cron day of month and day of week: the OR rule
If both the day of month and the day of week fields are restricted, a date matches when either one matches. Only when one of them is an asterisk do the restrictions behave like an AND.
At a glance
- The rule
- Both restricted means OR. One restricted and one asterisk means the restriction simply applies.
- Fields involved
- Day of month (third) and day of week (fifth) in a five field expression.
- Not involved
- The month field always applies as an AND, whichever date fields are restricted.
- Typical effect
- 0 9 1-7 * 1 fires on ten or eleven days a month instead of one.
- Where it comes from
- The behaviour of the original Unix cron, kept for compatibility ever since.
- How to avoid it
- Leave one of the two date fields as an asterisk and test the other condition in the script.
In detail
This is the one piece of cron behaviour that is genuinely counter intuitive rather than merely fiddly. Every other field narrows the match: adding an hour restriction to a minute restriction gives fewer runs. Adding a day of week restriction to a day of month restriction gives more, because the two are combined with OR rather than AND.
The reasoning behind it is that the two fields are alternative ways of describing a day, so a user writing both probably means either. In practice almost nobody means that, and the rule mostly appears as a bug: an expression intended to pick the first Monday of the month instead picks the first week and every Monday.
Work out the real count before assuming an expression is wrong or right. 0 9 1-7 * 1 in a month beginning on a Wednesday matches days 1 to 7, plus the Mondays on the 13th, 20th and 27th, which is ten runs. In a month beginning on a Monday it is eleven. Neither is close to one.
The way out is always the same: leave one of the two fields open and move that condition into the command. Keep the day of month range in the expression, since it narrows the schedule to seven runs, and have the script test the weekday and exit if it does not match. Six wasted process starts a month is a trivial cost for correctness.
Examples
0 9 1-7 * 1Days 1 to 7 or any Monday. Ten or eleven runs a month.
0 9 1-7 * * [ "$(date +\%u)" = "1" ] && /opt/app/bin/job.shOnly the day of month field is restricted, so the OR rule never applies.
0 9 * * 1-5Day of month is an asterisk, so the weekday restriction is the only one and behaves as expected.
Things that catch people out
- The expression is valid, so nothing warns you. The only symptom is more runs than expected.
- The rule applies whenever both fields are anything other than an asterisk, including a single value in each.
- It does not apply to the month field. Restricting the month always narrows, whatever the date fields say.
Try it: Cron Expression Parser
Paste any five or six field expression to get a plain English description, a per field breakdown and the next run times in your local time.
Cron Expression Parser
LOCAL · Browser-OnlyParse cron expressions and see the next scheduled run times
Supports 5-field (minute hour day month weekday), 6-field (with seconds), named values (JAN, MON), and @macros (@daily, @hourly, ...)
Use the dropdowns below to build your cron expression visually, or type a custom value in each field.
Human-Readable Description
Every 5 minutes
Field Breakdown
| Field | Allowed | Value | Meaning |
|---|---|---|---|
| Minute | 0-59 | */5 | every 5 minute(s) |
| Hour | 0-23 | * | every hour |
| Day of Month | 1-31 | * | every day of month |
| Month | 1-12 | * | every month |
| Day of Week | 0-7 | * | every day of week |
Related cron expressions
All cron expressions →The cron day of month field: 1 to 31 and missing dates
The day of month field runs 1 to 31, and a date that does not exist in a given month simply never matches. Steps start at 1, giving odd days.
The cron day of week field: 0 to 7 with Sunday at both ends
The day of week field runs 0 to 7 with Sunday as both 0 and 7, and accepts SUN through SAT. Restricting it changes how the day of month field behaves.
Cron on the first Monday of the month: the OR rule trap
0 9 1-7 * 1 does not mean the first Monday. Because both date fields are restricted, it fires on days 1 to 7 and on every Monday as well.
Cron on the last Friday of the month: no # or L support
The Quartz forms 6L and 6#5 are rejected here. Schedule every Friday and let the script check whether the following Friday is in the next month.
Cron name aliases: JAN to DEC and SUN to SAT
The month and day of week fields accept three letter English names, case insensitively, in single values, ranges, lists and steps.
Cron macros: @daily, @weekly, @monthly and the rest
Seven macros expand to ordinary five field expressions, and @reboot has no scheduled time at all. Here is exactly what each one becomes.
Tools for this
- Cron Expression Parser Free online cron expression parser - parse cron expressions and see the next scheduled run times
- Crontab Generator Free online crontab generator - build cron expressions visually with an intuitive interface