summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMathieu Le Marec - Pasquet <kiorky@cryptelium.net>2024-11-24 22:04:05 +0100
committerMathieu Le Marec - Pasquet <kiorky@cryptelium.net>2023-04-07 15:42:48 +0200
commit137434227162c9c16b3e4acfcbfc4d5c7615bc28 (patch)
treef7573acbd5c5981178c8b21e78a6e9f7d38a046c
parent9bc488fe77ea905a821ae0b3d066128db8cac879 (diff)
downloadcroniter-137434227162c9c16b3e4acfcbfc4d5c7615bc28.tar.gz
Fix DOW hash parsing
This fixes #33
-rw-r--r--src/croniter/croniter.py43
-rwxr-xr-xsrc/croniter/tests/test_croniter.py62
2 files changed, 88 insertions, 17 deletions
diff --git a/src/croniter/croniter.py b/src/croniter/croniter.py
index 1e4b199..af3130f 100644
--- a/src/croniter/croniter.py
+++ b/src/croniter/croniter.py
@@ -3,6 +3,7 @@
from __future__ import absolute_import, print_function, division
+import copy
import math
import re
import sys
@@ -21,10 +22,20 @@ except ImportError:
OrderedDict = dict # py26 degraded mode, expanders order will not be immutable
+M_ALPHAS = {'jan': 1, 'feb': 2, 'mar': 3, 'apr': 4, 'may': 5, 'jun': 6,
+ 'jul': 7, 'aug': 8, 'sep': 9, 'oct': 10, 'nov': 11, 'dec': 12}
+DOW_ALPHAS = {'sun': 0, 'mon': 1, 'tue': 2, 'wed': 3, 'thu': 4, 'fri': 5, 'sat': 6}
+ALPHAS = {}
+for i in M_ALPHAS, DOW_ALPHAS:
+ ALPHAS.update(i)
step_search_re = re.compile(r'^([^-]+)-([^-/]+)(/(\d+))?$')
only_int_re = re.compile(r'^\d+$')
+
+WEEKDAYS = '|'.join(DOW_ALPHAS.keys())
+MONTHS = '|'.join(M_ALPHAS.keys())
star_or_int_re = re.compile(r'^(\d+|\*)$')
-special_weekday_re = re.compile(r'^(\w+)#(\d+)|l(\d+)$')
+special_dow_re = re.compile(rf'^(?P<pre>((?P<he>(({WEEKDAYS})(-({WEEKDAYS}))?)'
+ rf'|(({MONTHS})(-({MONTHS}))?)|\w+)#)|l)(?P<last>\d+)$')
hash_expression_re = re.compile(
r'^(?P<hash_type>h|r)(\((?P<range_begin>\d+)-(?P<range_end>\d+)\))?(\/(?P<divisor>\d+))?$'
)
@@ -106,10 +117,9 @@ class croniter(object):
{}, # 1: hour
{"l": "l"}, # 2: dom
# 3: mon
- {'jan': 1, 'feb': 2, 'mar': 3, 'apr': 4, 'may': 5, 'jun': 6,
- 'jul': 7, 'aug': 8, 'sep': 9, 'oct': 10, 'nov': 11, 'dec': 12},
+ copy.deepcopy(M_ALPHAS),
# 4: dow
- {'sun': 0, 'mon': 1, 'tue': 2, 'wed': 3, 'thu': 4, 'fri': 5, 'sat': 6},
+ copy.deepcopy(DOW_ALPHAS),
# command/user
{}
)
@@ -635,27 +645,26 @@ class croniter(object):
while len(e_list) > 0:
e = e_list.pop()
+ nth = None
if i == 4:
- # Handle special case in the day-of-week expression
- m = special_weekday_re.match(str(e))
- if m:
- orig_e = e
- e, nth, last = m.groups()
- if nth:
+ # Handle special case in the dow expression: 2#3, l3
+ special_dow_rem = special_dow_re.match(str(e))
+ if special_dow_rem:
+ g = special_dow_rem.groupdict()
+ he, last = g.get('he', ''), g.get('last', '')
+ if he:
+ e = he
try:
- nth = int(nth)
+ nth = int(last)
assert (nth >= 1 and nth <= 5)
- except (ValueError, AssertionError):
+ except (KeyError, ValueError, AssertionError):
raise CroniterBadCronError(
"[{0}] is not acceptable. Invalid day_of_week "
- "value: '{1}'".format(expr_format, orig_e))
+ "value: '{1}'".format(expr_format, nth))
elif last:
- nth = "l"
e = last
- del last, orig_e
- else:
- nth = None
+ nth = g['pre'] # 'l'
# Before matching step_search_re, normalize "*" to "{min}-{max}".
# Example: in the minute field, "*/5" normalizes to "0-59/5"
diff --git a/src/croniter/tests/test_croniter.py b/src/croniter/tests/test_croniter.py
index 3a95490..a9d8ade 100755
--- a/src/croniter/tests/test_croniter.py
+++ b/src/croniter/tests/test_croniter.py
@@ -1473,6 +1473,68 @@ class CroniterTest(base.TestCase):
self.assertEqual(n1, datetime_to_timestamp(base) + 60)
+ def test_issue_k33(self):
+ y = 2018
+ # At 11:30 PM, between day 1 and 7 of the month, Monday through Friday, only in January
+ ret = []
+ for i in range(10):
+ cron = croniter("30 23 1-7 JAN MON-FRI#1", datetime(y+i, 1, 1), ret_type=datetime)
+ for j in range(7):
+ d = cron.get_next()
+ if d.year == y + i:
+ ret.append(d)
+ rets = [datetime(2018, 1, 1, 23, 30),
+ datetime(2018, 1, 2, 23, 30),
+ datetime(2018, 1, 3, 23, 30),
+ datetime(2018, 1, 4, 23, 30),
+ datetime(2018, 1, 5, 23, 30),
+ datetime(2019, 1, 1, 23, 30),
+ datetime(2019, 1, 2, 23, 30),
+ datetime(2019, 1, 3, 23, 30),
+ datetime(2019, 1, 4, 23, 30),
+ datetime(2019, 1, 7, 23, 30),
+ datetime(2020, 1, 1, 23, 30),
+ datetime(2020, 1, 2, 23, 30),
+ datetime(2020, 1, 3, 23, 30),
+ datetime(2020, 1, 6, 23, 30),
+ datetime(2020, 1, 7, 23, 30),
+ datetime(2021, 1, 1, 23, 30),
+ datetime(2021, 1, 4, 23, 30),
+ datetime(2021, 1, 5, 23, 30),
+ datetime(2021, 1, 6, 23, 30),
+ datetime(2021, 1, 7, 23, 30),
+ datetime(2022, 1, 3, 23, 30),
+ datetime(2022, 1, 4, 23, 30),
+ datetime(2022, 1, 5, 23, 30),
+ datetime(2022, 1, 6, 23, 30),
+ datetime(2022, 1, 7, 23, 30),
+ datetime(2023, 1, 2, 23, 30),
+ datetime(2023, 1, 3, 23, 30),
+ datetime(2023, 1, 4, 23, 30),
+ datetime(2023, 1, 5, 23, 30),
+ datetime(2023, 1, 6, 23, 30),
+ datetime(2024, 1, 1, 23, 30),
+ datetime(2024, 1, 2, 23, 30),
+ datetime(2024, 1, 3, 23, 30),
+ datetime(2024, 1, 4, 23, 30),
+ datetime(2024, 1, 5, 23, 30),
+ datetime(2025, 1, 1, 23, 30),
+ datetime(2025, 1, 2, 23, 30),
+ datetime(2025, 1, 3, 23, 30),
+ datetime(2025, 1, 6, 23, 30),
+ datetime(2025, 1, 7, 23, 30),
+ datetime(2026, 1, 1, 23, 30),
+ datetime(2026, 1, 2, 23, 30),
+ datetime(2026, 1, 5, 23, 30),
+ datetime(2026, 1, 6, 23, 30),
+ datetime(2026, 1, 7, 23, 30),
+ datetime(2027, 1, 1, 23, 30),
+ datetime(2027, 1, 4, 23, 30),
+ datetime(2027, 1, 5, 23, 30),
+ datetime(2027, 1, 6, 23, 30),
+ datetime(2027, 1, 7, 23, 30)]
+ self.assertEqual(ret, rets)
+ croniter.expand("30 6 1-7 MAY MON#1")
if __name__ == '__main__':
unittest.main()