diff options
author | Sherry Zhou <github@cssherry.com> | 2018-06-08 17:32:41 +0100 |
---|---|---|
committer | Paul Ganssle <paul@ganssle.io> | 2018-06-18 19:09:37 -0400 |
commit | cc945ff4a3362c1c0a640b12eda9575a479c53d7 (patch) | |
tree | 502ce604a5f54c382feb7c35c433e286c28049bd /dateutil | |
parent | af72d0dfb63617a2623b333069ece0300a49d1b7 (diff) | |
download | dateutil-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.py | 55 | ||||
-rw-r--r-- | dateutil/tz/tz.py | 25 |
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 |