bpo-10381: Add timezone to datetime C API (#5032)

* Add timezone to datetime C API

* Add documentation for timezone C API macros

* Add dedicated tests for datetime type check macros

* Remove superfluous C API test

* Drop support for TimeZoneType in datetime C API

* Expose UTC singleton to the datetime C API

* Update datetime C-API documentation to include links

* Add reference count information for timezone constructors
This commit is contained in:
Paul Ganssle 2018-01-24 17:29:30 -05:00 committed by Alexander Belopolsky
parent ccbe5818af
commit 04af5b1ba9
7 changed files with 329 additions and 11 deletions

View file

@ -31,6 +31,8 @@ from datetime import timezone
from datetime import date, datetime
import time as _time
import _testcapi
# Needed by test_datetime
import _strptime
#
@ -5443,6 +5445,185 @@ class ZoneInfoCompleteTest(unittest.TestSuite):
class IranTest(ZoneInfoTest):
zonename = 'Asia/Tehran'
class CapiTest(unittest.TestCase):
def setUp(self):
# Since the C API is not present in the _Pure tests, skip all tests
if self.__class__.__name__.endswith('Pure'):
self.skipTest('Not relevant in pure Python')
# This *must* be called, and it must be called first, so until either
# restriction is loosened, we'll call it as part of test setup
_testcapi.test_datetime_capi()
def test_utc_capi(self):
for use_macro in (True, False):
capi_utc = _testcapi.get_timezone_utc_capi(use_macro)
with self.subTest(use_macro=use_macro):
self.assertIs(capi_utc, timezone.utc)
def test_timezones_capi(self):
est_capi, est_macro, est_macro_nn = _testcapi.make_timezones_capi()
exp_named = timezone(timedelta(hours=-5), "EST")
exp_unnamed = timezone(timedelta(hours=-5))
cases = [
('est_capi', est_capi, exp_named),
('est_macro', est_macro, exp_named),
('est_macro_nn', est_macro_nn, exp_unnamed)
]
for name, tz_act, tz_exp in cases:
with self.subTest(name=name):
self.assertEqual(tz_act, tz_exp)
dt1 = datetime(2000, 2, 4, tzinfo=tz_act)
dt2 = datetime(2000, 2, 4, tzinfo=tz_exp)
self.assertEqual(dt1, dt2)
self.assertEqual(dt1.tzname(), dt2.tzname())
dt_utc = datetime(2000, 2, 4, 5, tzinfo=timezone.utc)
self.assertEqual(dt1.astimezone(timezone.utc), dt_utc)
def test_check_date(self):
class DateSubclass(date):
pass
d = date(2011, 1, 1)
ds = DateSubclass(2011, 1, 1)
dt = datetime(2011, 1, 1)
is_date = _testcapi.datetime_check_date
# Check the ones that should be valid
self.assertTrue(is_date(d))
self.assertTrue(is_date(dt))
self.assertTrue(is_date(ds))
self.assertTrue(is_date(d, True))
# Check that the subclasses do not match exactly
self.assertFalse(is_date(dt, True))
self.assertFalse(is_date(ds, True))
# Check that various other things are not dates at all
args = [tuple(), list(), 1, '2011-01-01',
timedelta(1), timezone.utc, time(12, 00)]
for arg in args:
for exact in (True, False):
with self.subTest(arg=arg, exact=exact):
self.assertFalse(is_date(arg, exact))
def test_check_time(self):
class TimeSubclass(time):
pass
t = time(12, 30)
ts = TimeSubclass(12, 30)
is_time = _testcapi.datetime_check_time
# Check the ones that should be valid
self.assertTrue(is_time(t))
self.assertTrue(is_time(ts))
self.assertTrue(is_time(t, True))
# Check that the subclass does not match exactly
self.assertFalse(is_time(ts, True))
# Check that various other things are not times
args = [tuple(), list(), 1, '2011-01-01',
timedelta(1), timezone.utc, date(2011, 1, 1)]
for arg in args:
for exact in (True, False):
with self.subTest(arg=arg, exact=exact):
self.assertFalse(is_time(arg, exact))
def test_check_datetime(self):
class DateTimeSubclass(datetime):
pass
dt = datetime(2011, 1, 1, 12, 30)
dts = DateTimeSubclass(2011, 1, 1, 12, 30)
is_datetime = _testcapi.datetime_check_datetime
# Check the ones that should be valid
self.assertTrue(is_datetime(dt))
self.assertTrue(is_datetime(dts))
self.assertTrue(is_datetime(dt, True))
# Check that the subclass does not match exactly
self.assertFalse(is_datetime(dts, True))
# Check that various other things are not datetimes
args = [tuple(), list(), 1, '2011-01-01',
timedelta(1), timezone.utc, date(2011, 1, 1)]
for arg in args:
for exact in (True, False):
with self.subTest(arg=arg, exact=exact):
self.assertFalse(is_datetime(arg, exact))
def test_check_delta(self):
class TimeDeltaSubclass(timedelta):
pass
td = timedelta(1)
tds = TimeDeltaSubclass(1)
is_timedelta = _testcapi.datetime_check_delta
# Check the ones that should be valid
self.assertTrue(is_timedelta(td))
self.assertTrue(is_timedelta(tds))
self.assertTrue(is_timedelta(td, True))
# Check that the subclass does not match exactly
self.assertFalse(is_timedelta(tds, True))
# Check that various other things are not timedeltas
args = [tuple(), list(), 1, '2011-01-01',
timezone.utc, date(2011, 1, 1), datetime(2011, 1, 1)]
for arg in args:
for exact in (True, False):
with self.subTest(arg=arg, exact=exact):
self.assertFalse(is_timedelta(arg, exact))
def test_check_tzinfo(self):
class TZInfoSubclass(tzinfo):
pass
tzi = tzinfo()
tzis = TZInfoSubclass()
tz = timezone(timedelta(hours=-5))
is_tzinfo = _testcapi.datetime_check_tzinfo
# Check the ones that should be valid
self.assertTrue(is_tzinfo(tzi))
self.assertTrue(is_tzinfo(tz))
self.assertTrue(is_tzinfo(tzis))
self.assertTrue(is_tzinfo(tzi, True))
# Check that the subclasses do not match exactly
self.assertFalse(is_tzinfo(tz, True))
self.assertFalse(is_tzinfo(tzis, True))
# Check that various other things are not tzinfos
args = [tuple(), list(), 1, '2011-01-01',
date(2011, 1, 1), datetime(2011, 1, 1)]
for arg in args:
for exact in (True, False):
with self.subTest(arg=arg, exact=exact):
self.assertFalse(is_tzinfo(arg, exact))
def load_tests(loader, standard_tests, pattern):
standard_tests.addTest(ZoneInfoCompleteTest())
return standard_tests