Hello community, here is the log from the commit of package python-croniter for openSUSE:Factory checked in at 2020-11-26 23:15:17 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-croniter (Old) and /work/SRC/openSUSE:Factory/.python-croniter.new.5913 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-croniter" Thu Nov 26 23:15:17 2020 rev:14 rq:850942 version:0.3.36 Changes: -------- --- /work/SRC/openSUSE:Factory/python-croniter/python-croniter.changes 2020-07-17 20:51:49.208981002 +0200 +++ /work/SRC/openSUSE:Factory/.python-croniter.new.5913/python-croniter.changes 2020-11-26 23:16:11.877075064 +0100 @@ -1,0 +2,12 @@ +Thu Nov 26 09:22:47 UTC 2020 - Dirk Mueller <dmuel...@suse.com> + +- update to 0.3.36: +- Updated docs section regarding ``max_years_between_matches`` to be more shorter and hopefully more relevant. +- Add a new initialization parameter ``max_years_between_matches`` to support finding the next/previous date beyond the default 1 year window, if so desired. Updated README to include additional notes and example of this usage. Fixes #145. +- The ``croniter_range()`` function was updated to automatically determines the appropriate ``max_years_between_matches`` value, this preventing handling of the ``CroniterBadDateError`` exception. +- Updated exception handling classes: ``CroniterBadDateError`` now only + applies during date finding operations (next/prev), and all parsing errors can now be caught using ``CroniterBadCronError``. The ``CroniterNotAlphaError`` exception is now a subclass of ``CroniterBadCronError``. A brief description of each exception class was added as an inline docstring. +- Updated iterable interfaces to replace the ``CroniterBadDateError`` with ``StopIteration`` if (and only if) the ``max_years_between_matches`` argument is provided. The rationale here is that if the user has specified the max tolerance between matches, then there's no need to further inform them of no additional matches. Just stop the iteration. This also keeps backwards compatibility. +- Minor docs update + +------------------------------------------------------------------- Old: ---- croniter-0.3.34.tar.gz New: ---- croniter-0.3.36.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-croniter.spec ++++++ --- /var/tmp/diff_new_pack.dM8S6n/_old 2020-11-26 23:16:12.485075536 +0100 +++ /var/tmp/diff_new_pack.dM8S6n/_new 2020-11-26 23:16:12.489075539 +0100 @@ -18,7 +18,7 @@ %{?!python_module:%define python_module() python-%{**} python3-%{**}} Name: python-croniter -Version: 0.3.34 +Version: 0.3.36 Release: 0 Summary: Python iterators for datetime objects with cron-like format License: MIT ++++++ croniter-0.3.34.tar.gz -> croniter-0.3.36.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/croniter-0.3.34/PKG-INFO new/croniter-0.3.36/PKG-INFO --- old/croniter-0.3.34/PKG-INFO 2020-06-19 15:28:16.774030200 +0200 +++ new/croniter-0.3.36/PKG-INFO 2020-11-02 17:09:51.354224200 +0100 @@ -1,6 +1,6 @@ Metadata-Version: 1.2 Name: croniter -Version: 0.3.34 +Version: 0.3.36 Summary: croniter provides iteration for datetime object with cron like format Home-page: http://github.com/kiorky/croniter Author: Matsumoto Taichi, kiorky @@ -144,10 +144,37 @@ >>> croniter.match("2 4 1 * wed", datetime(2019, 1, 1, 4, 2, 0, 0), day_or=False) # 04:02 on every 1st day of the month if it is a Wednesday False + Gaps between date matches + ========================= + For performance reasons, croniter limits the amount of CPU cycles spent attempting to find the next match. + Starting in v0.3.35, this behavior is configurable via the ``max_years_between_matches`` parameter, and the default window has been increased from 1 year to 50 years. + + The defaults should be fine for many use cases. + Applications that evaluate multiple cron expressions or handle cron expressions from untrusted sources or end-users should use this parameter. + Iterating over sparse cron expressions can result in increased CPU consumption or a raised ``CroniterBadDateError`` exception which indicates that croniter has given up attempting to find the next (or previous) match. + Explicitly specifying ``max_years_between_matches`` provides a way to limit CPU utilization and simplifies the iterable interface by eliminating the need for ``CroniterBadDateError``. + The difference in the iterable interface is based on the reasoning that whenever ``max_years_between_matches`` is explicitly agreed upon, there is no need for croniter to signal that it has given up; simply stopping the iteration is preferable. + + This example matches 4 AM Friday, January 1st. + Since January 1st isn't often a Friday, there may be a few years between each occurrence. + Setting the limit to 15 years ensures all matches:: + + >>> it = croniter("0 4 1 1 fri", datetime(2000,1,1), day_or=False, max_years_between_matches=15).all_next(datetime) + >>> for i in range(5): + ... print(next(it)) + ... + 2010-01-01 04:00:00 + 2016-01-01 04:00:00 + 2021-01-01 04:00:00 + 2027-01-01 04:00:00 + 2038-01-01 04:00:00 + + However, when only concerned with dates within the next 5 years, simply set ``max_years_between_matches=5`` in the above example. + This will result in no matches found, but no additional cycles will be wasted on unwanted matches far in the future. Iterating over a range using cron ================================= - Finding all matching times in at time range can be handled with the ``croniter_range()`` function. This is much like the builtin ``range(start,stop,step)`` function, but for dates using a cron expression as "step". + Find matches within a range using the ``croniter_range()`` function. This is much like the builtin ``range(start,stop,step)`` function, but for dates. The `step` argument is a cron expression. Added in (>=0.3.34) List the first Saturday of every month in 2019:: @@ -156,6 +183,7 @@ >>> for dt in croniter_range(datetime(2019, 1, 1), datetime(2019, 12, 31), "0 0 * * sat#1"): >>> print(dt) + Develop this package ==================== @@ -197,18 +225,48 @@ - chris-baynes - ipartola - yuzawa-san + - lowell80 (Kintyre) + - scop Changelog ============== + 0.3.36 (2020-11-02) + ------------------- + + - Nothing changed yet. + - Updated docs section regarding ``max_years_between_matches`` to be more shorter and hopefully more relevant. + [Kintyre] + - Don't install tests + [scop] + + + 0.3.35 (2020-10-11) + ------------------- + + - Handle L in ranges. This fixes #142. + [kiorky] + - Add a new initialization parameter ``max_years_between_matches`` to support finding the next/previous date beyond the default 1 year window, if so desired. Updated README to include additional notes and example of this usage. Fixes #145. + [Kintyre] + - The ``croniter_range()`` function was updated to automatically determines the appropriate ``max_years_between_matches`` value, this preventing handling of the ``CroniterBadDateError`` exception. + [Kintyre] + - Updated exception handling classes: ``CroniterBadDateError`` now only + applies during date finding operations (next/prev), and all parsing errors can now be caught using ``CroniterBadCronError``. The ``CroniterNotAlphaError`` exception is now a subclass of ``CroniterBadCronError``. A brief description of each exception class was added as an inline docstring. + [Kintyre] + - Updated iterable interfaces to replace the ``CroniterBadDateError`` with ``StopIteration`` if (and only if) the ``max_years_between_matches`` argument is provided. The rationale here is that if the user has specified the max tolerance between matches, then there's no need to further inform them of no additional matches. Just stop the iteration. This also keeps backwards compatibility. + [Kintyre] + - Minor docs update + [Kintyre] + + 0.3.34 (2020-06-19) ------------------- - - Feat croniter_range(start, stop, cron) - [Kintyr] + - Feat ``croniter_range(start, stop, cron)`` + [Kintyre] - Optimization for poorly written cron expression - [Kintyr] + [Kintyre] 0.3.33 (2020-06-15) ------------------- @@ -268,7 +326,7 @@ 0.3.23 (2018-05-23) ------------------- - - fix ``get_next`` while perserving the fix of ``get_prev`` in 7661c2aaa + - fix ``get_next`` while preserving the fix of ``get_prev`` in 7661c2aaa [Avikam Agur <avi...@pagaya-inv.com>] @@ -360,7 +418,7 @@ 0.3.10 (2015-11-29) ------------------- - - The fuctionality of 'l' as day of month was broken, since the month variable + - The functionality of 'l' as day of month was broken, since the month variable was not properly updated [Iddo Aviram <iddo.avi...@similarweb.com>] @@ -401,7 +459,7 @@ ------------------ - Python 3 compat - - QA Relase + - QA Release 0.3.3 (2012-09-29) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/croniter-0.3.34/README.rst new/croniter-0.3.36/README.rst --- old/croniter-0.3.34/README.rst 2020-06-19 15:28:16.000000000 +0200 +++ new/croniter-0.3.36/README.rst 2020-11-02 17:09:51.000000000 +0100 @@ -136,10 +136,37 @@ >>> croniter.match("2 4 1 * wed", datetime(2019, 1, 1, 4, 2, 0, 0), day_or=False) # 04:02 on every 1st day of the month if it is a Wednesday False +Gaps between date matches +========================= +For performance reasons, croniter limits the amount of CPU cycles spent attempting to find the next match. +Starting in v0.3.35, this behavior is configurable via the ``max_years_between_matches`` parameter, and the default window has been increased from 1 year to 50 years. + +The defaults should be fine for many use cases. +Applications that evaluate multiple cron expressions or handle cron expressions from untrusted sources or end-users should use this parameter. +Iterating over sparse cron expressions can result in increased CPU consumption or a raised ``CroniterBadDateError`` exception which indicates that croniter has given up attempting to find the next (or previous) match. +Explicitly specifying ``max_years_between_matches`` provides a way to limit CPU utilization and simplifies the iterable interface by eliminating the need for ``CroniterBadDateError``. +The difference in the iterable interface is based on the reasoning that whenever ``max_years_between_matches`` is explicitly agreed upon, there is no need for croniter to signal that it has given up; simply stopping the iteration is preferable. + +This example matches 4 AM Friday, January 1st. +Since January 1st isn't often a Friday, there may be a few years between each occurrence. +Setting the limit to 15 years ensures all matches:: + + >>> it = croniter("0 4 1 1 fri", datetime(2000,1,1), day_or=False, max_years_between_matches=15).all_next(datetime) + >>> for i in range(5): + ... print(next(it)) + ... + 2010-01-01 04:00:00 + 2016-01-01 04:00:00 + 2021-01-01 04:00:00 + 2027-01-01 04:00:00 + 2038-01-01 04:00:00 + +However, when only concerned with dates within the next 5 years, simply set ``max_years_between_matches=5`` in the above example. +This will result in no matches found, but no additional cycles will be wasted on unwanted matches far in the future. Iterating over a range using cron ================================= -Finding all matching times in at time range can be handled with the ``croniter_range()`` function. This is much like the builtin ``range(start,stop,step)`` function, but for dates using a cron expression as "step". +Find matches within a range using the ``croniter_range()`` function. This is much like the builtin ``range(start,stop,step)`` function, but for dates. The `step` argument is a cron expression. Added in (>=0.3.34) List the first Saturday of every month in 2019:: @@ -148,6 +175,7 @@ >>> for dt in croniter_range(datetime(2019, 1, 1), datetime(2019, 12, 31), "0 0 * * sat#1"): >>> print(dt) + Develop this package ==================== @@ -189,3 +217,5 @@ - chris-baynes - ipartola - yuzawa-san + - lowell80 (Kintyre) + - scop diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/croniter-0.3.34/docs/CHANGES.rst new/croniter-0.3.36/docs/CHANGES.rst --- old/croniter-0.3.34/docs/CHANGES.rst 2020-06-19 15:28:16.000000000 +0200 +++ new/croniter-0.3.36/docs/CHANGES.rst 2020-11-02 17:09:51.000000000 +0100 @@ -1,13 +1,41 @@ Changelog ============== +0.3.36 (2020-11-02) +------------------- + +- Nothing changed yet. +- Updated docs section regarding ``max_years_between_matches`` to be more shorter and hopefully more relevant. + [Kintyre] +- Don't install tests + [scop] + + +0.3.35 (2020-10-11) +------------------- + +- Handle L in ranges. This fixes #142. + [kiorky] +- Add a new initialization parameter ``max_years_between_matches`` to support finding the next/previous date beyond the default 1 year window, if so desired. Updated README to include additional notes and example of this usage. Fixes #145. + [Kintyre] +- The ``croniter_range()`` function was updated to automatically determines the appropriate ``max_years_between_matches`` value, this preventing handling of the ``CroniterBadDateError`` exception. + [Kintyre] +- Updated exception handling classes: ``CroniterBadDateError`` now only + applies during date finding operations (next/prev), and all parsing errors can now be caught using ``CroniterBadCronError``. The ``CroniterNotAlphaError`` exception is now a subclass of ``CroniterBadCronError``. A brief description of each exception class was added as an inline docstring. + [Kintyre] +- Updated iterable interfaces to replace the ``CroniterBadDateError`` with ``StopIteration`` if (and only if) the ``max_years_between_matches`` argument is provided. The rationale here is that if the user has specified the max tolerance between matches, then there's no need to further inform them of no additional matches. Just stop the iteration. This also keeps backwards compatibility. + [Kintyre] +- Minor docs update + [Kintyre] + + 0.3.34 (2020-06-19) ------------------- -- Feat croniter_range(start, stop, cron) - [Kintyr] +- Feat ``croniter_range(start, stop, cron)`` + [Kintyre] - Optimization for poorly written cron expression - [Kintyr] + [Kintyre] 0.3.33 (2020-06-15) ------------------- @@ -67,7 +95,7 @@ 0.3.23 (2018-05-23) ------------------- -- fix ``get_next`` while perserving the fix of ``get_prev`` in 7661c2aaa +- fix ``get_next`` while preserving the fix of ``get_prev`` in 7661c2aaa [Avikam Agur <avi...@pagaya-inv.com>] @@ -159,7 +187,7 @@ 0.3.10 (2015-11-29) ------------------- -- The fuctionality of 'l' as day of month was broken, since the month variable +- The functionality of 'l' as day of month was broken, since the month variable was not properly updated [Iddo Aviram <iddo.avi...@similarweb.com>] @@ -200,7 +228,7 @@ ------------------ - Python 3 compat -- QA Relase +- QA Release 0.3.3 (2012-09-29) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/croniter-0.3.34/setup.py new/croniter-0.3.36/setup.py --- old/croniter-0.3.34/setup.py 2020-06-19 15:28:16.000000000 +0200 +++ new/croniter-0.3.36/setup.py 2020-11-02 17:09:51.000000000 +0100 @@ -23,7 +23,7 @@ setup( name='croniter', - version='0.3.34', + version='0.3.36', py_modules=['croniter', ], description=( 'croniter provides iteration for datetime ' @@ -52,7 +52,8 @@ "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Topic :: Software Development :: Libraries :: Python Modules"], - packages=find_packages('src'), + packages=find_packages('src', exclude=['tests*', '*.tests*']), package_dir={'': 'src'}, include_package_data=True, + exclude_package_data={'croniter': ['tests/*']}, ) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/croniter-0.3.34/src/croniter/croniter.py new/croniter-0.3.36/src/croniter/croniter.py --- old/croniter-0.3.34/src/croniter/croniter.py 2020-06-19 15:28:16.000000000 +0200 +++ new/croniter-0.3.36/src/croniter/croniter.py 2020-11-02 17:09:51.000000000 +0100 @@ -21,18 +21,22 @@ class CroniterError(ValueError): + """ General top-level Croniter base exception """ pass class CroniterBadCronError(CroniterError): + """ Syntax, unknown value, or range error within a cron expression """ pass class CroniterBadDateError(CroniterError): + """ Unable to find next/prev timestamp match """ pass -class CroniterNotAlphaError(CroniterError): +class CroniterNotAlphaError(CroniterBadCronError): + """ Cron syntax contains an invalid day or month abreviation """ pass @@ -51,12 +55,15 @@ ) ALPHACONV = ( - {}, - {}, - {"l": "l"}, + {}, # 0: min + {}, # 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}, + # 4: dow {'sun': 0, 'mon': 1, 'tue': 2, 'wed': 3, 'thu': 4, 'fri': 5, 'sat': 6}, + # command/user {} ) @@ -82,10 +89,16 @@ 'expression.' def __init__(self, expr_format, start_time=None, ret_type=float, - day_or=True): + day_or=True, max_years_between_matches=None, is_prev=False): self._ret_type = ret_type self._day_or = day_or + self._max_years_btw_matches_explicitly_set = ( + max_years_between_matches is not None) + if not self._max_years_btw_matches_explicitly_set: + max_years_between_matches = 50 + self._max_years_between_matches = max(int(max_years_between_matches), 1) + if start_time is None: start_time = time() @@ -97,6 +110,7 @@ self.set_current(start_time) self.expanded, self.nth_weekday_of_month = self.expand(expr_format) + self._is_prev = is_prev @classmethod def _alphaconv(cls, index, key, expressions): @@ -161,30 +175,10 @@ return (td.microseconds + (td.seconds + td.days * 24 * 3600) * 10**6) \ / 10**6 - # iterator protocol, to enable direct use of croniter - # objects in a loop, like "for dt in croniter('5 0 * * *'): ..." - # or for combining multiple croniters into single - # dates feed using 'itertools' module - def __iter__(self): - return self - __next__ = next = get_next - - def all_next(self, ret_type=None): - '''Generator of all consecutive dates. Can be used instead of - implicit call to __iter__, whenever non-default - 'ret_type' has to be specified. - ''' - while True: - yield self._get_next(ret_type or self._ret_type, is_prev=False) - - def all_prev(self, ret_type=None): - '''Generator of all previous dates.''' - while True: - yield self._get_next(ret_type or self._ret_type, is_prev=True) - - iter = all_next # alias, you can call .iter() instead of .all_next() - - def _get_next(self, ret_type=None, is_prev=False): + def _get_next(self, ret_type=None, is_prev=None): + if is_prev is None: + is_prev = self._is_prev + self._is_prev = is_prev expanded = self.expanded[:] nth_weekday_of_month = self.nth_weekday_of_month.copy() @@ -242,6 +236,45 @@ result = dtresult return result + # iterator protocol, to enable direct use of croniter + # objects in a loop, like "for dt in croniter('5 0 * * *'): ..." + # or for combining multiple croniters into single + # dates feed using 'itertools' module + def all_next(self, ret_type=None): + '''Generator of all consecutive dates. Can be used instead of + implicit call to __iter__, whenever non-default + 'ret_type' has to be specified. + ''' + # In a Python 3.7+ world: contextlib.supress and contextlib.nullcontext could be used instead + try: + while True: + self._is_prev = False + yield self._get_next(ret_type or self._ret_type) + except CroniterBadDateError: + if self._max_years_btw_matches_explicitly_set: + return + else: + raise + + def all_prev(self, ret_type=None): + '''Generator of all previous dates.''' + try: + while True: + self._is_prev = True + yield self._get_next(ret_type or self._ret_type) + except CroniterBadDateError: + if self._max_years_btw_matches_explicitly_set: + return + else: + raise + + def iter(self, *args, **kwargs): + return (self._is_prev and self.all_prev or self.all_next) + + def __iter__(self): + return self + __next__ = next = _get_next + def _calc(self, now, expanded, nth_weekday_of_month, is_prev): if is_prev: now = math.ceil(now) @@ -414,7 +447,7 @@ proc_minute, proc_second] - while abs(year - current_year) <= 1: + while abs(year - current_year) <= self._max_years_between_matches: next = False for proc in procs: (changed, dst) = proc(dst) @@ -502,7 +535,7 @@ if i == 4: e, sep, nth = str(e).partition('#') if nth and not re.match(r'[1-5]', nth): - raise CroniterBadDateError( + raise CroniterBadCronError( "[{0}] is not acceptable".format(expr_format)) t = re.sub(r'^\*(\/.+)$', r'%d-%d\1' % ( @@ -519,6 +552,8 @@ if m: (low, high, step) = m.group(1), m.group(2), m.group(4) or 1 + if i == 2 and high == 'l': + high = '31' if not any_int_re.search(low): low = "{0}".format(cls._alphaconv(i, low, expressions)) @@ -534,7 +569,7 @@ # handle -Sun notation -> 7 high = '7' else: - raise CroniterBadDateError( + raise CroniterBadCronError( "[{0}] is not acceptable".format(expr_format)) low, high, step = map(int, [low, high, step]) @@ -633,7 +668,9 @@ else: # Reverse time order start += ms1 stop -= ms1 - ic = croniter(expr_format, start, ret_type=datetime.datetime, day_or=day_or) + year_span = math.floor(abs(stop.year - start.year)) + 1 + ic = croniter(expr_format, start, ret_type=datetime.datetime, day_or=day_or, + max_years_between_matches=year_span) # define a continue (cont) condition function and step function for the main while loop if start < stop: # Forward def cont(v): @@ -643,11 +680,14 @@ def cont(v): return v > stop step = ic.get_prev - - dt = step() - while cont(dt): - if ret_type is float: - yield ic.get_current(float) - else: - yield dt + try: dt = step() + while cont(dt): + if ret_type is float: + yield ic.get_current(float) + else: + yield dt + dt = step() + except CroniterBadDateError: + # Stop iteration when this exception is raised; no match found within the given year range + return diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/croniter-0.3.34/src/croniter/tests/test_croniter.py new/croniter-0.3.36/src/croniter/tests/test_croniter.py --- old/croniter-0.3.34/src/croniter/tests/test_croniter.py 2020-06-19 15:28:16.000000000 +0200 +++ new/croniter-0.3.36/src/croniter/tests/test_croniter.py 2020-11-02 17:09:51.000000000 +0100 @@ -6,6 +6,7 @@ from functools import partial from time import sleep import pytz +import croniter as cr from croniter import croniter, croniter_range, CroniterBadDateError, CroniterBadCronError, CroniterNotAlphaError from croniter.tests import base from tzlocal import get_localzone @@ -303,7 +304,7 @@ """ Non-optimal cron expressions that can be simplified.""" wildcard = ['*'] m, h, d, mon, dow, s = range(6) - # Test each field individually + # Test each field individually self.assertEqual(croniter('0-59 0 0 0 0').expanded[m], wildcard) self.assertEqual(croniter('0 0-23 0 0 0').expanded[h], wildcard) self.assertEqual(croniter('0 0 0-31 0 0').expanded[d], wildcard) @@ -1182,6 +1183,108 @@ "2020-03-09T03:00:00-04:00", "2020-03-10T03:00:00-04:00"]) + def test_issue_142_dow(self): + ret = [] + for i in range(1, 31): + ret.append((i, + croniter('35 * 0-l/8 * *', datetime(2020, 1, i), + ret_type=datetime).get_next()) + ) + i += 1 + self.assertEqual( + ret, + [(1, datetime(2020, 1, 1, 0, 35)), + (2, datetime(2020, 1, 8, 0, 35)), + (3, datetime(2020, 1, 8, 0, 35)), + (4, datetime(2020, 1, 8, 0, 35)), + (5, datetime(2020, 1, 8, 0, 35)), + (6, datetime(2020, 1, 8, 0, 35)), + (7, datetime(2020, 1, 8, 0, 35)), + (8, datetime(2020, 1, 8, 0, 35)), + (9, datetime(2020, 1, 16, 0, 35)), + (10, datetime(2020, 1, 16, 0, 35)), + (11, datetime(2020, 1, 16, 0, 35)), + (12, datetime(2020, 1, 16, 0, 35)), + (13, datetime(2020, 1, 16, 0, 35)), + (14, datetime(2020, 1, 16, 0, 35)), + (15, datetime(2020, 1, 16, 0, 35)), + (16, datetime(2020, 1, 16, 0, 35)), + (17, datetime(2020, 1, 24, 0, 35)), + (18, datetime(2020, 1, 24, 0, 35)), + (19, datetime(2020, 1, 24, 0, 35)), + (20, datetime(2020, 1, 24, 0, 35)), + (21, datetime(2020, 1, 24, 0, 35)), + (22, datetime(2020, 1, 24, 0, 35)), + (23, datetime(2020, 1, 24, 0, 35)), + (24, datetime(2020, 1, 24, 0, 35)), + (25, datetime(2020, 2, 1, 0, 35)), + (26, datetime(2020, 2, 1, 0, 35)), + (27, datetime(2020, 2, 1, 0, 35)), + (28, datetime(2020, 2, 1, 0, 35)), + (29, datetime(2020, 2, 1, 0, 35)), + (30, datetime(2020, 2, 1, 0, 35))]) + + def test_issue145_getnext(self): + # Example of quarterly event cron schedule + start = datetime(2020, 9, 24) + cron = "0 13 8 1,4,7,10 wed" + with self.assertRaises(CroniterBadDateError): + it = croniter(cron, start, day_or=False, max_years_between_matches=1) + it.get_next() + # New functionality (0.3.35) allowing croniter to find spare matches of cron patterns across multiple years + it = croniter(cron, start, day_or=False, max_years_between_matches=5) + self.assertEqual(it.get_next(datetime), datetime(2025, 1, 8, 13)) + + def test_issue145_range(self): + cron = "0 13 8 1,4,7,10 wed" + matches = list(croniter_range(datetime(2020, 1, 1), datetime(2020, 12, 31), cron, day_or=False)) + self.assertEqual(len(matches), 3) + self.assertEqual(matches[0], datetime(2020, 1, 8, 13)) + self.assertEqual(matches[1], datetime(2020, 4, 8, 13)) + self.assertEqual(matches[2], datetime(2020, 7, 8, 13)) + + # No matches within this range; therefore expect empty list + matches = list(croniter_range(datetime(2020, 9, 30), datetime(2020, 10, 30), cron, day_or=False)) + self.assertEqual(len(matches), 0) + + def test_explicit_year_forward(self): + start = datetime(2020, 9, 24) + cron = "0 13 8 1,4,7,10 wed" + + # Expect exception because no explict range was provided. Therefore, the caller should be made aware that an implicit limit was hit. + ccron = croniter(cron, start, day_or=False) + ccron._max_years_between_matches = 1 + iterable = ccron.all_next() + with self.assertRaises(CroniterBadDateError): + next(iterable) + + iterable = croniter(cron, start, day_or=False, max_years_between_matches=5).all_next(datetime) + n = next(iterable) + self.assertEqual(n, datetime(2025, 1, 8, 13)) + + # If the explictly given lookahead isn't enough to reach the next date, that's fine. The caller specified the maximum gap, so no just stop iteration + iterable = croniter(cron, start, day_or=False, max_years_between_matches=2).all_next(datetime) + with self.assertRaises(StopIteration): + next(iterable) + + def test_explicit_year_reverse(self): + start = datetime(2025, 1, 1) + cron = "0 13 8 1,4,7,10 wed" + + ccron = croniter(cron, start, day_or=False) + ccron._max_years_between_matches = 1 + iterable = ccron.all_prev() + with self.assertRaises(CroniterBadDateError): + next(iterable) + + iterable = croniter(cron, start, day_or=False, max_years_between_matches=5).all_prev(datetime) + n = next(iterable) + self.assertEqual(n, datetime(2020, 7, 8, 13)) + + iterable = croniter(cron, start, day_or=False, max_years_between_matches=2).all_prev() + with self.assertRaises(StopIteration): + next(iterable) + if __name__ == '__main__': unittest.main() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/croniter-0.3.34/src/croniter.egg-info/PKG-INFO new/croniter-0.3.36/src/croniter.egg-info/PKG-INFO --- old/croniter-0.3.34/src/croniter.egg-info/PKG-INFO 2020-06-19 15:28:16.000000000 +0200 +++ new/croniter-0.3.36/src/croniter.egg-info/PKG-INFO 2020-11-02 17:09:51.000000000 +0100 @@ -1,6 +1,6 @@ Metadata-Version: 1.2 Name: croniter -Version: 0.3.34 +Version: 0.3.36 Summary: croniter provides iteration for datetime object with cron like format Home-page: http://github.com/kiorky/croniter Author: Matsumoto Taichi, kiorky @@ -144,10 +144,37 @@ >>> croniter.match("2 4 1 * wed", datetime(2019, 1, 1, 4, 2, 0, 0), day_or=False) # 04:02 on every 1st day of the month if it is a Wednesday False + Gaps between date matches + ========================= + For performance reasons, croniter limits the amount of CPU cycles spent attempting to find the next match. + Starting in v0.3.35, this behavior is configurable via the ``max_years_between_matches`` parameter, and the default window has been increased from 1 year to 50 years. + + The defaults should be fine for many use cases. + Applications that evaluate multiple cron expressions or handle cron expressions from untrusted sources or end-users should use this parameter. + Iterating over sparse cron expressions can result in increased CPU consumption or a raised ``CroniterBadDateError`` exception which indicates that croniter has given up attempting to find the next (or previous) match. + Explicitly specifying ``max_years_between_matches`` provides a way to limit CPU utilization and simplifies the iterable interface by eliminating the need for ``CroniterBadDateError``. + The difference in the iterable interface is based on the reasoning that whenever ``max_years_between_matches`` is explicitly agreed upon, there is no need for croniter to signal that it has given up; simply stopping the iteration is preferable. + + This example matches 4 AM Friday, January 1st. + Since January 1st isn't often a Friday, there may be a few years between each occurrence. + Setting the limit to 15 years ensures all matches:: + + >>> it = croniter("0 4 1 1 fri", datetime(2000,1,1), day_or=False, max_years_between_matches=15).all_next(datetime) + >>> for i in range(5): + ... print(next(it)) + ... + 2010-01-01 04:00:00 + 2016-01-01 04:00:00 + 2021-01-01 04:00:00 + 2027-01-01 04:00:00 + 2038-01-01 04:00:00 + + However, when only concerned with dates within the next 5 years, simply set ``max_years_between_matches=5`` in the above example. + This will result in no matches found, but no additional cycles will be wasted on unwanted matches far in the future. Iterating over a range using cron ================================= - Finding all matching times in at time range can be handled with the ``croniter_range()`` function. This is much like the builtin ``range(start,stop,step)`` function, but for dates using a cron expression as "step". + Find matches within a range using the ``croniter_range()`` function. This is much like the builtin ``range(start,stop,step)`` function, but for dates. The `step` argument is a cron expression. Added in (>=0.3.34) List the first Saturday of every month in 2019:: @@ -156,6 +183,7 @@ >>> for dt in croniter_range(datetime(2019, 1, 1), datetime(2019, 12, 31), "0 0 * * sat#1"): >>> print(dt) + Develop this package ==================== @@ -197,18 +225,48 @@ - chris-baynes - ipartola - yuzawa-san + - lowell80 (Kintyre) + - scop Changelog ============== + 0.3.36 (2020-11-02) + ------------------- + + - Nothing changed yet. + - Updated docs section regarding ``max_years_between_matches`` to be more shorter and hopefully more relevant. + [Kintyre] + - Don't install tests + [scop] + + + 0.3.35 (2020-10-11) + ------------------- + + - Handle L in ranges. This fixes #142. + [kiorky] + - Add a new initialization parameter ``max_years_between_matches`` to support finding the next/previous date beyond the default 1 year window, if so desired. Updated README to include additional notes and example of this usage. Fixes #145. + [Kintyre] + - The ``croniter_range()`` function was updated to automatically determines the appropriate ``max_years_between_matches`` value, this preventing handling of the ``CroniterBadDateError`` exception. + [Kintyre] + - Updated exception handling classes: ``CroniterBadDateError`` now only + applies during date finding operations (next/prev), and all parsing errors can now be caught using ``CroniterBadCronError``. The ``CroniterNotAlphaError`` exception is now a subclass of ``CroniterBadCronError``. A brief description of each exception class was added as an inline docstring. + [Kintyre] + - Updated iterable interfaces to replace the ``CroniterBadDateError`` with ``StopIteration`` if (and only if) the ``max_years_between_matches`` argument is provided. The rationale here is that if the user has specified the max tolerance between matches, then there's no need to further inform them of no additional matches. Just stop the iteration. This also keeps backwards compatibility. + [Kintyre] + - Minor docs update + [Kintyre] + + 0.3.34 (2020-06-19) ------------------- - - Feat croniter_range(start, stop, cron) - [Kintyr] + - Feat ``croniter_range(start, stop, cron)`` + [Kintyre] - Optimization for poorly written cron expression - [Kintyr] + [Kintyre] 0.3.33 (2020-06-15) ------------------- @@ -268,7 +326,7 @@ 0.3.23 (2018-05-23) ------------------- - - fix ``get_next`` while perserving the fix of ``get_prev`` in 7661c2aaa + - fix ``get_next`` while preserving the fix of ``get_prev`` in 7661c2aaa [Avikam Agur <avi...@pagaya-inv.com>] @@ -360,7 +418,7 @@ 0.3.10 (2015-11-29) ------------------- - - The fuctionality of 'l' as day of month was broken, since the month variable + - The functionality of 'l' as day of month was broken, since the month variable was not properly updated [Iddo Aviram <iddo.avi...@similarweb.com>] @@ -401,7 +459,7 @@ ------------------ - Python 3 compat - - QA Relase + - QA Release 0.3.3 (2012-09-29) _______________________________________________ openSUSE Commits mailing list -- commit@lists.opensuse.org To unsubscribe, email commit-le...@lists.opensuse.org List Netiquette: https://en.opensuse.org/openSUSE:Mailing_list_netiquette List Archives: https://lists.opensuse.org/archives/list/commit@lists.opensuse.org