]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
GH-103857: Deprecate utcnow and utcfromtimestamp (#103858)
authorPaul Ganssle <1377457+pganssle@users.noreply.github.com>
Thu, 27 Apr 2023 17:32:30 +0000 (11:32 -0600)
committerGitHub <noreply@github.com>
Thu, 27 Apr 2023 17:32:30 +0000 (11:32 -0600)
Using `datetime.datetime.utcnow()` and `datetime.datetime.utcfromtimestamp()` will now raise a `DeprecationWarning`.

We also have removed our internal uses of these functions and documented the change.

Doc/library/datetime.rst
Lib/datetime.py
Lib/email/utils.py
Lib/http/cookiejar.py
Lib/test/datetimetester.py
Lib/test/support/testresult.py
Lib/test/test_plistlib.py
Lib/test/test_sqlite3/test_types.py
Misc/NEWS.d/next/Library/2023-04-25-17-03-18.gh-issue-103857.Mr2Cak.rst [new file with mode: 0644]
Modules/_datetimemodule.c

index 7889dd7d1c3ef0b3127317ad739349a56eb0051f..bed19ad145a20c5100863896cb8e1b003d042154 100644 (file)
@@ -896,6 +896,10 @@ Other constructors, all class methods:
       in UTC. As such, the recommended way to create an object representing the
       current time in UTC is by calling ``datetime.now(timezone.utc)``.
 
+   .. deprecated:: 3.12
+
+      Use :meth:`datetime.now` with :attr:`UTC` instead.
+
 
 .. classmethod:: datetime.fromtimestamp(timestamp, tz=None)
 
@@ -964,6 +968,10 @@ Other constructors, all class methods:
       :c:func:`gmtime` function. Raise :exc:`OSError` instead of
       :exc:`ValueError` on :c:func:`gmtime` failure.
 
+   .. deprecated:: 3.12
+
+      Use :meth:`datetime.fromtimestamp` with :attr:`UTC` instead.
+
 
 .. classmethod:: datetime.fromordinal(ordinal)
 
index 09a2d2d5381c34132cae6761e288cac4d64b6cb5..b0eb1c216a689dfb56cc891d88e82b2e3349a51a 100644 (file)
@@ -1801,6 +1801,13 @@ class datetime(date):
     @classmethod
     def utcfromtimestamp(cls, t):
         """Construct a naive UTC datetime from a POSIX timestamp."""
+        import warnings
+        warnings.warn("datetime.utcfromtimestamp() is deprecated and scheduled "
+                      "for removal in a future version. Use timezone-aware "
+                      "objects to represent datetimes in UTC: "
+                      "datetime.fromtimestamp(t, datetime.UTC).",
+                      DeprecationWarning,
+                      stacklevel=2)
         return cls._fromtimestamp(t, True, None)
 
     @classmethod
@@ -1812,8 +1819,15 @@ class datetime(date):
     @classmethod
     def utcnow(cls):
         "Construct a UTC datetime from time.time()."
+        import warnings
+        warnings.warn("datetime.utcnow() is deprecated and scheduled for "
+                      "removal in a future version. Instead, Use timezone-aware "
+                      "objects to represent datetimes in UTC: "
+                      "datetime.now(datetime.UTC).",
+                      DeprecationWarning,
+                      stacklevel=2)
         t = _time.time()
-        return cls.utcfromtimestamp(t)
+        return cls._fromtimestamp(t, True, None)
 
     @classmethod
     def combine(cls, date, time, tzinfo=True):
index 4d014bacd6182e10cfe825260088becf6f1813cc..81da5394ea169521d2c525f01480b1bf43b73c18 100644 (file)
@@ -143,13 +143,13 @@ def formatdate(timeval=None, localtime=False, usegmt=False):
     # 2822 requires that day and month names be the English abbreviations.
     if timeval is None:
         timeval = time.time()
-    if localtime or usegmt:
-        dt = datetime.datetime.fromtimestamp(timeval, datetime.timezone.utc)
-    else:
-        dt = datetime.datetime.utcfromtimestamp(timeval)
+    dt = datetime.datetime.fromtimestamp(timeval, datetime.timezone.utc)
+
     if localtime:
         dt = dt.astimezone()
         usegmt = False
+    elif not usegmt:
+        dt = dt.replace(tzinfo=None)
     return format_datetime(dt, usegmt)
 
 def format_datetime(dt, usegmt=False):
index 93b10d26c84545e43e1bcbcbc5ffcf36eba86244..bd89370e16831e4d6986d728346239eca29697a1 100644 (file)
@@ -104,9 +104,9 @@ def time2isoz(t=None):
 
     """
     if t is None:
-        dt = datetime.datetime.utcnow()
+        dt = datetime.datetime.now(tz=datetime.UTC)
     else:
-        dt = datetime.datetime.utcfromtimestamp(t)
+        dt = datetime.datetime.fromtimestamp(t, tz=datetime.UTC)
     return "%04d-%02d-%02d %02d:%02d:%02dZ" % (
         dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second)
 
@@ -122,9 +122,9 @@ def time2netscape(t=None):
 
     """
     if t is None:
-        dt = datetime.datetime.utcnow()
+        dt = datetime.datetime.now(tz=datetime.UTC)
     else:
-        dt = datetime.datetime.utcfromtimestamp(t)
+        dt = datetime.datetime.fromtimestamp(t, tz=datetime.UTC)
     return "%s, %02d-%s-%04d %02d:%02d:%02d GMT" % (
         DAYS[dt.weekday()], dt.day, MONTHS[dt.month-1],
         dt.year, dt.hour, dt.minute, dt.second)
index 477f16f1841f6260eb668d43b13feef9b4285f14..c5eb6e7f1643ee26a5dcf8a354032095b66ec571 100644 (file)
@@ -2437,7 +2437,8 @@ class TestDateTime(TestDate):
 
         ts = time.time()
         expected = time.gmtime(ts)
-        got = self.theclass.utcfromtimestamp(ts)
+        with self.assertWarns(DeprecationWarning):
+            got = self.theclass.utcfromtimestamp(ts)
         self.verify_field_equality(expected, got)
 
     # Run with US-style DST rules: DST begins 2 a.m. on second Sunday in
@@ -2483,8 +2484,12 @@ class TestDateTime(TestDate):
 
     @support.run_with_tz('MSK-03')  # Something east of Greenwich
     def test_microsecond_rounding(self):
+        def utcfromtimestamp(*args, **kwargs):
+            with self.assertWarns(DeprecationWarning):
+                return self.theclass.utcfromtimestamp(*args, **kwargs)
+
         for fts in [self.theclass.fromtimestamp,
-                    self.theclass.utcfromtimestamp]:
+                    utcfromtimestamp]:
             zero = fts(0)
             self.assertEqual(zero.second, 0)
             self.assertEqual(zero.microsecond, 0)
