Hello community, here is the log from the commit of package python-croniter for openSUSE:Factory checked in at 2018-10-18 15:39:05 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-croniter (Old) and /work/SRC/openSUSE:Factory/.python-croniter.new (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-croniter" Thu Oct 18 15:39:05 2018 rev:7 rq:642790 version:0.3.20 Changes: -------- --- /work/SRC/openSUSE:Factory/python-croniter/python-croniter.changes 2017-08-28 15:17:19.223417956 +0200 +++ /work/SRC/openSUSE:Factory/.python-croniter.new/python-croniter.changes 2018-10-18 15:39:17.726095245 +0200 @@ -1,0 +2,30 @@ +Wed Oct 17 18:29:01 UTC 2018 - Jan Engelhardt <[email protected]> + +- Avoid name repetition in summary. + +------------------------------------------------------------------- +Wed Oct 17 13:29:54 UTC 2018 - [email protected] + +- update to 0.3.20 + - (tag: 0.3.20) Preparing release 0.3.20 + - pep8 + - Fix sao paulo timezone test. + - remove outdated comment + - correctly handle DST changes + - Merge pull request #89 from kiorky/master + - Back to development: 0.3.20 + - (tag: 0.3.19) Preparing release 0.3.19 + - fix #87: backward dst changes + - Merge pull request #88 from kiorky/master + - Back to development: 0.3.19 + - (tag: 0.3.18) Preparing release 0.3.18 + - Merge pull request #18 from taichino/master + - Merge pull request #86 from otherpirate/master + - Adding is_valid class method to readme + - Adding class method is_valid to validate cron syntax + - Creating base croniter error + - Merge pull request #85 from kiorky/master + - Back to development: 0.3.18 + + +------------------------------------------------------------------- Old: ---- croniter-0.3.17.tar.gz New: ---- croniter-0.3.20.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-croniter.spec ++++++ --- /var/tmp/diff_new_pack.XVCMZ9/_old 2018-10-18 15:39:18.698094140 +0200 +++ /var/tmp/diff_new_pack.XVCMZ9/_new 2018-10-18 15:39:18.702094135 +0200 @@ -1,7 +1,7 @@ # # spec file for package python-croniter # -# Copyright (c) 2017 SUSE LINUX GmbH, Nuernberg, Germany. +# Copyright (c) 2018 SUSE LINUX GmbH, Nuernberg, Germany. # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -12,26 +12,25 @@ # license that conforms to the Open Source Definition (Version 1.9) # published by the Open Source Initiative. -# Please submit bugfixes or comments via http://bugs.opensuse.org/ +# Please submit bugfixes or comments via https://bugs.opensuse.org/ # %{?!python_module:%define python_module() python-%{**} python3-%{**}} Name: python-croniter -Version: 0.3.17 +Version: 0.3.20 Release: 0 -Summary: Croniter provides iteration for datetime object with cron like format +Summary: Python iterators for datetime objects with cron-like format License: MIT Group: Development/Languages/Python Url: http://github.com/kiorky/croniter Source: https://files.pythonhosted.org/packages/source/c/croniter/croniter-%{version}.tar.gz -BuildRequires: %{python_module devel} BuildRequires: %{python_module setuptools} BuildRequires: fdupes BuildRequires: python-rpm-macros BuildRequires: unzip # Test requirements: -BuildRequires: %{python_module nose} +BuildRequires: %{python_module pytest} BuildRequires: %{python_module python-dateutil} BuildRequires: %{python_module pytz} Requires: python-python-dateutil @@ -40,7 +39,7 @@ %python_subpackages %description -croniter provides iteration for datetime object with cron like format. +croniter provides iterators for datetime object with cron-like format. %prep %setup -q -n croniter-%{version} @@ -53,7 +52,7 @@ %python_expand %fdupes %{buildroot}%{$python_sitelib} %check -%python_exec %{_bindir}/nosetests +%python_exec %{_bindir}/py.test src %files %{python_files} %defattr(-,root,root,-) ++++++ croniter-0.3.17.tar.gz -> croniter-0.3.20.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/croniter-0.3.17/PKG-INFO new/croniter-0.3.20/PKG-INFO --- old/croniter-0.3.17/PKG-INFO 2017-05-22 12:11:46.000000000 +0200 +++ new/croniter-0.3.20/PKG-INFO 2017-11-06 22:22:31.000000000 +0100 @@ -1,6 +1,6 @@ Metadata-Version: 1.1 Name: croniter -Version: 0.3.17 +Version: 0.3.20 Summary: croniter provides iteration for datetime object with cron like format Home-page: http://github.com/kiorky/croniter Author: Matsumoto Taichi, kiorky @@ -90,6 +90,11 @@ >>> print itr.get_prev(datetime) # 2010-07-01 00:00:00 >>> print itr.get_prev(datetime) # 2010-06-01 00:00:00 + You can validate your crons using ``is_valid`` class method. (>= 0.3.18):: + + >>> croniter.is_valid('0 0 1 * *') # True + >>> croniter.is_valid('0 wrong_value 1 * *') # False + About DST ========= Be sure to init your croniter instance with a TZ aware datetime for this to work !:: @@ -144,6 +149,27 @@ Changelog ============== + 0.3.20 (2017-11-06) + ------------------- + + - More DST fixes + [Kevin Rose <kbrose@github>] + + + 0.3.19 (2017-08-31) + ------------------- + + - fix #87: backward dst changes + [kiorky] + + + 0.3.18 (2017-08-31) + ------------------- + + - Add is valid method, refactor errors + [otherpirate, Mauro Murari <[email protected]>] + + 0.3.17 (2017-05-22) ------------------- - DOW occurence sharp style support. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/croniter-0.3.17/README.rst new/croniter-0.3.20/README.rst --- old/croniter-0.3.17/README.rst 2017-05-22 12:11:46.000000000 +0200 +++ new/croniter-0.3.20/README.rst 2017-11-06 22:22:31.000000000 +0100 @@ -82,6 +82,11 @@ >>> print itr.get_prev(datetime) # 2010-07-01 00:00:00 >>> print itr.get_prev(datetime) # 2010-06-01 00:00:00 +You can validate your crons using ``is_valid`` class method. (>= 0.3.18):: + + >>> croniter.is_valid('0 0 1 * *') # True + >>> croniter.is_valid('0 wrong_value 1 * *') # False + About DST ========= Be sure to init your croniter instance with a TZ aware datetime for this to work !:: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/croniter-0.3.17/docs/CHANGES.rst new/croniter-0.3.20/docs/CHANGES.rst --- old/croniter-0.3.17/docs/CHANGES.rst 2017-05-22 12:11:46.000000000 +0200 +++ new/croniter-0.3.20/docs/CHANGES.rst 2017-11-06 22:22:31.000000000 +0100 @@ -1,6 +1,27 @@ Changelog ============== +0.3.20 (2017-11-06) +------------------- + +- More DST fixes + [Kevin Rose <kbrose@github>] + + +0.3.19 (2017-08-31) +------------------- + +- fix #87: backward dst changes + [kiorky] + + +0.3.18 (2017-08-31) +------------------- + +- Add is valid method, refactor errors + [otherpirate, Mauro Murari <[email protected]>] + + 0.3.17 (2017-05-22) ------------------- - DOW occurence sharp style support. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/croniter-0.3.17/setup.py new/croniter-0.3.20/setup.py --- old/croniter-0.3.17/setup.py 2017-05-22 12:11:46.000000000 +0200 +++ new/croniter-0.3.20/setup.py 2017-11-06 22:22:31.000000000 +0100 @@ -23,7 +23,7 @@ setup( name='croniter', - version='0.3.17', + version='0.3.20', py_modules=['croniter', ], description=( 'croniter provides iteration for datetime ' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/croniter-0.3.17/src/croniter/croniter.py new/croniter-0.3.20/src/croniter/croniter.py --- old/croniter-0.3.17/src/croniter/croniter.py 2017-05-22 12:11:46.000000000 +0200 +++ new/croniter-0.3.20/src/croniter/croniter.py 2017-11-06 22:22:31.000000000 +0100 @@ -14,18 +14,23 @@ only_int_re = re.compile(r'^\d+$') any_int_re = re.compile(r'^\d+') star_or_int_re = re.compile(r'^(\d+|\*)$') +VALID_LEN_EXPRESSION = [5, 6] -class CroniterBadCronError(ValueError): - '''.''' +class CroniterError(ValueError): + pass -class CroniterBadDateError(ValueError): - '''.''' +class CroniterBadCronError(CroniterError): + pass -class CroniterNotAlphaError(ValueError): - '''.''' +class CroniterBadDateError(CroniterError): + pass + + +class CroniterNotAlphaError(CroniterError): + pass class croniter(object): @@ -78,107 +83,18 @@ start_time = self._datetime_to_timestamp(start_time) self.start_time = start_time + self.dst_start_time = start_time self.cur = start_time - self.exprs = expr_format.split() - - if len(self.exprs) != 5 and len(self.exprs) != 6: - raise CroniterBadCronError(self.bad_length) - - expanded = [] - nth_weekday_of_month = {} - - for i, expr in enumerate(self.exprs): - e_list = expr.split(',') - res = [] - - while len(e_list) > 0: - e = e_list.pop() - - if i == 4: - e, sep, nth = str(e).partition('#') - if nth and not re.match(r'[1-5]', nth): - raise CroniterBadDateError( - "[{0}] is not acceptable".format(expr_format)) - - t = re.sub(r'^\*(\/.+)$', r'%d-%d\1' % ( - self.RANGES[i][0], - self.RANGES[i][1]), - str(e)) - m = search_re.search(t) - - if not m: - t = re.sub(r'^(.+)\/(.+)$', r'\1-%d/\2' % ( - self.RANGES[i][1]), - str(e)) - m = step_search_re.search(t) - - if m: - (low, high, step) = m.group(1), m.group(2), m.group(4) or 1 - - if not any_int_re.search(low): - low = "{0}".format(self._alphaconv(i, low)) - - if not any_int_re.search(high): - high = "{0}".format(self._alphaconv(i, high)) - - if ( - not low or not high or int(low) > int(high) - or not only_int_re.search(str(step)) - ): - raise CroniterBadDateError( - "[{0}] is not acceptable".format(expr_format)) - - low, high, step = map(int, [low, high, step]) - rng = range(low, high + 1, step) - e_list += (["{0}#{1}".format(item, nth) for item in rng] - if i == 4 and nth else rng) - else: - if t.startswith('-'): - raise CroniterBadCronError( - "[{0}] is not acceptable,\ - negative numbers not allowed".format( - expr_format)) - if not star_or_int_re.search(t): - t = self._alphaconv(i, t) - - try: - t = int(t) - except: - pass - - if t in self.LOWMAP[i]: - t = self.LOWMAP[i][t] - if ( - t not in ["*", "l"] - and (int(t) < self.RANGES[i][0] or - int(t) > self.RANGES[i][1]) - ): - raise CroniterBadCronError( - "[{0}] is not acceptable, out of range".format( - expr_format)) + self.expanded, self.nth_weekday_of_month = self.expand(expr_format) - res.append(t) - - if i == 4 and nth: - if t not in nth_weekday_of_month: - nth_weekday_of_month[t] = set() - nth_weekday_of_month[t].add(int(nth)) - - res.sort() - expanded.append(['*'] if (len(res) == 1 - and res[0] == '*') - else res) - - self.expanded = expanded - self.nth_weekday_of_month = nth_weekday_of_month - - def _alphaconv(self, index, key): + @classmethod + def _alphaconv(cls, index, key, expressions): try: - return self.ALPHACONV[index][key.lower()] + return cls.ALPHACONV[index][key.lower()] except KeyError: raise CroniterNotAlphaError( - "[{0}] is not acceptable".format(" ".join(self.exprs))) + "[{0}] is not acceptable".format(" ".join(expressions))) def get_next(self, ret_type=None): return self._get_next(ret_type or self._ret_type, is_prev=False) @@ -192,14 +108,15 @@ return self._timestamp_to_datetime(self.cur) return self.cur - def _datetime_to_timestamp(self, d): + @classmethod + def _datetime_to_timestamp(cls, d): """ Converts a `datetime` object `d` into a UNIX timestamp. """ if d.tzinfo is not None: d = d.replace(tzinfo=None) - d.utcoffset() - return self._timedelta_to_seconds(d - datetime.datetime(1970, 1, 1)) + return cls._timedelta_to_seconds(d - datetime.datetime(1970, 1, 1)) def _timestamp_to_datetime(self, timestamp): """ @@ -269,29 +186,34 @@ else: result = t1 if t1 > t2 else t2 else: - result = self._calc(self.cur, expanded, nth_weekday_of_month, is_prev) + result = self._calc(self.cur, expanded, + nth_weekday_of_month, is_prev) # DST Handling for cron job spanning accross days - dtstarttime = self._timestamp_to_datetime(self.start_time) - dtresult = self._timestamp_to_datetime(result) - dtresult_utcoffset = dtresult.utcoffset() or datetime.timedelta(0) + dtstarttime = self._timestamp_to_datetime(self.dst_start_time) dtstarttime_utcoffset = ( dtstarttime.utcoffset() or datetime.timedelta(0)) - hours_before_midnight = 24 - dtstarttime.hour - lag_hours = ( - self._timedelta_to_seconds(dtresult - dtstarttime) / (60*60) - ) - if ( - lag_hours >= hours_before_midnight and - (dtresult_utcoffset or dtstarttime_utcoffset) and - (dtresult_utcoffset != dtstarttime_utcoffset) - ): + dtresult = self._timestamp_to_datetime(result) + lag = lag_hours = 0 + # do we trigger DST on next crontab (handle backward changes) + dtresult_utcoffset = dtstarttime_utcoffset + if dtresult and self.tzinfo: + dtresult_utcoffset = dtresult.utcoffset() + lag_hours = ( + self._timedelta_to_seconds(dtresult - dtstarttime) / (60*60) + ) lag = self._timedelta_to_seconds( dtresult_utcoffset - dtstarttime_utcoffset ) - dtresult = dtresult - datetime.timedelta(seconds=lag) - result = self._datetime_to_timestamp(dtresult) - + hours_before_midnight = 24 - dtstarttime.hour + if dtresult_utcoffset != dtstarttime_utcoffset: + if ((lag > 0 and lag_hours >= hours_before_midnight) + or (lag < 0 and + ((3600*lag_hours+abs(lag)) >= hours_before_midnight*3600)) + ): + dtresult = dtresult - datetime.timedelta(seconds=lag) + result = self._datetime_to_timestamp(dtresult) + self.dst_start_time = result self.cur = result if issubclass(ret_type, datetime.datetime): result = dtresult @@ -532,3 +454,107 @@ return True else: return False + + @classmethod + def expand(cls, expr_format): + expressions = expr_format.split() + + if len(expressions) not in VALID_LEN_EXPRESSION: + raise CroniterBadCronError(cls.bad_length) + + expanded = [] + nth_weekday_of_month = {} + + for i, expr in enumerate(expressions): + e_list = expr.split(',') + res = [] + + while len(e_list) > 0: + e = e_list.pop() + + if i == 4: + e, sep, nth = str(e).partition('#') + if nth and not re.match(r'[1-5]', nth): + raise CroniterBadDateError( + "[{0}] is not acceptable".format(expr_format)) + + t = re.sub(r'^\*(\/.+)$', r'%d-%d\1' % ( + cls.RANGES[i][0], + cls.RANGES[i][1]), + str(e)) + m = search_re.search(t) + + if not m: + t = re.sub(r'^(.+)\/(.+)$', r'\1-%d/\2' % ( + cls.RANGES[i][1]), + str(e)) + m = step_search_re.search(t) + + if m: + (low, high, step) = m.group(1), m.group(2), m.group(4) or 1 + + if not any_int_re.search(low): + low = "{0}".format(cls._alphaconv(i, low, expressions)) + + if not any_int_re.search(high): + high = "{0}".format(cls._alphaconv(i, high, expressions)) + + if ( + not low or not high or int(low) > int(high) + or not only_int_re.search(str(step)) + ): + raise CroniterBadDateError( + "[{0}] is not acceptable".format(expr_format)) + + low, high, step = map(int, [low, high, step]) + rng = range(low, high + 1, step) + e_list += (["{0}#{1}".format(item, nth) for item in rng] + if i == 4 and nth else rng) + else: + if t.startswith('-'): + raise CroniterBadCronError( + "[{0}] is not acceptable,\ + negative numbers not allowed".format( + expr_format)) + if not star_or_int_re.search(t): + t = cls._alphaconv(i, t, expressions) + + try: + t = int(t) + except: + pass + + if t in cls.LOWMAP[i]: + t = cls.LOWMAP[i][t] + + if ( + t not in ["*", "l"] + and (int(t) < cls.RANGES[i][0] or + int(t) > cls.RANGES[i][1]) + ): + raise CroniterBadCronError( + "[{0}] is not acceptable, out of range".format( + expr_format)) + + res.append(t) + + if i == 4 and nth: + if t not in nth_weekday_of_month: + nth_weekday_of_month[t] = set() + nth_weekday_of_month[t].add(int(nth)) + + res.sort() + expanded.append(['*'] if (len(res) == 1 + and res[0] == '*') + else res) + + return expanded, nth_weekday_of_month + + @classmethod + def is_valid(cls, expression): + try: + cls.expand(expression) + except CroniterError: + return False + else: + return True diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/croniter-0.3.17/src/croniter/tests/test_croniter.py new/croniter-0.3.20/src/croniter/tests/test_croniter.py --- old/croniter-0.3.17/src/croniter/tests/test_croniter.py 2017-05-22 12:11:46.000000000 +0200 +++ new/croniter-0.3.20/src/croniter/tests/test_croniter.py 2017-11-06 22:22:31.000000000 +0100 @@ -2,10 +2,10 @@ # -*- coding: utf-8 -*- import unittest -from datetime import datetime, timedelta +from datetime import datetime from time import sleep import pytz -from croniter import croniter, CroniterBadDateError +from croniter import croniter, CroniterBadDateError, CroniterBadCronError, CroniterNotAlphaError from croniter.tests import base @@ -733,5 +733,62 @@ val = croniter('0 * * * *', local_date).get_next(datetime) self.assertEqual(val, tz.localize(datetime(2017, 10, 29, 6))) + def test_std_dst2(self): + """ + DST tests + + This fixes https://github.com/taichino/croniter/issues/87 + + São Paulo, Brazil: 18/02/2018 00:00 -> 17/02/2018 23:00 + + """ + tz = pytz.timezone("America/Sao_Paulo") + local_dates = [ + # 17-22: 00 -> 18-00:00 + (tz.localize(datetime(2018, 2, 17, 21, 0, 0)), + '2018-02-18 00:00:00-03:00'), + # 17-23: 00 -> 18-00:00 + (tz.localize(datetime(2018, 2, 17, 22, 0, 0)), + '2018-02-18 00:00:00-03:00'), + # 17-23: 00 -> 18-00:00 + (tz.localize(datetime(2018, 2, 17, 23, 0, 0)), + '2018-02-18 00:00:00-03:00'), + # 18-00: 00 -> 19-00:00 + (tz.localize(datetime(2018, 2, 18, 0, 0, 0)), + '2018-02-19 00:00:00-03:00'), + # 17-22: 00 -> 18-00:00 + (tz.localize(datetime(2018, 2, 17, 21, 5, 0)), + '2018-02-18 00:00:00-03:00'), + # 17-23: 00 -> 18-00:00 + (tz.localize(datetime(2018, 2, 17, 22, 5, 0)), + '2018-02-18 00:00:00-03:00'), + # 17-23: 00 -> 18-00:00 + (tz.localize(datetime(2018, 2, 17, 23, 5, 0)), + '2018-02-18 00:00:00-03:00'), + # 18-00: 00 -> 19-00:00 + (tz.localize(datetime(2018, 2, 18, 0, 5, 0)), + '2018-02-19 00:00:00-03:00'), + ] + ret1 = [croniter("0 0 * * *", d[0]).get_next(datetime) + for d in local_dates] + sret1 = ['{0}'.format(d) for d in ret1] + lret1 = ['{0}'.format(d[1]) for d in local_dates] + self.assertEqual(sret1, lret1) + + def test_error_alpha_cron(self): + self.assertRaises(CroniterNotAlphaError, croniter.expand, + '* * * janu-jun *') + + def test_error_bad_cron(self): + self.assertRaises(CroniterBadCronError, croniter.expand, + '* * * *') + self.assertRaises(CroniterBadCronError, croniter.expand, + '* * * * * * *') + + def test_is_valid(self): + self.assertTrue(croniter.is_valid('0 * * * *')) + self.assertFalse(croniter.is_valid('0 * *')) + self.assertFalse(croniter.is_valid('* * * janu-jun *')) + if __name__ == '__main__': unittest.main() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/croniter-0.3.17/src/croniter.egg-info/PKG-INFO new/croniter-0.3.20/src/croniter.egg-info/PKG-INFO --- old/croniter-0.3.17/src/croniter.egg-info/PKG-INFO 2017-05-22 12:11:46.000000000 +0200 +++ new/croniter-0.3.20/src/croniter.egg-info/PKG-INFO 2017-11-06 22:22:31.000000000 +0100 @@ -1,6 +1,6 @@ Metadata-Version: 1.1 Name: croniter -Version: 0.3.17 +Version: 0.3.20 Summary: croniter provides iteration for datetime object with cron like format Home-page: http://github.com/kiorky/croniter Author: Matsumoto Taichi, kiorky @@ -90,6 +90,11 @@ >>> print itr.get_prev(datetime) # 2010-07-01 00:00:00 >>> print itr.get_prev(datetime) # 2010-06-01 00:00:00 + You can validate your crons using ``is_valid`` class method. (>= 0.3.18):: + + >>> croniter.is_valid('0 0 1 * *') # True + >>> croniter.is_valid('0 wrong_value 1 * *') # False + About DST ========= Be sure to init your croniter instance with a TZ aware datetime for this to work !:: @@ -144,6 +149,27 @@ Changelog ============== + 0.3.20 (2017-11-06) + ------------------- + + - More DST fixes + [Kevin Rose <kbrose@github>] + + + 0.3.19 (2017-08-31) + ------------------- + + - fix #87: backward dst changes + [kiorky] + + + 0.3.18 (2017-08-31) + ------------------- + + - Add is valid method, refactor errors + [otherpirate, Mauro Murari <[email protected]>] + + 0.3.17 (2017-05-22) ------------------- - DOW occurence sharp style support.
