aboutsummaryrefslogtreecommitdiff
path: root/dateutil/test/test_tz.py
diff options
context:
space:
mode:
Diffstat (limited to 'dateutil/test/test_tz.py')
-rw-r--r--dateutil/test/test_tz.py244
1 files changed, 215 insertions, 29 deletions
diff --git a/dateutil/test/test_tz.py b/dateutil/test/test_tz.py
index fd7e47d..bb0f4b7 100644
--- a/dateutil/test/test_tz.py
+++ b/dateutil/test/test_tz.py
@@ -8,12 +8,15 @@ from ._common import ComparesEqual
from datetime import datetime, timedelta
from datetime import time as dt_time
from datetime import tzinfo
-from six import BytesIO, StringIO
+from six import PY2
+from io import BytesIO, StringIO
import unittest
import sys
import base64
import copy
+import gc
+import weakref
from functools import partial
@@ -154,6 +157,8 @@ END:VTIMEZONE
EST_TUPLE = ('EST', timedelta(hours=-5), timedelta(hours=0))
EDT_TUPLE = ('EDT', timedelta(hours=-4), timedelta(hours=1))
+SUPPORTS_SUB_MINUTE_OFFSETS = sys.version_info >= (3, 6)
+
###
# Helper functions
@@ -731,6 +736,28 @@ class TzOffsetTest(unittest.TestCase):
assert tz1 is tz2
+
+@pytest.mark.smoke
+@pytest.mark.tzoffset
+def test_tzoffset_weakref():
+ UTC1 = tz.tzoffset('UTC', 0)
+ UTC_ref = weakref.ref(tz.tzoffset('UTC', 0))
+ UTC1 is UTC_ref()
+ del UTC1
+ gc.collect()
+
+ assert UTC_ref() is not None # Should be in the strong cache
+ assert UTC_ref() is tz.tzoffset('UTC', 0)
+
+ # Fill the strong cache with other items
+ for offset in range(5,15):
+ tz.tzoffset('RandomZone', offset)
+
+ gc.collect()
+ assert UTC_ref() is None
+ assert UTC_ref() is not tz.tzoffset('UTC', 0)
+
+
@pytest.mark.tzoffset
@pytest.mark.parametrize('args', [
('UTC', 0),
@@ -744,6 +771,25 @@ def test_tzoffset_singleton(args):
assert tz1 is tz2
+
+@pytest.mark.tzoffset
+@pytest.mark.skipif(not SUPPORTS_SUB_MINUTE_OFFSETS,
+ reason='Sub-minute offsets not supported')
+def test_tzoffset_sub_minute():
+ delta = timedelta(hours=12, seconds=30)
+ test_datetime = datetime(2000, 1, 1, tzinfo=tz.tzoffset(None, delta))
+ assert test_datetime.utcoffset() == delta
+
+
+@pytest.mark.tzoffset
+@pytest.mark.skipif(SUPPORTS_SUB_MINUTE_OFFSETS,
+ reason='Sub-minute offsets supported')
+def test_tzoffset_sub_minute_rounding():
+ 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)
+
+
@pytest.mark.tzlocal
class TzLocalTest(unittest.TestCase):
def testEquality(self):
@@ -1037,6 +1083,25 @@ class GettzTest(unittest.TestCase, TzFoldMixin):
assert local1 is not local2
+
+@pytest.mark.gettz
+@pytest.mark.parametrize('badzone', [
+ 'Fake.Region/Abcdefghijklmnop', # Violates several tz project name rules
+])
+def test_gettz_badzone(badzone):
+ # Make sure passing a bad TZ string to gettz returns None (GH #800)
+ tzi = tz.gettz(badzone)
+ assert tzi is None
+
+
+@pytest.mark.gettz
+def test_gettz_badzone_unicode():
+ # Make sure a unicode string can be passed to TZ (GH #802)
+ # When fixed, combine this with test_gettz_badzone
+ tzi = tz.gettz('🐼')
+ assert tzi is None
+
+
@pytest.mark.gettz
@pytest.mark.xfail(IS_WIN, reason='zoneinfo separately cached')
def test_gettz_cache_clear():
@@ -1047,6 +1112,52 @@ def test_gettz_cache_clear():
assert NYC1 is not NYC2
+@pytest.mark.gettz
+@pytest.mark.xfail(IS_WIN, reason='zoneinfo separately cached')
+def test_gettz_set_cache_size():
+ tz.gettz.cache_clear()
+ tz.gettz.set_cache_size(3)
+
+ MONACO_ref = weakref.ref(tz.gettz('Europe/Monaco'))
+ EASTER_ref = weakref.ref(tz.gettz('Pacific/Easter'))
+ CURRIE_ref = weakref.ref(tz.gettz('Australia/Currie'))
+
+ gc.collect()
+
+ assert MONACO_ref() is not None
+ assert EASTER_ref() is not None
+ assert CURRIE_ref() is not None
+
+ tz.gettz.set_cache_size(2)
+ gc.collect()
+
+ assert MONACO_ref() is None
+
+@pytest.mark.xfail(IS_WIN, reason="Windows does not use system zoneinfo")
+@pytest.mark.smoke
+@pytest.mark.gettz
+def test_gettz_weakref():
+ tz.gettz.cache_clear()
+ tz.gettz.set_cache_size(2)
+ NYC1 = tz.gettz('America/New_York')
+ NYC_ref = weakref.ref(tz.gettz('America/New_York'))
+
+ assert NYC1 is NYC_ref()
+
+ del NYC1
+ gc.collect()
+
+ assert NYC_ref() is not None # Should still be in the strong cache
+ assert tz.gettz('America/New_York') is NYC_ref()
+
+ # Populate strong cache with other timezones
+ tz.gettz('Europe/Monaco')
+ tz.gettz('Pacific/Easter')
+ tz.gettz('Australia/Currie')
+
+ gc.collect()
+ assert NYC_ref() is None # Should have been pushed out
+ assert tz.gettz('America/New_York') is not NYC_ref()
class ZoneInfoGettzTest(GettzTest, WarningTestMixin):
def gettz(self, name):
@@ -1375,6 +1486,28 @@ class TZStrTest(unittest.TestCase, TzFoldMixin):
# Ensure that these still are all the same zone
assert tz1 == tz2 == tz3
+
+@pytest.mark.smoke
+@pytest.mark.tzstr
+def test_tzstr_weakref():
+ tz_t1 = tz.tzstr('EST5EDT')
+ tz_t2_ref = weakref.ref(tz.tzstr('EST5EDT'))
+ assert tz_t1 is tz_t2_ref()
+
+ del tz_t1
+ gc.collect()
+
+ assert tz_t2_ref() is not None
+ assert tz.tzstr('EST5EDT') is tz_t2_ref()
+
+ for offset in range(5,15):
+ tz.tzstr('GMT+{}'.format(offset))
+ gc.collect()
+
+ assert tz_t2_ref() is None
+ assert tz.tzstr('EST5EDT') is not tz_t2_ref()
+
+
@pytest.mark.tzstr
@pytest.mark.parametrize('tz_str,expected', [
# From https://www.gnu.org/software/libc/manual/html_node/TZ-Variable.html
@@ -1851,11 +1984,13 @@ class TZTest(unittest.TestCase):
with self.assertRaises(ValueError):
tz.tzfile(BytesIO(b'BadFile'))
- def testRoundNonFullMinutes(self):
- # This timezone has an offset of 5992 seconds in 1900-01-01.
- 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")
+ def testFilestreamWithNameRepr(self):
+ # If fileobj is a filestream with a "name" attribute this name should
+ # be reflected in the tz object's repr
+ fileobj = BytesIO(base64.b64decode(TZFILE_EST5EDT))
+ fileobj.name = 'foo'
+ tzc = tz.tzfile(fileobj)
+ self.assertEqual(repr(tzc), 'tzfile(' + repr('foo') + ')')
def testLeapCountDecodesProperly(self):
# This timezone has leapcnt, and failed to decode until
@@ -1917,6 +2052,40 @@ class TZTest(unittest.TestCase):
self.assertEqual(str(dt), '2014-07-20 12:34:56+00:00')
+@pytest.mark.tzfile
+@pytest.mark.skipif(not SUPPORTS_SUB_MINUTE_OFFSETS,
+ reason='Sub-minute offsets not supported')
+def test_tzfile_sub_minute_offset():
+ # If user running python 3.6 or newer, exact offset is used
+ tzc = tz.tzfile(BytesIO(base64.b64decode(EUROPE_HELSINKI)))
+ offset = timedelta(hours=1, minutes=39, seconds=52)
+ assert datetime(1900, 1, 1, 0, 0, tzinfo=tzc).utcoffset() == offset
+
+
+@pytest.mark.tzfile
+@pytest.mark.skipif(SUPPORTS_SUB_MINUTE_OFFSETS,
+ reason='Sub-minute offsets supported.')
+def test_sub_minute_rounding_tzfile():
+ # 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)))
+ offset = timedelta(hours=1, minutes=40)
+ assert datetime(1900, 1, 1, 0, 0, tzinfo=tzc).utcoffset() == offset
+
+
+@pytest.mark.tzfile
+def test_samoa_transition():
+ # utcoffset() was erroneously returning +14:00 an hour early (GH #812)
+ APIA = tz.gettz('Pacific/Apia')
+ dt = datetime(2011, 12, 29, 23, 59, tzinfo=APIA)
+ assert dt.utcoffset() == timedelta(hours=-10)
+
+ # Make sure the transition actually works, too
+ dt_after = (dt.astimezone(tz.UTC) + timedelta(minutes=1)).astimezone(APIA)
+ assert dt_after == datetime(2011, 12, 31, tzinfo=APIA)
+ assert dt_after.utcoffset() == timedelta(hours=14)
+
+
@unittest.skipUnless(IS_WIN, "Requires Windows")
class TzWinTest(unittest.TestCase, TzWinFoldMixin):
def setUp(self):
@@ -2464,22 +2633,44 @@ class DatetimeExistsTest(unittest.TestCase):
self.assertFalse(tz.datetime_exists(dt, tz=AEST))
-class EnfoldTest(unittest.TestCase):
- def testEnterFoldDefault(self):
+class TestEnfold:
+ def test_enter_fold_default(self):
dt = tz.enfold(datetime(2020, 1, 19, 3, 32))
- self.assertEqual(dt.fold, 1)
+ assert dt.fold == 1
- def testEnterFold(self):
+ def test_enter_fold(self):
dt = tz.enfold(datetime(2020, 1, 19, 3, 32), fold=1)
- self.assertEqual(dt.fold, 1)
+ assert dt.fold == 1
- def testExitFold(self):
+ def test_exit_fold(self):
dt = tz.enfold(datetime(2020, 1, 19, 3, 32), fold=0)
# Before Python 3.6, dt.fold won't exist if fold is 0.
- self.assertEqual(getattr(dt, 'fold', 0), 0)
+ assert getattr(dt, 'fold', 0) == 0
+
+ def test_defold(self):
+ dt = tz.enfold(datetime(2020, 1, 19, 3, 32), fold=1)
+
+ dt2 = tz.enfold(dt, fold=0)
+
+ assert getattr(dt2, 'fold', 0) == 0
+
+ def test_fold_replace_args(self):
+ # This test can be dropped when Python < 3.6 is dropped, since it
+ # is mainly to cover the `replace` method on _DatetimeWithFold
+ dt = tz.enfold(datetime(1950, 1, 2, 12, 30, 15, 8), fold=1)
+
+ dt2 = dt.replace(1952, 2, 3, 13, 31, 16, 9)
+ assert dt2 == tz.enfold(datetime(1952, 2, 3, 13, 31, 16, 9), fold=1)
+ assert dt2.fold == 1
+
+ def test_fold_replace_exception_duplicate_args(self):
+ dt = tz.enfold(datetime(1999, 1, 3), fold=1)
+
+ with pytest.raises(TypeError):
+ dt.replace(1950, year=2000)
@pytest.mark.tz_resolve_imaginary
@@ -2561,8 +2752,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'),
@@ -2570,24 +2760,20 @@ 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(),
-])
-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()
+
+if SUPPORTS_SUB_MINUTE_OFFSETS:
+ resolve_imaginary_tests.append(
+ (tz.gettz('Africa/Monrovia'),
+ datetime(1972, 1, 7, 0, 30), datetime(1972, 1, 7, 1, 14, 30)))
-@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)
+@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