Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 12 additions & 6 deletions pycron/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,9 @@ def _to_int(value: Any, allow_daynames: bool = False) -> int:
raise ValueError("Failed to parse string to integer")


def _parse_arg(value: str, target: int, allow_daynames: bool = False) -> bool:
def _parse_arg(
value: str, target: int, allow_daynames: bool = False, min_value: int = 0
) -> bool:
"""
Parses a given value and checks if it matches the provided target.
Allowing day names is optional, but can be useful for certain situations.
Expand All @@ -46,6 +48,8 @@ def _parse_arg(value: str, target: int, allow_daynames: bool = False) -> bool:
value = value to parse and check
target = target value to compare with
allow_daynames = True, to allow values like Mon or Monday
min_value = smallest value the field accepts, used as the
starting point for step values like */2
@output: True if the value matches the target, False otherwise
"""
# pylint: disable=too-many-branches
Expand Down Expand Up @@ -99,8 +103,10 @@ def _parse_arg(value: str, target: int, allow_daynames: bool = False) -> bool:
# 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:
# Steps count from the start of the field, not from zero,
# so */2 on a 1-based field means 1, 3, 5... not 2, 4, 6...
interval_int = _to_int(interval, allow_daynames=allow_daynames)
if (target - min_value) % interval_int == 0:
return True

return False
Expand All @@ -124,18 +130,18 @@ def is_now(s: str, dt: Optional[datetime] = None) -> bool:
# Special case if both of the 'day' -fields are set -> allow either one to match
# See: https://github.com/kipe/pycron/issues/29
if "*" not in dom and "*" not in dow:
day_rule = _parse_arg(dom, dt.day) or _parse_arg(
day_rule = _parse_arg(dom, dt.day, min_value=1) or _parse_arg(
dow, 0 if weekday == 7 else weekday, True
)
else:
day_rule = _parse_arg(dom, dt.day) and _parse_arg(
day_rule = _parse_arg(dom, dt.day, min_value=1) and _parse_arg(
dow, 0 if weekday == 7 else weekday, True
)

return (
_parse_arg(minute, dt.minute)
and _parse_arg(hour, dt.hour)
and _parse_arg(month, dt.month)
and _parse_arg(month, dt.month, min_value=1)
and day_rule
)

Expand Down
12 changes: 11 additions & 1 deletion tests/test_dom.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ def test_dom(self):
def run(now):
assert pycron.is_now("* * * * *", now)
assert pycron.is_now("* * 18 * *", now)
assert pycron.is_now("* * */6 * *", now)
# Steps count from the 1st, so */6 is the 1st, 7th, 13th, 19th...
# and the 18th is not one of those.
assert pycron.is_now("* * */6 * *", now) is False
assert pycron.is_now("* * 1,16,18 * *", now)
assert pycron.is_now("* * 19 * *", now) is False
assert pycron.is_now("* * */4 * *", now) is False
Expand All @@ -29,3 +31,11 @@ def run(now):
run(arrow.get(now))
run(udatetime.from_string(now.isoformat()))
run(Delorean(datetime=now, timezone="UTC").datetime)

def test_dom_step(self):
# */6 is just a shorthand for 1-31/6, both start counting from the 1st.
assert pycron.is_now("* * */6 * *", datetime(2015, 6, 1, 16, 7))
assert pycron.is_now("* * */6 * *", datetime(2015, 6, 7, 16, 7))
assert pycron.is_now("* * */6 * *", datetime(2015, 6, 19, 16, 7))
assert pycron.is_now("* * */6 * *", datetime(2015, 6, 6, 16, 7)) is False
assert pycron.is_now("* * 1-31/6 * *", datetime(2015, 6, 7, 16, 7))
14 changes: 12 additions & 2 deletions tests/test_month.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,12 @@ def test_parser(self):
def run(now):
assert pycron.is_now("* * * * *", now)
assert pycron.is_now("* * * 6 *", now)
assert pycron.is_now("* * * */2 *", now)
# Steps count from January, so */2 is Jan, Mar, May... and */5 is
# Jan, Jun, Nov. June matches the second one, not the first.
assert pycron.is_now("* * * */2 *", now) is False
assert pycron.is_now("* * * 1,4,6,12 *", now)
assert pycron.is_now("* * * 5 *", now) is False
assert pycron.is_now("* * * */5 *", now) is False
assert pycron.is_now("* * * */5 *", now)
assert pycron.is_now("* * * 1,4,12 *", now) is False
assert pycron.MONTH_CHOICES[now.month - 1][1] == "June"
assert pycron.is_now("* * * 5-8 *", now)
Expand All @@ -29,3 +31,11 @@ def run(now):
run(arrow.get(now))
run(udatetime.from_string(now.isoformat()))
run(Delorean(datetime=now, timezone="UTC").datetime)

def test_month_step(self):
# */3 is just a shorthand for 1-12/3, both start counting from January.
assert pycron.is_now("* * * */3 *", datetime(2015, 1, 18, 16, 7))
assert pycron.is_now("* * * */3 *", datetime(2015, 4, 18, 16, 7))
assert pycron.is_now("* * * */3 *", datetime(2015, 7, 18, 16, 7))
assert pycron.is_now("* * * */3 *", datetime(2015, 6, 18, 16, 7)) is False
assert pycron.is_now("* * * 1-12/3 *", datetime(2015, 4, 18, 16, 7))