@@ -2581,10 +2586,11 @@ class TestDateTime(TestDate):
                     self.theclass.fromtimestamp(ts)
 
     def test_utcfromtimestamp_limits(self):
-        try:
-            self.theclass.utcfromtimestamp(-2**32 - 1)
-        except (OSError, OverflowError):
-            self.skipTest("Test not valid on this platform")
+        with self.assertWarns(DeprecationWarning):
+            try:
+                self.theclass.utcfromtimestamp(-2**32 - 1)
+            except (OSError, OverflowError):
+                self.skipTest("Test not valid on this platform")
 
         min_dt = self.theclass.min.replace(tzinfo=timezone.utc)
         min_ts = min_dt.timestamp()
@@ -2597,10 +2603,11 @@ class TestDateTime(TestDate):
                 ("maximum", max_ts, max_dt.replace(tzinfo=None)),
         ]:
             with self.subTest(test_name, ts=ts, expected=expected):
-                try:
-                    actual = self.theclass.utcfromtimestamp(ts)
-                except (OSError, OverflowError) as exc:
-                    self.skipTest(str(exc))
+                with self.assertWarns(DeprecationWarning):
+                    try:
+                        actual = self.theclass.utcfromtimestamp(ts)
+                    except (OSError, OverflowError) as exc:
+                        self.skipTest(str(exc))
 
                 self.assertEqual(actual, expected)
 
@@ -2645,7 +2652,8 @@ class TestDateTime(TestDate):
 
     @unittest.skipIf(sys.platform == "win32", "Windows doesn't accept negative timestamps")
     def test_negative_float_utcfromtimestamp(self):
-        d = self.theclass.utcfromtimestamp(-1.05)
+        with self.assertWarns(DeprecationWarning):
+            d = self.theclass.utcfromtimestamp(-1.05)
         self.assertEqual(d, self.theclass(1969, 12, 31, 23, 59, 58, 950000))
 
     def test_utcnow(self):
@@ -2655,8 +2663,11 @@ class TestDateTime(TestDate):
         # a second of each other.
         tolerance = timedelta(seconds=1)
         for dummy in range(3):
-            from_now = self.theclass.utcnow()
-            from_timestamp = self.theclass.utcfromtimestamp(time.time())
+            with self.assertWarns(DeprecationWarning):
+                from_now = self.theclass.utcnow()
+
+            with self.assertWarns(DeprecationWarning):
+                from_timestamp = self.theclass.utcfromtimestamp(time.time())
             if abs(from_timestamp - from_now) <= tolerance:
                 break
             # Else try again a few times.
@@ -2956,7 +2967,11 @@ class TestDateTime(TestDate):
                                   constr_name=constr_name):
                     constructor = getattr(base_obj, constr_name)
 
-                    dt = constructor(*constr_args)
+                    if constr_name == "utcfromtimestamp":
+                        with self.assertWarns(DeprecationWarning):
+                            dt = constructor(*constr_args)
+                    else:
+                        dt = constructor(*constr_args)
 
                     # Test that it creates the right subclass
                     self.assertIsInstance(dt, DateTimeSubclass)
@@ -2986,7 +3001,11 @@ class TestDateTime(TestDate):
         for name, meth_name, kwargs in test_cases:
             with self.subTest(name):
                 constr = getattr(DateTimeSubclass, meth_name)
-                dt = constr(**kwargs)
+                if constr == "utcnow":
+                    with self.assertWarns(DeprecationWarning):
+                        dt = constr(**kwargs)
+                else:
+                    dt = constr(**kwargs)
 
                 self.assertIsInstance(dt, DateTimeSubclass)
                 self.assertEqual(dt.extra, 7)
@@ -4642,7 +4661,8 @@ class TestDateTimeTZ(TestDateTime, TZInfoBase, unittest.TestCase):
             for dummy in range(3):
                 now = datetime.now(weirdtz)
                 self.assertIs(now.tzinfo, weirdtz)
-                utcnow = datetime.utcnow().replace(tzinfo=utc)
+                with self.assertWarns(DeprecationWarning):
+                    utcnow = datetime.utcnow().replace(tzinfo=utc)
                 now2 = utcnow.astimezone(weirdtz)
                 if abs(now - now2) < timedelta(seconds=30):
                     break
@@ -4676,7 +4696,8 @@ class TestDateTimeTZ(TestDateTime, TZInfoBase, unittest.TestCase):
 
         # Try to make sure tz= actually does some conversion.
         timestamp = 1000000000
-        utcdatetime = datetime.utcfromtimestamp(timestamp)
+        with self.assertWarns(DeprecationWarning):
+            utcdatetime = datetime.utcfromtimestamp(timestamp)
         # In POSIX (epoch 1970), that's 2001-09-09 01:46:40 UTC, give or take.
         # But on some flavor of Mac, it's nowhere near that.  So we can't have
         # any idea here what time that actually is, we can only test that
@@ -4690,7 +4711,8 @@ class TestDateTimeTZ(TestDateTime, TZInfoBase, unittest.TestCase):
     def test_tzinfo_utcnow(self):
         meth = self.theclass.utcnow
         # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
-        base = meth()
+        with self.assertWarns(DeprecationWarning):
+            base = meth()
         # Try with and without naming the keyword; for whatever reason,
         # utcnow() doesn't accept a tzinfo argument.
         off42 = FixedOffset(42, "42")
@@ -4702,7 +4724,8 @@ class TestDateTimeTZ(TestDateTime, TZInfoBase, unittest.TestCase):
         meth = self.theclass.utcfromtimestamp
         ts = time.time()
         # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
-        base = meth(ts)
+        with self.assertWarns(DeprecationWarning):
+            base = meth(ts)
         # Try with and without naming the keyword; for whatever reason,
         # utcfromtimestamp() doesn't accept a tzinfo argument.
         off42 = FixedOffset(42, "42")
@@ -5309,7 +5332,7 @@ class TestTimezoneConversions(unittest.TestCase):
 
     def test_fromutc(self):
         self.assertRaises(TypeError, Eastern.fromutc)   # not enough args
-        now = datetime.utcnow().replace(tzinfo=utc_real)
+        now = datetime.now(tz=utc_real)
         self.assertRaises(ValueError, Eastern.fromutc, now) # wrong tzinfo
         now = now.replace(tzinfo=Eastern)   # insert correct tzinfo
         enow = Eastern.fromutc(now)         # doesn't blow up
@@ -5411,9 +5434,11 @@ class Oddballs(unittest.TestCase):
         self.assertEqual(datetime_sc, as_datetime)
 
     def test_extra_attributes(self):
