English

Developer tools · Crontab generator

Day of month vs day of week in cron: the OR rule that surprises everyone

· How it works

cron calendar scheduling

Two calendar filters joining through an OR gate into one schedule
Original ToolAcre vector illustration

When both day fields are restricted, cron runs the job if either matches. This post explains the rule from crontab(5), why it exists, and how to get the schedules it prevents.

The report ran on the 1st and on every Monday — the expression 0 9 1 * 1 does not mean what it looks like

An expression such as `0 9 1 * 1` looks like “09:00 on the first Monday” if the two day columns are read as cumulative filters. ToolAcre does not read it that way. With day of month set to 1 and day of week set to Monday, the parser warns that either condition can match, so the schedule includes every first and every Monday.

This is not a minor wording preference. A monthly report can run four or five extra times, and an operation expected only on Mondays can also run on the first of a month. The generator places a “Worth checking” warning beside the description so the expanded behavior is visible before an expression is copied into an external crontab.

The rule as written — if both day-of-month and day-of-week are restricted, the command runs when either field matches

The implementation’s `dayMatches` function handles four cases explicitly. Two wildcards accept every date; a wildcard day-of-month defers to weekday; a wildcard weekday defers to day-of-month; and two restricted fields return the logical OR of their matches. Tests cover a date matching only the thirteenth, only Friday and neither condition.

Descriptions preserve the same rule. `0 0 13 * 5` is rendered with “on the 13th of the month or on Friday,” not an ambiguous conjunction. Parsing, next-run selection and English output all use the same interpretation. That shared behavior is stronger evidence than an isolated help label because disagreement among those paths would immediately create misleading previews.

The parser implements OR and exposes the consequence; it does not establish the rule’s history

The workbook outline offered a historical reason for the OR design, but this repository proves behavior rather than provenance. The code comments identify a Vixie-style rule, and the tests establish the result modeled by ToolAcre. They do not document who chose the rule, when it was adopted or why a permissive interpretation was preferred.

Keeping that boundary matters in technical writing. Readers need to know exactly how this tool expands the expression; they do not need an invented standards narrative to use it safely. If a deployment target documents different day semantics, its own manual outranks this browser preview. ToolAcre states the dialect it computes rather than claiming every scheduler agrees.

Worked example: enumerating 0 9 1 * 1 for one month — the days it fires, compared with the schedule the author wanted

Consider a month whose first day is not Monday. `0 9 1 * 1` produces one run on the first and additional runs on every Monday. If the first itself is Monday, that date still appears once because the next-run search considers one calendar minute, not two separate triggers. The union changes in size with the calendar but not in logic.

You can inspect that union by choosing a time zone and reading the next five runs. The preview starts strictly after the current instant and searches calendar dates, applying month, day and time fields. It does not promise a specific month in article prose because the live list depends on when and where the reader performs the check.

AND-style first-Monday command workarounds are outside this schedule generator

The plan suggested embedding a shell date test to obtain AND behavior. ToolAcre cannot verify such a command: it accepts only the five schedule fields, and its copied full line contains a placeholder executable. Shell syntax, percent escaping, command availability and exit behavior all belong to the environment that eventually runs the job.

Within this generator, the safe design move is to leave one day field as `*` unless the OR union is genuinely intended. For a first-Monday requirement, document that five-field cron alone does not express the intersection in this dialect. Choose and test an environment-specific solution separately instead of presenting an unexecuted snippet as guaranteed.

Other scheduler operators are not interpreted by this five-field parser

Some scheduler grammars expose operators such as `#`, `L`, `W` or `?`, but this parser accepts none of them. It also refuses six fields and explains that its contract is five-field crontab syntax. Therefore an expression that works in another product cannot be pasted here as evidence that the same calendar rule exists.

Dialect translation begins by identifying the source and target grammars, not by deleting punctuation until validation succeeds. ToolAcre can help construct the target’s five ordinary fields, names, ranges, lists and steps. It cannot preserve semantics carried by an unsupported operator, and the articles deliberately avoid describing those foreign operators as if they were implemented.

Command escaping is outside the expression-only grammar

Percent signs and command quoting appear after the schedule and are outside `parseCron`. The parser splits one expression into exactly five whitespace-separated parts; it never reads a shell pipeline, date command or escaped command payload. Consequently, this article does not teach a percent-sign workaround even though the workbook proposed one.

That omission is a correctness decision. A schedule tool should not imply that a command is safe because its timing fields validate. Review command syntax in the actual cron implementation and shell, with harmless inputs and observable output. Keep the generator’s result scoped to the calendar rule it demonstrably understands.

Takeaway: restrict one day field, not both — and the generator's explanation makes the OR visible before you save the line

When both day fields are restricted, read them as a union and say “or” aloud. Better still, inspect the generator’s warning and upcoming runs. If those dates exceed the intended set, return one field to `*` and solve any more specialized requirement with a mechanism documented by the target environment.

The core lesson is not that cron is mysterious; it is that two adjacent columns do not combine like the other restrictions. ToolAcre centralizes that exception in parsing, description and preview logic. Use those three views to catch the mismatch before an expression leaves the browser and becomes an operational schedule.