3
0
Fork 0

feat: add "complete" timezone support

at least for now, this is all we need i think..  if nothing
configured, will fallback to default system local timezone.  or can
configure the default, or alternate(s) as needed.

some day when we drop support for python 3.8, can also remove the
python-dateutil dependency..
This commit is contained in:
Lance Edgar 2025-12-16 21:28:40 -06:00
parent dac91406c7
commit 0ffc72f766
6 changed files with 396 additions and 87 deletions

View file

@ -165,49 +165,122 @@ class TestLoadObject(TestCase):
self.assertIs(result, TestCase)
class TestGetTimezoneByName(TestCase):
def test_modern(self):
try:
import zoneinfo
except ImportError:
self.assertLess(sys.version_info, (3, 9))
pytest.skip("this test is not relevant before python 3.9")
tz = mod.get_timezone_by_name("America/Chicago")
self.assertIsInstance(tz, zoneinfo.ZoneInfo)
self.assertIsInstance(tz, datetime.tzinfo)
self.assertEqual(tz.key, "America/Chicago")
def test_legacy(self):
import dateutil.tz
orig_import = __import__
def mock_import(name, *args, **kwargs):
if name == "zoneinfo":
raise ImportError
return orig_import(name, *args, **kwargs)
with patch("builtins.__import__", side_effect=mock_import):
tz = mod.get_timezone_by_name("America/Chicago")
self.assertIsInstance(tz, dateutil.tz.tzfile)
self.assertIsInstance(tz, datetime.tzinfo)
dt = datetime.datetime.now(tz)
self.assertIn(dt.tzname(), ["CDT", "CST"])
class TestLocaltime(TestCase):
def test_naive_utc(self):
# nb. must override local_zone for test consistency
tz = datetime.timezone(-datetime.timedelta(hours=5))
dt = datetime.datetime(2025, 12, 16, 0, 16) # utc
result = mod.localtime(dt, local_zone=tz)
self.assertIsInstance(result, datetime.datetime)
self.assertIs(result.tzinfo, tz)
self.assertEqual(result, datetime.datetime(2025, 12, 15, 19, 16, tzinfo=tz))
def test_naive_local(self):
# nb. must override local_zone for test consistency
tz = datetime.timezone(-datetime.timedelta(hours=5))
dt = datetime.datetime(2025, 12, 15, 19, 16) # local
# can test precisely when overriding local_zone
result = mod.localtime(dt, local_zone=tz, from_utc=False)
self.assertIsInstance(result, datetime.datetime)
self.assertIs(result.tzinfo, tz)
self.assertEqual(result, datetime.datetime(2025, 12, 15, 19, 16, tzinfo=tz))
# must test loosely for fallback to system local timezone
result = mod.localtime(dt, from_utc=False)
self.assertIsInstance(result, datetime.datetime)
self.assertIsInstance(result.tzinfo, datetime.tzinfo)
self.assertEqual(result.year, 2025)
self.assertEqual(result.month, 12)
def test_aware_utc(self):
# nb. must override local_zone for test consistency
tz = datetime.timezone(-datetime.timedelta(hours=5))
dt = datetime.datetime(2025, 12, 16, 0, 16, tzinfo=datetime.timezone.utc)
result = mod.localtime(dt, local_zone=tz)
self.assertIsInstance(result, datetime.datetime)
self.assertIs(result.tzinfo, tz)
self.assertEqual(result, datetime.datetime(2025, 12, 15, 19, 16, tzinfo=tz))
def test_aware_local(self):
# nb. must override local_zone for test consistency
tz = datetime.timezone(-datetime.timedelta(hours=5))
other = datetime.timezone(-datetime.timedelta(hours=7))
dt = datetime.datetime(2025, 12, 15, 17, 16, tzinfo=other)
# can test precisely when overriding local_zone
result = mod.localtime(dt, local_zone=tz)
self.assertIsInstance(result, datetime.datetime)
self.assertIs(result.tzinfo, tz)
self.assertEqual(result, datetime.datetime(2025, 12, 15, 19, 16, tzinfo=tz))
# must test loosely for fallback to system local timezone
result = mod.localtime(dt)
self.assertIsInstance(result, datetime.datetime)
self.assertIsInstance(result.tzinfo, datetime.tzinfo)
self.assertEqual(result.year, 2025)
self.assertEqual(result.month, 12)
def test_current_time(self):
tz = datetime.timezone(-datetime.timedelta(hours=5))
# has tzinfo by default
dt = mod.localtime()
self.assertIsInstance(dt, datetime.datetime)
self.assertIsNotNone(dt.tzinfo)
now = datetime.datetime.now()
self.assertAlmostEqual(int(dt.timestamp()), int(now.timestamp()))
# overriding local_zone
result = mod.localtime(local_zone=tz)
self.assertIsInstance(result, datetime.datetime)
self.assertIs(result.tzinfo, tz)
# fallback to system local timezone
result = mod.localtime()
self.assertIsInstance(result, datetime.datetime)
self.assertIsInstance(result.tzinfo, datetime.tzinfo)
self.assertIsNot(result.tzinfo, tz)
def test_want_tzinfo(self):
# wants tzinfo implicitly
result = mod.localtime()
self.assertIsInstance(result.tzinfo, datetime.tzinfo)
# wants tzinfo explicitly
result = mod.localtime(want_tzinfo=True)
self.assertIsInstance(result.tzinfo, datetime.tzinfo)
# no tzinfo
dt = mod.localtime(tzinfo=False)
self.assertIsInstance(dt, datetime.datetime)
self.assertIsNone(dt.tzinfo)
now = datetime.datetime.now()
self.assertAlmostEqual(int(dt.timestamp()), int(now.timestamp()))
def test_convert_with_tzinfo(self):
sample = datetime.datetime(2024, 9, 15, 13, 30, tzinfo=datetime.timezone.utc)
# has tzinfo by default
dt = mod.localtime(sample)
self.assertIsInstance(dt, datetime.datetime)
self.assertIsNotNone(dt.tzinfo)
# no tzinfo
dt = mod.localtime(sample, tzinfo=False)
self.assertIsInstance(dt, datetime.datetime)
self.assertIsNone(dt.tzinfo)
def test_convert_without_tzinfo(self):
sample = datetime.datetime(2024, 9, 15, 13, 30)
# has tzinfo by default
dt = mod.localtime(sample)
self.assertIsInstance(dt, datetime.datetime)
self.assertIsNotNone(dt.tzinfo)
# no tzinfo
dt = mod.localtime(sample, tzinfo=False)
self.assertIsInstance(dt, datetime.datetime)
self.assertIsNone(dt.tzinfo)
result = mod.localtime(want_tzinfo=False)
self.assertIsNone(result.tzinfo)
class TestMakeUTC(TestCase):