Skip to content

bug(cron): numeric day-of-week is off by one — docs promise POSIX but cron crate uses Sun=1..Sat=7 #784

@charlie0228

Description

@charlie0228

Description

When a cronjob uses numeric day-of-week values (e.g. 0 7 * * 1-5), openab
fires on the wrong day. The docs promise POSIX semantics (Sun=0/7, Mon=1, ..., Sat=6),
so 1-5 should mean Mon–Fri. In practice it fires Sun–Thu — every numeric
day-of-week value is shifted by one day.

The timezone handling itself is correct. The bug is purely in how numeric
day-of-week values are interpreted.

Steps to Reproduce

  1. Configure a cronjob:
    [[jobs]]
    schedule = "0 7 * * 1-5"
    timezone = "Asia/Taipei"
    channel = "<redacted>"
    message = "weekday job"
    enabled = true
  2. Wait for the next Sunday 07:00 in Asia/Taipei (= Saturday 23:00 UTC).
  3. Observe: the job fires, even though Sunday is not in 1-5.

Real trigger observed on 2026-05-10 (Sunday):

2026-05-09T23:00:01Z  INFO openab::cron: 🔔 cronjob fired
    schedule=0 7 * * 1-5  channel=<redacted>  platform=discord
    message=<redacted>  sender=openab-cron

UTC 23:00 on 2026-05-09 = Taipei 07:00 on 2026-05-10 (Sunday). Should not have fired.

Expected Behavior

Per docs/cronjob.md, which says:

Standard 5-field POSIX cron, same as Linux crontab, K8s CronJob, and GitHub Actions
day of week (0-7, 0 and 7 = Sunday)
| 0 9 * * 1-5 | Weekdays at 09:00 |

1-5 should fire Mon–Fri only.

Actual Behavior

1-5 fires Sun–Thu (entire weekday range shifted by one). Every numeric
day-of-week value is off by one:

User writes (POSIX intent) Actually fires on
0 (Sun) out-of-range — invalid
1 (Mon) Sun
5 (Fri) Thu
6 (Sat) Fri
7 (Sun) Sat
1-5 (Mon–Fri) Sun–Thu

Root Cause

openab uses the cron crate (v0.16.0), which
does not follow POSIX day-of-week numbering. From
cron-0.16.0/src/time_unit/days_of_week.rs:

static DAY_OF_WEEK_MAP: phf::Map<&'static str, Ordinal> = phf_map! {
    "sun" => 1,
    "mon" => 2,
    "tue" => 3,
    "wed" => 4,
    "thu" => 5,
    "fri" => 6,
    "sat" => 7,
};
// inclusive_min = 1, inclusive_max = 7

And in schedule.rs,
the weekday match uses chrono's number_from_sunday() (Sun=1, Mon=2, ..., Sat=7),
which matches the map above but not POSIX.

So 1-5 passed to the cron crate means Sun–Thu, not Mon–Fri.

Note: the timezone handling in should_fire is correct. Utc::now().with_timezone(&tz)
and the weekday check both use the same local DateTime<Tz>. This is not a timezone bug.

Proposed Fix

Translate numeric day-of-week tokens in the 5th field before handing the
expression to cron::Schedule. This keeps docs/cronjob.md's POSIX promise
intact and is a localized change to parse_cron_expr:

  • 0 or 71 (Sunday in cron crate)
  • n (1..=6) → n + 1
  • Applies to each numeric token inside ranges (1-52-6), lists (1,3,52,4,6),
    and step bases (*/2 unchanged; 1-5/22-6/2).
  • Name tokens (Mon, Sun, Mon-Fri, ...) pass through unchanged — the cron crate's
    name → ordinal map is already internally consistent.

Alternative considered: switching to a POSIX-compliant crate (e.g. saffron,
croner-rust). Larger surface area; can be re-evaluated if the translation
approach proves insufficient.

Workaround (for existing users)

Use name-based day-of-week:

schedule = "0 7 * * Mon-Fri"

This works correctly because the cron crate's name→ordinal map is internally
consistent; only the numeric interface diverges from POSIX.

Environment

  • openab: 0.8.2
  • cron crate: 0.16.0
  • Platform: Discord adapter, via usercron (~/.openab/usercron.toml)

Issue filed by my agent (OpenAB w/ Kiro CLI) under my account; content reviewed by me.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions