diff options
author | Lowell Alleman <lowell@kintyre.co> | 2021-03-29 21:25:50 -0400 |
---|---|---|
committer | kiorky <kiorky@cryptelium.net> | 2021-04-07 17:01:14 +0200 |
commit | 60ced5cd2a96f0a62d9ef87d72065aaf36349ffb (patch) | |
tree | c9d62db0f0a0e6611654a87fe4f8069f3d507f78 | |
parent | eaf067317ae18e3831c616b75db2239ba72e6241 (diff) | |
download | croniter-60ced5cd2a96f0a62d9ef87d72065aaf36349ffb.tar.gz |
Cleanup PR based on feedback
- Optimize scenario where only 'L' modifier exists in the wday expression to
reduce the number of invocations of the calendar module.
- Cleanup and combined parsing logic for specialty wday syntax (d#n/ Ld)
- Add new unit tests to confirm proper exception handling for out-of-range 'L'
and '#' nth month for the wday expression.
- Add examples of new syntax in README.
- Update changelog to cover new syntax and new exception.
-rw-r--r-- | README.rst | 5 | ||||
-rw-r--r-- | docs/CHANGES.rst | 9 | ||||
-rw-r--r-- | src/croniter/croniter.py | 42 | ||||
-rwxr-xr-x | src/croniter/tests/test_croniter.py | 12 |
4 files changed, 49 insertions, 19 deletions
@@ -50,8 +50,13 @@ A simple example:: >>> print(iter.get_next(datetime)) # 2010-09-01 04:02:00 >>> print(iter.get_next(datetime)) # 2010-12-01 04:02:00 >>> print(iter.get_next(datetime)) # 2011-06-01 04:02:00 + >>> >>> iter = croniter('0 0 * * sat#1,sun#2', base) >>> print(iter.get_next(datetime)) # datetime.datetime(2010, 2, 6, 0, 0) + >>> iter = croniter('0 0 * * 5#3,L5', base) # 3rd and last Fridays of the month + >>> print(iter.get_next(datetime)) # 2010-01-29 00:00:00 + >>> print(iter.get_next(datetime)) # 2010-02-19 00:00:00 + All you need to know is how to use the constructor and the ``get_next`` method, the signature of these methods are listed below:: diff --git a/docs/CHANGES.rst b/docs/CHANGES.rst index 2ef4104..7ef9e9d 100644 --- a/docs/CHANGES.rst +++ b/docs/CHANGES.rst @@ -4,7 +4,14 @@ Changelog 1.0.11 (unreleased) ------------------- -- Nothing changed yet. +- Add support for ``L`` in the day_of_week component. This enable expressions like ``* * * * L4``, which means last Thursday of the month. This resolves #159. + [Kintyre] +- Create ``CroniterUnsupportedSyntaxError`` exception for situations where CRON syntax may be valid but some combinations of features is not supported. + Currently, this is used when the ``day_of_week`` component has a combination of literal values and nth/last syntax at the same time. + For example, ``0 0 * * 1,L6`` or ``0 0 * * 15,sat#1`` will both raise this exception because of mixing literal days of the week with nth-weekday or last-weekday syntax. + This *may* impact existing cron expressions in prior releases, because ``0 0 * * 15,sat#1`` was previously allowed but incorrectly handled. + [Kintyre] + - Update ``croniter_range()`` to allow an alternate ``croniter`` class to be used. Helpful when using a custom class derived from croniter. [Kintyre] diff --git a/src/croniter/croniter.py b/src/croniter/croniter.py index c2f6e1d..17e883d 100644 --- a/src/croniter/croniter.py +++ b/src/croniter/croniter.py @@ -17,7 +17,7 @@ import calendar step_search_re = re.compile(r'^([^-]+)-([^-/]+)(/(\d+))?$') only_int_re = re.compile(r'^\d+$') star_or_int_re = re.compile(r'^(\d+|\*)$') -last_weekday_re = re.compile(r'^l([0-7])$') +special_weekday_re = re.compile(r'^(\w+)#(\d+)|l(\d+)$') VALID_LEN_EXPRESSION = [5, 6] @@ -394,24 +394,24 @@ class croniter(object): candidates = [] for wday, nth in nth_weekday_of_month.items(): - # XXX: Optimize to only run calendar, when needed: if len(nth) > 1 or nth[0] != "l": - w = (wday + 6) % 7 - c = calendar.Calendar(w).monthdayscalendar(d.year, d.month) - if c[0][0] == 0: - c.pop(0) + c = None + if nth != {"l"}: + w = (wday + 6) % 7 + c = calendar.Calendar(w).monthdayscalendar(d.year, d.month) + if c[0][0] == 0: + c.pop(0) for n in nth: if n == "l": candidate = self._get_last_weekday_of_month(d.year, d.month, wday) + elif len(c) < n: + continue else: - if len(c) < n: - continue candidate = c[n - 1][0] if ( (is_prev and candidate <= d.day) or (not is_prev and d.day <= candidate) ): candidates.append(candidate) - del w, c if not candidates: if is_prev: @@ -592,18 +592,24 @@ class croniter(object): if i == 4: # Handle special case in the day-of-week expression - m = last_weekday_re.match(str(e)) + m = special_weekday_re.match(str(e)) if m: - e = m.group(1) - nth = "l" - else: - e, sep, nth = str(e).partition('#') + orig_e = e + e, nth, last = m.groups() if nth: - if not re.match(r'[1-5]', nth): + try: + nth = int(nth) + assert (nth >= 1 and nth <= 5) + except (ValueError, AssertionError): raise CroniterBadCronError( - "[{0}] is not acceptable".format(expr_format)) - nth = int(nth) - del sep + "[{0}] is not acceptable. Invalid day_of_week " + "value: '{1}'".format(expr_format, orig_e)) + elif last: + nth = "l" + e = last + del last, orig_e + else: + nth = None # 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 fd82580..c171983 100755 --- a/src/croniter/tests/test_croniter.py +++ b/src/croniter/tests/test_croniter.py @@ -1308,6 +1308,18 @@ class CroniterTest(base.TestCase): self.assertListEqual(getn(cron_b, 3), expect_b) self.assertListEqual(getn(cron_c, 5), expect_c) + def test_nth_out_of_range(self): + with self.assertRaises(CroniterBadCronError): + croniter("0 0 * * 1#7") + with self.assertRaises(CroniterBadCronError): + croniter("0 0 * * 1#0") + + def test_last_out_of_range(self): + with self.assertRaises(CroniterBadCronError): + croniter("0 0 * * L-1") + with self.assertRaises(CroniterBadCronError): + croniter("0 0 * * L8") + def test_issue_142_dow(self): ret = [] for i in range(1, 31): |