Commit 2e728652 by pybind11_abseil authors Committed by Copybara-Service

Use Python 3.9's zoneinfo.ZoneInfo instead of dateutil.tz.gettz

This change replaces the third party dateutil.tz.gettz with the builtin
zoneinfo.ZoneInfo to construct a timezone object of a given IANA time zone
when converting an absl::Time to a Python datetime.datetime object.

PiperOrigin-RevId: 544627455
parent afa0489d
...@@ -5,16 +5,39 @@ ...@@ -5,16 +5,39 @@
"""Tests for absl pybind11 casters.""" """Tests for absl pybind11 casters."""
import array import array
import contextlib
import datetime import datetime
import os
import threading
import time
from typing import Iterator
from absl.testing import absltest from absl.testing import absltest
from absl.testing import parameterized from absl.testing import parameterized
from dateutil import tz
import numpy as np import numpy as np
from pybind11_abseil.tests import absl_example from pybind11_abseil.tests import absl_example
_TZ_LOCK = threading.Lock()
@contextlib.contextmanager
def override_local_timezone(tzname: str) -> Iterator[None]: # pylint: disable=invalid-name
with _TZ_LOCK:
orig_tz = os.environ.get('TZ', None)
try:
os.environ['TZ'] = tzname
time.tzset()
yield
finally:
if orig_tz is None:
del os.environ['TZ']
else:
os.environ['TZ'] = orig_tz
time.tzset()
def dt_time(h=0, m=0, s=0, micros=0, tzoff=0): def dt_time(h=0, m=0, s=0, micros=0, tzoff=0):
return datetime.time(h, m, s, micros).replace( return datetime.time(h, m, s, micros).replace(
tzinfo=datetime.timezone(datetime.timedelta(seconds=tzoff)) tzinfo=datetime.timezone(datetime.timedelta(seconds=tzoff))
...@@ -26,10 +49,7 @@ class AbslTimeTest(parameterized.TestCase): ...@@ -26,10 +49,7 @@ class AbslTimeTest(parameterized.TestCase):
POSITIVE_SECS = 3 * SECONDS_IN_DAY + 2.5 POSITIVE_SECS = 3 * SECONDS_IN_DAY + 2.5
NEGATIVE_SECS = -3 * SECONDS_IN_DAY + 2.5 NEGATIVE_SECS = -3 * SECONDS_IN_DAY + 2.5
TEST_DATETIME = datetime.datetime(2000, 1, 2, 3, 4, 5, int(5e5)) TEST_DATETIME = datetime.datetime(2000, 1, 2, 3, 4, 5, int(5e5))
# Linter error relevant for pytz only. TEST_DATETIME_UTC = TEST_DATETIME.replace(tzinfo=datetime.timezone.utc)
# pylint: disable=g-tzinfo-replace
TEST_DATETIME_UTC = TEST_DATETIME.replace(tzinfo=tz.tzutc())
# pylint: enable=g-tzinfo-replace
TEST_DATE = datetime.date(2000, 1, 2) TEST_DATE = datetime.date(2000, 1, 2)
def test_return_positive_duration(self): def test_return_positive_duration(self):
...@@ -74,9 +94,6 @@ class AbslTimeTest(parameterized.TestCase): ...@@ -74,9 +94,6 @@ class AbslTimeTest(parameterized.TestCase):
def test_return_datetime(self): def test_return_datetime(self):
secs = self.TEST_DATETIME.timestamp() secs = self.TEST_DATETIME.timestamp()
local_tz = tz.gettz()
# pylint: disable=g-tzinfo-datetime
# Warning about tzinfo applies to pytz, but we are using dateutil.tz
expected_datetime = datetime.datetime( expected_datetime = datetime.datetime(
year=self.TEST_DATETIME.year, year=self.TEST_DATETIME.year,
month=self.TEST_DATETIME.month, month=self.TEST_DATETIME.month,
...@@ -85,8 +102,7 @@ class AbslTimeTest(parameterized.TestCase): ...@@ -85,8 +102,7 @@ class AbslTimeTest(parameterized.TestCase):
minute=self.TEST_DATETIME.minute, minute=self.TEST_DATETIME.minute,
second=self.TEST_DATETIME.second, second=self.TEST_DATETIME.second,
microsecond=self.TEST_DATETIME.microsecond, microsecond=self.TEST_DATETIME.microsecond,
tzinfo=local_tz) ).astimezone() # returns timezone aware datetime in local time
# pylint: enable=g-tzinfo-datetime
# datetime handling code will set the local timezone on C++ times for want # datetime handling code will set the local timezone on C++ times for want
# of a better alternative # of a better alternative
self.assertEqual(expected_datetime, absl_example.make_datetime(secs)) self.assertEqual(expected_datetime, absl_example.make_datetime(secs))
...@@ -101,22 +117,18 @@ class AbslTimeTest(parameterized.TestCase): ...@@ -101,22 +117,18 @@ class AbslTimeTest(parameterized.TestCase):
self.assertTrue(absl_example.check_datetime(self.TEST_DATETIME, secs)) self.assertTrue(absl_example.check_datetime(self.TEST_DATETIME, secs))
def test_pass_datetime_with_timezone(self): def test_pass_datetime_with_timezone(self):
pacific_tz = tz.gettz('America/Los_Angeles') with override_local_timezone('America/Los_Angeles'):
# pylint: disable=g-tzinfo-datetime dt_with_tz = datetime.datetime(
# Warning about tzinfo applies to pytz, but we are using dateutil.tz year=2020, month=2, day=1, hour=20
dt_with_tz = datetime.datetime( ).astimezone()
year=2020, month=2, day=1, hour=20, tzinfo=pacific_tz) secs = dt_with_tz.timestamp()
# pylint: enable=g-tzinfo-datetime self.assertTrue(absl_example.check_datetime(dt_with_tz, secs))
secs = dt_with_tz.timestamp()
self.assertTrue(absl_example.check_datetime(dt_with_tz, secs))
def test_pass_datetime_dst_with_timezone(self): def test_pass_datetime_dst_with_timezone(self):
pacific_tz = tz.gettz('America/Los_Angeles') with override_local_timezone('America/Los_Angeles'):
# pylint: disable=g-tzinfo-datetime dst_end = datetime.datetime(2020, 11, 1, 2, 0, 0).astimezone()
dst_end = datetime.datetime(2020, 11, 1, 2, 0, 0, tzinfo=pacific_tz) secs = dst_end.timestamp()
# pylint: enable=g-tzinfo-datetime self.assertTrue(absl_example.check_datetime(dst_end, secs))
secs = dst_end.timestamp()
self.assertTrue(absl_example.check_datetime(dst_end, secs))
def test_pass_datetime_dst(self): def test_pass_datetime_dst(self):
dst_end = datetime.datetime(2020, 11, 1, 2, 0, 0) dst_end = datetime.datetime(2020, 11, 1, 2, 0, 0)
...@@ -125,15 +137,17 @@ class AbslTimeTest(parameterized.TestCase): ...@@ -125,15 +137,17 @@ class AbslTimeTest(parameterized.TestCase):
@parameterized.named_parameters(('before', -1), ('flip', 0), ('after', 1)) @parameterized.named_parameters(('before', -1), ('flip', 0), ('after', 1))
def test_dst_datetime_from_timestamp(self, offs): def test_dst_datetime_from_timestamp(self, offs):
secs_flip = 1604224799 # 2020-11-01T02:00:00-08:00 with override_local_timezone('America/Los_Angeles'):
secs = secs_flip + offs secs_flip = 1604224799 # 2020-11-01T01:59:59-08:00
time_utc = datetime.datetime.fromtimestamp(secs, tz.tzutc()) secs = secs_flip + offs
time_local_aware = time_utc.astimezone(tz.gettz()) time_utc = datetime.datetime.fromtimestamp(secs, tz=datetime.timezone.utc)
time_local_naive = time_local_aware.replace(tzinfo=None) time_local_aware = time_utc.astimezone()
for time in (time_utc, time_local_aware, time_local_naive): for date_time in (time_utc, time_local_aware):
self.assertTrue(absl_example.check_datetime(time, secs)) self.assertTrue(absl_example.check_datetime(date_time, secs))
self.assertEqual(int(absl_example.roundtrip_time(time).timestamp()), secs) self.assertEqual(
self.assertEqual(absl_example.roundtrip_time(time_utc), time_utc) int(absl_example.roundtrip_time(date_time).timestamp()), secs
)
self.assertEqual(absl_example.roundtrip_time(time_utc), time_utc)
def test_pass_datetime_pre_unix_epoch(self): def test_pass_datetime_pre_unix_epoch(self):
dt = datetime.datetime(1969, 7, 16, 10, 56, 7, microsecond=140) dt = datetime.datetime(1969, 7, 16, 10, 56, 7, microsecond=140)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment