From c3fac5f7f9d0d4bf0a1d7809cf518ba2ef116fde Mon Sep 17 00:00:00 2001 From: Paul Ganssle Date: Sat, 29 Apr 2023 17:37:47 -0400 Subject: [PATCH 1/3] Remove last use of `utcfromtimestamp` There might be easier ways to do this in the future, or we may be able to avoid it entirely by switching to `ZoneInfo` for these tests, but this will remove the last valid use of `utcfromtimetamp` that I see. --- Lib/test/datetimetester.py | 34 ++++++++++++++++++++++++++++------ 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/Lib/test/datetimetester.py b/Lib/test/datetimetester.py index c5eb6e7f1643ee..1e8e60ccb41aa9 100644 --- a/Lib/test/datetimetester.py +++ b/Lib/test/datetimetester.py @@ -2,17 +2,18 @@ See https://www.zope.dev/Members/fdrake/DateTimeWiki/TestCases """ -import io -import itertools import bisect import copy import decimal -import sys +import io +import itertools +import math import os import pickle import random import re import struct +import sys import unittest from array import array @@ -57,6 +58,28 @@ NAN = float("nan") +def _utcfromtimestamp(klass, ts): + """Simple re-implementation of datetime.utcfromtimestamp. + + utcfromtimestamp is deprecated because it returns a naïve datetime object + despite being aware that it is UTC. This sort of deliberately wrong object + happens to be useful when calculating transition times from a TZif file, + so this is a re-implementation of that. + """ + frac, ts = math.modf(ts) + + us = round(frac * 1e6) + if us >= 1000000: + ts += 1 + us -= 1000000 + elif us < 0: + ts -= 1 + us += 1000000 + + y, m, d, hh, mm, ss, *_ = _time.gmtime(ts) + + return klass(y, m, d, hh, mm, ss, us) + ############################################################################# # module tests @@ -6098,15 +6121,14 @@ def stats(cls, start_year=1): def transitions(self): for (_, prev_ti), (t, ti) in pairs(zip(self.ut, self.ti)): shift = ti[0] - prev_ti[0] - # TODO: Remove this use of utcfromtimestamp - yield datetime.utcfromtimestamp(t), shift + yield _utcfromtimestamp(datetime, t), shift def nondst_folds(self): """Find all folds with the same value of isdst on both sides of the transition.""" for (_, prev_ti), (t, ti) in pairs(zip(self.ut, self.ti)): shift = ti[0] - prev_ti[0] if shift < ZERO and ti[1] == prev_ti[1]: - yield datetime.utcfromtimestamp(t), -shift, prev_ti[2], ti[2] + yield _utcfromtimestamp(datetime, t,), -shift, prev_ti[2], ti[2] @classmethod def print_all_nondst_folds(cls, same_abbr=False, start_year=1): From 0dbc58a008c4aa3799bbd4afcb4ca92d347cef17 Mon Sep 17 00:00:00 2001 From: Paul Ganssle Date: Sat, 29 Apr 2023 17:56:41 -0400 Subject: [PATCH 2/3] Fix a few missing DeprecationWarnings in tests In one test, we simply turn off DeprecationWarning rather than asserting about it, because whether the error condition happens before or after the warning seems to differ between the Python and C versions. --- Lib/test/datetimetester.py | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/Lib/test/datetimetester.py b/Lib/test/datetimetester.py index 1e8e60ccb41aa9..3febe1949ce821 100644 --- a/Lib/test/datetimetester.py +++ b/Lib/test/datetimetester.py @@ -15,6 +15,7 @@ import struct import sys import unittest +import warnings from array import array @@ -52,7 +53,6 @@ # mixed-type comparisons. OTHERSTUFF = (10, 34.5, "abc", {}, [], ()) - # XXX Copied from test_float. INF = float("inf") NAN = float("nan") @@ -2645,9 +2645,10 @@ def test_utcfromtimestamp_limits(self): for test_name, ts in test_cases: with self.subTest(test_name, ts=ts): with self.assertRaises((ValueError, OverflowError)): - # converting a Python int to C time_t can raise a - # OverflowError, especially on 32-bit platforms. - self.theclass.utcfromtimestamp(ts) + with self.assertWarns(DeprecationWarning): + # converting a Python int to C time_t can raise a + # OverflowError, especially on 32-bit platforms. + self.theclass.utcfromtimestamp(ts) def test_insane_fromtimestamp(self): # It's possible that some platform maps time_t to double, @@ -2664,8 +2665,9 @@ def test_insane_utcfromtimestamp(self): # exempt such platforms (provided they return reasonable # results!). for insane in -1e200, 1e200: - self.assertRaises(OverflowError, self.theclass.utcfromtimestamp, - insane) + with self.assertWarns(DeprecationWarning): + self.assertRaises(OverflowError, self.theclass.utcfromtimestamp, + insane) @unittest.skipIf(sys.platform == "win32", "Windows doesn't accept negative timestamps") def test_negative_float_fromtimestamp(self): @@ -3024,7 +3026,7 @@ def __new__(cls, *args, **kwargs): for name, meth_name, kwargs in test_cases: with self.subTest(name): constr = getattr(DateTimeSubclass, meth_name) - if constr == "utcnow": + if meth_name == "utcnow": with self.assertWarns(DeprecationWarning): dt = constr(**kwargs) else: @@ -4752,8 +4754,10 @@ def test_tzinfo_utcfromtimestamp(self): # Try with and without naming the keyword; for whatever reason, # utcfromtimestamp() doesn't accept a tzinfo argument. off42 = FixedOffset(42, "42") - self.assertRaises(TypeError, meth, ts, off42) - self.assertRaises(TypeError, meth, ts, tzinfo=off42) + with warnings.catch_warnings(category=DeprecationWarning): + warnings.simplefilter("ignore", category=DeprecationWarning) + self.assertRaises(TypeError, meth, ts, off42) + self.assertRaises(TypeError, meth, ts, tzinfo=off42) def test_tzinfo_timetuple(self): # TestDateTime tested most of this. datetime adds a twist to the From de0a954ac8a12fa4e8124b8f921b1620b777a0b7 Mon Sep 17 00:00:00 2001 From: Paul Ganssle Date: Mon, 1 May 2023 10:56:45 -0400 Subject: [PATCH 3/3] Remove _utcfromtimestamp function --- Lib/test/datetimetester.py | 27 +++------------------------ 1 file changed, 3 insertions(+), 24 deletions(-) diff --git a/Lib/test/datetimetester.py b/Lib/test/datetimetester.py index 3febe1949ce821..a8b42f78b9a37a 100644 --- a/Lib/test/datetimetester.py +++ b/Lib/test/datetimetester.py @@ -7,7 +7,6 @@ import decimal import io import itertools -import math import os import pickle import random @@ -49,6 +48,8 @@ for proto in range(pickle.HIGHEST_PROTOCOL + 1)] assert len(pickle_choices) == pickle.HIGHEST_PROTOCOL + 1 +EPOCH_NAIVE = datetime(1970, 1, 1, 0, 0) # For calculating transitions + # An arbitrary collection of objects of non-datetime types, for testing # mixed-type comparisons. OTHERSTUFF = (10, 34.5, "abc", {}, [], ()) @@ -58,28 +59,6 @@ NAN = float("nan") -def _utcfromtimestamp(klass, ts): - """Simple re-implementation of datetime.utcfromtimestamp. - - utcfromtimestamp is deprecated because it returns a naïve datetime object - despite being aware that it is UTC. This sort of deliberately wrong object - happens to be useful when calculating transition times from a TZif file, - so this is a re-implementation of that. - """ - frac, ts = math.modf(ts) - - us = round(frac * 1e6) - if us >= 1000000: - ts += 1 - us -= 1000000 - elif us < 0: - ts -= 1 - us += 1000000 - - y, m, d, hh, mm, ss, *_ = _time.gmtime(ts) - - return klass(y, m, d, hh, mm, ss, us) - ############################################################################# # module tests @@ -6125,7 +6104,7 @@ def stats(cls, start_year=1): def transitions(self): for (_, prev_ti), (t, ti) in pairs(zip(self.ut, self.ti)): shift = ti[0] - prev_ti[0] - yield _utcfromtimestamp(datetime, t), shift + yield (EPOCH_NAIVE + timedelta(seconds=t)), shift def nondst_folds(self): """Find all folds with the same value of isdst on both sides of the transition."""