summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlex Grönholm <alex.gronholm@nextday.fi>2018-01-05 06:38:46 +0200
committerAlex Grönholm <alex.gronholm@nextday.fi>2018-01-05 06:38:46 +0200
commitcbf300d5f35104ddd27eb6250d2f2bdfdb83415e (patch)
tree8ced5deb4336fe3de6939d798f64b044a8d72194
parentc838f68864974bacd04c8f3550d2e00b8887af4c (diff)
downloadapscheduler-cbf300d5f35104ddd27eb6250d2f2bdfdb83415e.tar.gz
Fixed DST behavior for CronTrigger
Fixes #120.
-rw-r--r--apscheduler/triggers/cron/__init__.py22
-rw-r--r--tests/test_triggers.py25
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', [