Skip to content

*/n on day of month and month counts from 0, so it matches the wrong days #47

Description

@Prafyl

Hi!

*/n on the day of month and month fields counts from 0, but those two fields start
at 1.

import pycron
from datetime import datetime as dt

print([d for d in range(1, 32) if pycron.is_now('0 0 */7 * *', dt(2026, 1, d))])
# [7, 14, 21, 28], expecting [1, 8, 15, 22, 29]

print([m for m in range(1, 13) if pycron.is_now('0 0 1 */5 *', dt(2026, m, 1))])
# [5, 10], expecting [1, 6, 11]

Minutes are fine since that field really does start at 0.

The clearest way to see it is that */7 is just short for 1-31/7, and the long
form gives the right answer:

pycron.is_now('0 0 */7 * *',    dt(2026, 1, 1))  # False
pycron.is_now('0 0 1-31/7 * *', dt(2026, 1, 1))  # True

I think it is this line, target % interval == 0 assumes the field starts at 0,
while the a-b/n branch just above uses range(start_int, end + 1, step) which is
why the long form works:

pycron/pycron/__init__.py

Lines 97 to 104 in 8a17626

if "/" in _value:
v, interval = [x.strip() for x in _value.split("/")]
# Not sure if applicable for every situation, but just to make sure...
if v != "*":
continue
# If the remainder is zero, this matches
if target % _to_int(interval, allow_daynames=allow_daynames) == 0:
return True

_parse_arg does not know which field it is parsing so it cannot know the minimum.
Passing one in and using (target - min_value) % interval == 0 gives the right
answers for both fields and leaves minutes and hours alone.

It would change when existing jobs fire though, so maybe worth a changelog note.

Can do a pull request with tests if you want.

Version: pycron 3.2.0, Python 3.13

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions