aboutsummaryrefslogtreecommitdiff
path: root/dateutil
diff options
context:
space:
mode:
authorSherry Zhou <github@cssherry.com>2018-06-08 17:32:41 +0100
committerPaul Ganssle <paul@ganssle.io>2018-06-18 19:09:37 -0400
commitcc945ff4a3362c1c0a640b12eda9575a479c53d7 (patch)
tree502ce604a5f54c382feb7c35c433e286c28049bd /dateutil
parentaf72d0dfb63617a2623b333069ece0300a49d1b7 (diff)
downloaddateutil-cc945ff4a3362c1c0a640b12eda9575a479c53d7.tar.gz
Allow partial minutes for python 3.6 or newer
- Add "Africa/Monrovia" to test_resolve_imaginary test for python 3.6 or newer - Test that partial minutes is supported in python 3.6 or newer for tz.tzfile and datetime.utcoffset - Enable partial minutes in both tz.tzoffset and tz.tzfile - For now, don't warn user when rounding to nearest minute for python pre-3.6
Diffstat (limited to 'dateutil')
-rw-r--r--dateutil/test/test_tz.py55
-rw-r--r--dateutil/tz/tz.py25
2 files changed, 55 insertions, 25 deletions
diff --git a/dateutil/test/test_tz.py b/dateutil/test/test_tz.py
index 9ae59c7..48b6ae8 100644
--- a/dateutil/test/test_tz.py
+++ b/dateutil/test/test_tz.py
@@ -156,6 +156,8 @@ END:VTIMEZONE
EST_TUPLE = ('EST', timedelta(hours=-5), timedelta(hours=0))
EDT_TUPLE = ('EDT', timedelta(hours=-4), timedelta(hours=1))
+SUPPORTS_PARTIAL_MINUTES = sys.version_info >= (3, 6)
+
###
# Helper functions
@@ -1908,11 +1910,31 @@ class TZTest(unittest.TestCase):
tzc = tz.tzfile(fileobj)
self.assertEqual(repr(tzc), 'tzfile(' + repr('foo') + ')')
- def testRoundNonFullMinutes(self):
+ @pytest.mark.skipif(not SUPPORTS_PARTIAL_MINUTES, reason='Partial minute offset supported')
+ def testPartialMinuteSupportTzfile(self):
+ # If user running python 3.6 or newer, exact offset is used
+ tzc = tz.tzfile(BytesIO(base64.b64decode(EUROPE_HELSINKI)))
+ assert datetime(1900, 1, 1, 0, 0, tzinfo=tzc).utcoffset() == timedelta(hours=1, minutes=39, seconds=52)
+
+ @pytest.mark.skipif(not SUPPORTS_PARTIAL_MINUTES, reason='Partial minute offset supported')
+ def testPartialMinuteSupportTimedelta(self):
+ delta = timedelta(hours=12, seconds=30)
+ test_date = datetime(2000, 1, 1, tzinfo=tz.tzoffset(None, delta))
+ assert test_date.utcoffset() == delta
+
+ @pytest.mark.skipif(SUPPORTS_PARTIAL_MINUTES, reason='Partial minute offset not supported')
+ def testRoundNonFullMinutesTzfile(self):
# This timezone has an offset of 5992 seconds in 1900-01-01.
+ # For python version pre-3.6, this will be rounded
tzc = tz.tzfile(BytesIO(base64.b64decode(EUROPE_HELSINKI)))
- self.assertEqual(str(datetime(1900, 1, 1, 0, 0, tzinfo=tzc)),
- "1900-01-01 00:00:00+01:40")
+ assert datetime(1900, 1, 1, 0, 0, tzinfo=tzc).utcoffset() == timedelta(hours=1, minutes=40)
+
+ @pytest.mark.skipif(SUPPORTS_PARTIAL_MINUTES, reason='Partial minute offset not supported')
+ def testRoundNonFullMinutesTimedelta(self):
+ delta = timedelta(hours=12, seconds=30)
+ test_date = datetime(2000, 1, 1, tzinfo=tz.tzoffset(None, delta))
+ assert test_date.utcoffset() == timedelta(hours=12, minutes=1)
+
def testLeapCountDecodesProperly(self):
# This timezone has leapcnt, and failed to decode until
@@ -2617,9 +2639,7 @@ def __get_kiritimati_resolve_imaginary_test():
return (tzi, ) + dates
-
-@pytest.mark.tz_resolve_imaginary
-@pytest.mark.parametrize('tzi, dt, dt_exp', [
+resolve_imaginary_tests = [
(tz.gettz('Europe/London'),
datetime(2018, 3, 25, 1, 30), datetime(2018, 3, 25, 2, 30)),
(tz.gettz('America/New_York'),
@@ -2627,24 +2647,19 @@ def __get_kiritimati_resolve_imaginary_test():
(tz.gettz('Australia/Sydney'),
datetime(2014, 10, 5, 2, 0), datetime(2014, 10, 5, 3, 0)),
__get_kiritimati_resolve_imaginary_test(),
-])
+]
+
+if SUPPORTS_PARTIAL_MINUTES:
+ resolve_imaginary_tests.append(
+ (tz.gettz('Africa/Monrovia'),
+ datetime(1972, 1, 7, 0, 30), datetime(1972, 1, 7, 1, 14, 30)))
+
+@pytest.mark.tz_resolve_imaginary
+@pytest.mark.parametrize('tzi, dt, dt_exp', resolve_imaginary_tests)
def test_resolve_imaginary(tzi, dt, dt_exp):
dt = dt.replace(tzinfo=tzi)
dt_exp = dt_exp.replace(tzinfo=tzi)
- dt_r = tz.resolve_imaginary(dt)
- assert dt_r == dt_exp
- assert dt_r.tzname() == dt_exp.tzname()
- assert dt_r.utcoffset() == dt_exp.utcoffset()
-
-
-@pytest.mark.xfail
-@pytest.mark.tz_resolve_imaginary
-def test_resolve_imaginary_monrovia():
- # See GH #582 - When that is resolved, move this into test_resolve_imaginary
- tzi = tz.gettz('Africa/Monrovia')
- dt = datetime(1972, 1, 7, hour=0, minute=30, second=0, tzinfo=tzi)
- dt_exp = datetime(1972, 1, 7, hour=1, minute=14, second=30, tzinfo=tzi)
dt_r = tz.resolve_imaginary(dt)
assert dt_r == dt_exp
diff --git a/dateutil/tz/tz.py b/dateutil/tz/tz.py
index f10c1aa..a9ed663 100644
--- a/dateutil/tz/tz.py
+++ b/dateutil/tz/tz.py
@@ -29,6 +29,9 @@ try:
except ImportError:
tzwin = tzwinlocal = None
+# For warning about rounding tzinfo
+from warnings import warn
+
ZERO = datetime.timedelta(0)
EPOCH = datetime.datetime.utcfromtimestamp(0)
EPOCHORDINAL = EPOCH.toordinal()
@@ -138,7 +141,8 @@ class tzoffset(datetime.tzinfo):
offset = offset.total_seconds()
except (TypeError, AttributeError):
pass
- self._offset = datetime.timedelta(seconds=offset)
+
+ self._offset = datetime.timedelta(seconds=_get_supported_offset(offset))
def utcoffset(self, dt):
return self._offset
@@ -601,10 +605,7 @@ class tzfile(_tzinfo):
out.ttinfo_list = []
for i in range(typecnt):
gmtoff, isdst, abbrind = ttinfo[i]
- # Round to full-minutes if that's not the case. Python's
- # datetime doesn't accept sub-minute timezones. Check
- # http://python.org/sf/1447945 for some information.
- gmtoff = 60 * ((gmtoff + 30) // 60)
+ gmtoff = _get_supported_offset(gmtoff)
tti = _ttinfo()
tti.offset = gmtoff
tti.dstoffset = datetime.timedelta(0)
@@ -1769,6 +1770,20 @@ def _datetime_to_timestamp(dt):
return (dt.replace(tzinfo=None) - EPOCH).total_seconds()
+if sys.version_info >= (3, 6):
+ def _get_supported_offset(second_offset):
+ return second_offset
+else:
+ def _get_supported_offset(second_offset):
+ # For python pre-3.6, round to full-minutes if that's not the case.
+ # Python's datetime doesn't accept sub-minute timezones. Check
+ # http://python.org/sf/1447945 or https://bugs.python.org/issue5288
+ # for some information.
+ old_offset = second_offset
+ calculated_offset = 60 * ((second_offset + 30) // 60)
+ return calculated_offset
+
+
class _ContextWrapper(object):
"""
Class for wrapping contexts so that they are passed through in a