+        with self.assertWarns(DeprecationWarning):
+            utcnow = datetime.utcnow()
         for x in [date.today(),
                   time(),
-                  datetime.utcnow(),
+                  utcnow,
                   timedelta(),
                   tzinfo(),
                   timezone(timedelta())]:
@@ -6073,6 +6098,7 @@ class ZoneInfo(tzinfo):
     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
 
     def nondst_folds(self):
index 2cd1366cd8a9e1fbfecd69aba6440f943890e742..14474be222dc4b30e2a526dc4c921113395210af 100644 (file)
@@ -18,10 +18,13 @@ class RegressionTestResult(unittest.TextTestResult):
         self.buffer = True
         if self.USE_XML:
             from xml.etree import ElementTree as ET
-            from datetime import datetime
+            from datetime import datetime, UTC
             self.__ET = ET
             self.__suite = ET.Element('testsuite')
-            self.__suite.set('start', datetime.utcnow().isoformat(' '))
+            self.__suite.set('start',
+                             datetime.now(UTC)
+                                     .replace(tzinfo=None)
+                                     .isoformat(' '))
             self.__e = None
         self.__start_time = None
 
index 6b457440be5430efe9bd417b4ee7d9db340af9e5..b08ababa341cfe548fa5dd4e6548de945b29d4e8 100644 (file)
@@ -925,7 +925,7 @@ class TestBinaryPlistlib(unittest.TestCase):
         # Issue #26709: 32-bit timestamp out of range
         for ts in -2**31-1, 2**31:
             with self.subTest(ts=ts):
-                d = (datetime.datetime.utcfromtimestamp(0) +
+                d = (datetime.datetime(1970, 1, 1, 0, 0) +
                      datetime.timedelta(seconds=ts))
                 data = plistlib.dumps(d, fmt=plistlib.FMT_BINARY)
                 self.assertEqual(plistlib.loads(data), d)
index 5e0ff353cbbd6bfe04890a7779e784b903d55d7f..fde5f888e64009fcdcdeb0bb81777dc553821b8a 100644 (file)
@@ -517,7 +517,7 @@ class DateTimeTests(unittest.TestCase):
         self.assertEqual(ts, ts2)
 
     def test_sql_timestamp(self):
-        now = datetime.datetime.utcnow()
+        now = datetime.datetime.now(tz=datetime.UTC)
         self.cur.execute("insert into test(ts) values (current_timestamp)")
         self.cur.execute("select ts from test")
         with self.assertWarnsRegex(DeprecationWarning, "converter"):
diff --git a/Misc/NEWS.d/next/Library/2023-04-25-17-03-18.gh-issue-103857.Mr2Cak.rst b/Misc/NEWS.d/next/Library/2023-04-25-17-03-18.gh-issue-103857.Mr2Cak.rst
new file mode 100644 (file)
index 0000000..3bd370d
--- /dev/null
@@ -0,0 +1,2 @@
+Deprecated :meth:`datetime.datetime.utcnow` and
+:meth:`datetime.datetime.utcfromtimestamp`. (Patch by Paul Ganssle)
index f317dc14e15bf1ee09d69a0d93cabaf97752922a..d392d384c3eee6fbc999ccf208e34c44bf3f70f6 100644 (file)
@@ -5144,6 +5144,13 @@ datetime_datetime_now_impl(PyTypeObject *type, PyObject *tz)
 static PyObject *
 datetime_utcnow(PyObject *cls, PyObject *dummy)
 {
+    PyErr_WarnEx(
+        PyExc_DeprecationWarning,
+        "datetime.utcnow() is deprecated and scheduled for removal in a future "
+        "version. Use timezone-aware objects to represent datetimes in UTC: "
+        "datetime.now(datetime.UTC).",
+        2
+    );
     return datetime_best_possible(cls, _PyTime_gmtime, Py_None);
 }
 
@@ -5180,6 +5187,13 @@ datetime_fromtimestamp(PyObject *cls, PyObject *args, PyObject *kw)
 static PyObject *
 datetime_utcfromtimestamp(PyObject *cls, PyObject *args)
 {
+    PyErr_WarnEx(
+        PyExc_DeprecationWarning,
+        "datetime.utcfromtimestamp() is deprecated and scheduled for removal "
+        "in a future version. Use timezone-aware objects to represent "
+        "datetimes in UTC: datetime.now(datetime.UTC).",
+        2
+    );
     PyObject *timestamp;
     PyObject *result = NULL;