summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlex Grönholm <alex.gronholm@nextday.fi>2018-01-16 00:33:36 +0200
committerAlex Grönholm <alex.gronholm@nextday.fi>2018-01-16 00:33:36 +0200
commitcc519b72476f0bd03fd5087dc8be1b26c2cc3ed7 (patch)
tree83563334f980a43910d6f5881309df8f8a8d2541
parentc94428ef1e3cb67bc0400ae7d5ba5b8b5798e193 (diff)
downloadapscheduler-cron-dst-fix.tar.gz
Added support for UTC offsets in datetime parsingcron-dst-fix
Fixes #271.
-rw-r--r--apscheduler/util.py24
-rw-r--r--docs/versionhistory.rst3
-rw-r--r--tests/test_util.py8
3 files changed, 28 insertions, 7 deletions
diff --git a/apscheduler/util.py b/apscheduler/util.py
index d9beb18..88254ff 100644
--- a/apscheduler/util.py
+++ b/apscheduler/util.py
@@ -7,7 +7,7 @@ from calendar import timegm
from functools import partial
import re
-from pytz import timezone, utc
+from pytz import timezone, utc, FixedOffset
import six
try:
@@ -94,8 +94,9 @@ def astimezone(obj):
_DATE_REGEX = re.compile(
r'(?P<year>\d{4})-(?P<month>\d{1,2})-(?P<day>\d{1,2})'
- r'(?: (?P<hour>\d{1,2}):(?P<minute>\d{1,2}):(?P<second>\d{1,2})'
- r'(?:\.(?P<microsecond>\d{1,6}))?)?')
+ r'(?:[ T](?P<hour>\d{1,2}):(?P<minute>\d{1,2}):(?P<second>\d{1,2})'
+ r'(?:\.(?P<microsecond>\d{1,6}))?'
+ r'(?P<timezone>Z|[+-]\d\d:\d\d)?)?$')
def convert_to_datetime(input, tz, arg_name):
@@ -107,7 +108,9 @@ def convert_to_datetime(input, tz, arg_name):
If the input is a string, it is parsed as a datetime with the given timezone.
Date strings are accepted in three different forms: date only (Y-m-d), date with time
- (Y-m-d H:M:S) or with date+time with microseconds (Y-m-d H:M:S.micro).
+ (Y-m-d H:M:S) or with date+time with microseconds (Y-m-d H:M:S.micro). Additionally you can
+ override the time zone by giving a specific offset in the format specified by ISO 8601:
+ Z (UTC), +HH:MM or -HH:MM.
:param str|datetime input: the datetime or string to convert to a timezone aware datetime
:param datetime.tzinfo tz: timezone to interpret ``input`` in
@@ -125,8 +128,17 @@ def convert_to_datetime(input, tz, arg_name):
m = _DATE_REGEX.match(input)
if not m:
raise ValueError('Invalid date string')
- values = [(k, int(v or 0)) for k, v in m.groupdict().items()]
- values = dict(values)
+
+ values = m.groupdict()
+ tzname = values.pop('timezone')
+ if tzname == 'Z':
+ tz = utc
+ elif tzname:
+ hours, minutes = (int(x) for x in tzname[1:].split(':'))
+ sign = 1 if tzname[0] == '+' else -1
+ tz = FixedOffset(sign * (hours * 60 + minutes))
+
+ values = {k: int(v or 0) for k, v in values.items()}
datetime_ = datetime(**values)
else:
raise TypeError('Unsupported type for %s: %s' % (arg_name, input.__class__.__name__))
diff --git a/docs/versionhistory.rst b/docs/versionhistory.rst
index d5a22d0..8911030 100644
--- a/docs/versionhistory.rst
+++ b/docs/versionhistory.rst
@@ -12,6 +12,9 @@ APScheduler, see the :doc:`migration section <migration>`.
* Fixed ``CronTrigger`` sometimes producing fire times beyond ``end_date`` when jitter is enabled
(thanks to gilbsgilbs for the tests)
+* Fixed ISO 8601 UTC offset information being silently discarded from string formatted datetimes by
+ adding support for parsing them
+
3.5.0
-----
diff --git a/tests/test_util.py b/tests/test_util.py
index 55bf197..973b81a 100644
--- a/tests/test_util.py
+++ b/tests/test_util.py
@@ -107,9 +107,15 @@ class TestConvertToDatetime(object):
(datetime(2009, 8, 1, 5, 6, 12), datetime(2009, 8, 1, 5, 6, 12)),
('2009-8-1', datetime(2009, 8, 1)),
('2009-8-1 5:16:12', datetime(2009, 8, 1, 5, 16, 12)),
+ ('2009-8-1T5:16:12Z', datetime(2009, 8, 1, 5, 16, 12, tzinfo=pytz.utc)),
+ ('2009-8-1T5:16:12+02:30',
+ pytz.FixedOffset(150).localize(datetime(2009, 8, 1, 5, 16, 12))),
+ ('2009-8-1T5:16:12-05:30',
+ pytz.FixedOffset(-330).localize(datetime(2009, 8, 1, 5, 16, 12))),
(pytz.FixedOffset(-60).localize(datetime(2009, 8, 1)),
pytz.FixedOffset(-60).localize(datetime(2009, 8, 1)))
- ], ids=['None', 'date', 'datetime', 'date as text', 'datetime as text', 'existing tzinfo'])
+ ], ids=['None', 'date', 'datetime', 'date as text', 'datetime as text', 'utc', 'tzoffset',
+ 'negtzoffset', 'existing tzinfo'])
def test_date(self, timezone, input, expected):
returned = convert_to_datetime(input, timezone, None)
if expected is not None: