Skip to content

fix: OR day-of-month and day-of-week when a field is a full-coverage range#1070

Open
spokodev wants to merge 1 commit into
kelektiv:mainfrom
spokodev:fix/dom-dow-or-semantics-full-range
Open

fix: OR day-of-month and day-of-week when a field is a full-coverage range#1070
spokodev wants to merge 1 commit into
kelektiv:mainfrom
spokodev:fix/dom-dow-or-semantics-full-range

Conversation

@spokodev

Copy link
Copy Markdown

Problem

Per crontab(5):

If both fields are restricted (i.e. do not contain the * character), the command will be run when either field matches.

The criterion is the literal * character, not whether the value-set covers every value. node-cron breaks this when one day field is a full-coverage range or step (1-31, 0-6, */1, 0,1,...,6) instead of a literal *.

Repro

Expression 0 0 12 1-31 * 1 (day-of-month = 1-31, day-of-week = Monday), starting 2026-06-01T00:00:00Z, UTC:

Implementation Result
node-cron (current) Jun 1, 8, 15, 22 (only Mondays) — wrong
cron-parser 5.6.1 Jun 1, 2, 3, 4 (every day)
croniter 6.2.2 Jun 1, 2, 3, 4 (every day)

Both 1-31 and 1 are restricted (neither is *), so the two day fields are OR'd and the job runs every day. node-cron applies AND and fires only on Mondays.

Root cause

getNextDateFrom in src/time.ts decides whether each day field is "restricted" by cardinality:

Object.keys(this.dayOfMonth).length !== 31
Object.keys(this.dayOfWeek).length !== 7

That is a proxy for "the field is not *". A full-coverage range such as 1-31 has full cardinality yet is not *, so the proxy misclassifies it as a wildcard and falls back to AND. The parser never recorded whether the raw token was literally *.

Fix

  • Capture dayOfMonthWildcard / dayOfWeekWildcard at parse time, set when the raw field contains the * character (before the * to low-high expansion).
  • Use those flags in the day-advance condition instead of the cardinality checks.

This keys the OR/AND decision on the * character exactly as crontab(5) specifies. */1 and other step-wildcards keep their existing wildcard treatment because they contain *.

Tests

Added a crontime test asserting 0 0 12 1-31 * 1 fires Jun 1, 2, 3, 4 from 2026-06-01. It fails on main (returns Jun 1, 8, 15, 22) and passes with the fix. Full suite stays green (162/162 under TZ='Europe/Paris').

…range

Per crontab(5), when both day fields are restricted (neither contains a
literal "*"), a job runs when either field matches. The criterion is the
"*" character, not whether the value-set covers every value.

getNextDateFrom decided "restricted?" by cardinality
(Object.keys(dayOfMonth).length !== 31, dayOfWeek !== 7), which treats a
full-coverage range such as "1-31", "0-6" or "*/1" as a wildcard even
though it is not "*". As a result "0 0 12 1-31 * 1" fired only on
Mondays instead of every day.

cron-parser 5.6.1 and croniter 6.2.2 both return every day for that
expression from 2026-06-01 (Jun 1, 2, 3, 4); node-cron returned only
Mondays (Jun 1, 8, 15, 22).

Capture whether each day field was a literal "*" at parse time and use
those flags in the day-advance condition instead of the cardinality
proxy.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant