From f7ddc1f69c8bff3a86b499e6fe82efc5db53b279 Mon Sep 17 00:00:00 2001 From: Devraj Pal Date: Fri, 18 Sep 2026 23:52:59 +0530 Subject: [PATCH] cron: accept a step over a range (e.g. 1-5/2) and in list items _validate_cron_component checked for /, - and , in a fixed order, so a stepped range like "1-5/2" (valid cron) was rejected, as was any comma-list whose items used ranges or steps. Split each field on "," first, then parse each item as an optional base ("*", a number or a range) followed by an optional step. --- src/validators/cron.py | 42 +++++++++++++++++++----------------------- tests/test_cron.py | 4 +++- 2 files changed, 22 insertions(+), 24 deletions(-) diff --git a/src/validators/cron.py b/src/validators/cron.py index a8449b6a..8c7a91e7 100644 --- a/src/validators/cron.py +++ b/src/validators/cron.py @@ -4,40 +4,36 @@ from .utils import validator -def _validate_cron_component(component: str, min_val: int, max_val: int): - if component == "*": - return True - - if component.isdecimal(): - return min_val <= int(component) <= max_val - - if "/" in component: - parts = component.split("/") - if len(parts) != 2 or not parts[1].isdecimal() or int(parts[1]) < 1: +def _validate_cron_item(item: str, min_val: int, max_val: int): + # An item may carry a step, e.g. "*/5", "1-30/2" or "5/10". + if "/" in item: + base, _, step = item.partition("/") + if not step.isdecimal() or int(step) < 1: return False - if parts[0] == "*": - return True - return parts[0].isdecimal() and min_val <= int(parts[0]) <= max_val + else: + base = item + + if base == "*": + return True - if "-" in component: - parts = component.split("-") + if "-" in base: + parts = base.split("-") if len(parts) != 2 or not parts[0].isdecimal() or not parts[1].isdecimal(): return False start, end = int(parts[0]), int(parts[1]) return min_val <= start <= max_val and min_val <= end <= max_val and start <= end - if "," in component: - for item in component.split(","): - if not _validate_cron_component(item, min_val, max_val): - return False - return True - # return all( - # _validate_cron_component(item, min_val, max_val) for item in component.split(",") - # ) # throws type error. why? + if base.isdecimal(): + return min_val <= int(base) <= max_val return False +def _validate_cron_component(component: str, min_val: int, max_val: int): + # A field is a comma-separated list of items, each optionally stepped. + return all(_validate_cron_item(item, min_val, max_val) for item in component.split(",")) + + @validator def cron(value: str, /): """Return whether or not given value is a valid cron string. diff --git a/tests/test_cron.py b/tests/test_cron.py index e0b2a199..f1963c45 100644 --- a/tests/test_cron.py +++ b/tests/test_cron.py @@ -23,7 +23,9 @@ "*/15 0,6,12,18 * * *", "0 12 * * 0", "*/61 * * * *", - # "5-10/2 * * * *", # this is valid, but not supported yet + "5-10/2 * * * *", + "1-30/2 * * * *", + "15,45 6-18/3 * * *", ], ) def test_returns_true_on_valid_cron(value: str):