diff options
author | Alex Grönholm <alex.gronholm@nextday.fi> | 2018-01-05 06:38:46 +0200 |
---|---|---|
committer | Alex Grönholm <alex.gronholm@nextday.fi> | 2018-01-05 06:38:46 +0200 |
commit | cbf300d5f35104ddd27eb6250d2f2bdfdb83415e (patch) | |
tree | 8ced5deb4336fe3de6939d798f64b044a8d72194 | |
parent | c838f68864974bacd04c8f3550d2e00b8887af4c (diff) | |
download | apscheduler-cbf300d5f35104ddd27eb6250d2f2bdfdb83415e.tar.gz |
Fixed DST behavior for CronTrigger
Fixes #120.
-rw-r--r-- | apscheduler/triggers/cron/__init__.py | 22 | ||||
-rw-r--r-- | tests/test_triggers.py | 25 |
2 files changed, 43 insertions, 4 deletions
diff --git a/apscheduler/triggers/cron/__init__.py b/apscheduler/triggers/cron/__init__.py index ce675dd..c3e40fc 100644 --- a/apscheduler/triggers/cron/__init__.py +++ b/apscheduler/triggers/cron/__init__.py @@ -1,5 +1,6 @@ from datetime import datetime, timedelta +from pytz import NonExistentTimeError, AmbiguousTimeError from tzlocal import get_localzone import six @@ -145,7 +146,7 @@ class CronTrigger(BaseTrigger): difference = datetime(**values) - dateval.replace(tzinfo=None) return self.timezone.normalize(dateval + difference), fieldnum - def _set_field_value(self, dateval, fieldnum, new_value): + def _set_field_value(self, dateval, fieldnum, new_value, is_dst=None): values = {} for i, field in enumerate(self.fields): if field.REAL: @@ -156,7 +157,7 @@ class CronTrigger(BaseTrigger): else: values[field.name] = new_value - return self.timezone.localize(datetime(**values)) + return self.timezone.localize(datetime(**values), is_dst=is_dst) def get_next_fire_time(self, previous_fire_time, now): if previous_fire_time: @@ -179,8 +180,21 @@ class CronTrigger(BaseTrigger): elif next_value > curr_value: # A valid, but higher than the starting value, was found if field.REAL: - next_date = self._set_field_value(next_date, fieldnum, next_value) - fieldnum += 1 + try: + next_date = self._set_field_value(next_date, fieldnum, next_value) + fieldnum += 1 + except NonExistentTimeError: + # Skip this field value + next_date, fieldnum = self._increment_field_value(next_date, fieldnum) + except AmbiguousTimeError: + # Try this datetime with DST set unless it's earlier than + # previous_run_time, in which case don't set DST + next_date = self._set_field_value(next_date, fieldnum, next_value, + is_dst=True) + if previous_fire_time and next_date <= previous_fire_time: + next_date = self._set_field_value(next_date, fieldnum, next_value, + is_dst=False) + fieldnum += 1 else: next_date, fieldnum = self._increment_field_value(next_date, fieldnum) else: diff --git a/tests/test_triggers.py b/tests/test_triggers.py index dffc71d..0779ab7 100644 --- a/tests/test_triggers.py +++ b/tests/test_triggers.py @@ -410,6 +410,31 @@ class TestCronTrigger(object): trigger = CronTrigger.from_crontab(expr, timezone) assert repr(trigger) == expected_repr + @pytest.mark.parametrize('args, now, expected', [ + ({'minute': '*/5'}, datetime(2016, 3, 27, 1, 59), datetime(2016, 3, 27, 3)), + ({'hour': 2, 'minute': 30}, datetime(2016, 3, 26, 2, 31), datetime(2016, 3, 28, 2, 30)) + ], ids=['jump_forward', 'skip_day']) + def test_dst_forward(self, args, now, expected, timezone): + trigger = CronTrigger(timezone=timezone, **args) + now = timezone.localize(now) + expected = timezone.localize(expected) + assert trigger.get_next_fire_time(None, now) == expected + + @pytest.mark.parametrize('args, previous, previous_dst, expected, expected_dst', [ + ({'minute': '*/5'}, datetime(2016, 10, 30, 2, 59), True, datetime(2016, 10, 30, 2), False), + ({'hour': 2, 'minute': 30}, datetime(2016, 10, 30, 2, 30), True, + datetime(2016, 10, 30, 2, 30), False), + ({'hour': 2, 'minute': 30}, datetime(2016, 10, 30, 1), True, + datetime(2016, 10, 30, 2, 30), True) + ], ids=['backward1', 'backward2', 'forward']) + def test_dst_backward(self, args, previous: datetime, previous_dst, expected, expected_dst, + timezone): + trigger = CronTrigger(timezone=timezone, **args) + now = timezone.localize(datetime(2016, 10, 30, 4)) + previous = timezone.localize(previous, is_dst=previous_dst) + expected = timezone.localize(expected, is_dst=expected_dst) + assert trigger.get_next_fire_time(previous, now) == expected + class TestDateTrigger(object): @pytest.mark.parametrize('run_date,alter_tz,previous,now,expected', [ |