From 6704876063585e2baeba7552209e9102ffa3e07d Mon Sep 17 00:00:00 2001 From: John Keith Hohm Date: Wed, 21 May 2025 12:32:08 -0400 Subject: [PATCH 01/13] Support negative values in `fromtimestamp` on Windows using 0 + `timedelta`. --- Lib/_pydatetime.py | 10 ++++++ Lib/test/datetimetester.py | 33 ++++++++----------- ...5-05-21-12-29-59.gh-issue-80620.WKel4J.rst | 4 +++ Modules/_datetimemodule.c | 28 ++++++++++++++++ 4 files changed, 55 insertions(+), 20 deletions(-) create mode 100644 Misc/NEWS.d/next/Windows/2025-05-21-12-29-59.gh-issue-80620.WKel4J.rst diff --git a/Lib/_pydatetime.py b/Lib/_pydatetime.py index 71f619024e570d..799ac9692a65a3 100644 --- a/Lib/_pydatetime.py +++ b/Lib/_pydatetime.py @@ -1037,6 +1037,11 @@ def fromtimestamp(cls, t): "Construct a date from a POSIX timestamp (like time.time())." if t is None: raise TypeError("'NoneType' object cannot be interpreted as an integer") + if t < 0 and sys.platform.startswith("win"): + # Windows converters throw an OSError for negative values. + y, m, d, hh, mm, ss, weekday, jday, dst = _time.localtime(0) + result = cls(y, m, d) + return result + timedelta(seconds=t) y, m, d, hh, mm, ss, weekday, jday, dst = _time.localtime(t) return cls(y, m, d) @@ -1864,6 +1869,11 @@ def _fromtimestamp(cls, t, utc, tz): us += 1000000 converter = _time.gmtime if utc else _time.localtime + if t < 0 and sys.platform.startswith("win"): + # Windows converters throw an OSError for negative values. + y, m, d, hh, mm, ss, weekday, jday, dst = converter(0) + result = cls(y, m, d, hh, mm, ss, us, tz) + return result + timedelta(seconds=t, microseconds=us) y, m, d, hh, mm, ss, weekday, jday, dst = converter(t) ss = min(ss, 59) # clamp out leap seconds if the platform has them result = cls(y, m, d, hh, mm, ss, us, tz) diff --git a/Lib/test/datetimetester.py b/Lib/test/datetimetester.py index df5e45f5f20e32..9699040e6acc16 100644 --- a/Lib/test/datetimetester.py +++ b/Lib/test/datetimetester.py @@ -2683,24 +2683,19 @@ def utcfromtimestamp(*args, **kwargs): self.assertEqual(zero.second, 0) self.assertEqual(zero.microsecond, 0) one = fts(1e-6) - try: - minus_one = fts(-1e-6) - except OSError: - # localtime(-1) and gmtime(-1) is not supported on Windows - pass - else: - self.assertEqual(minus_one.second, 59) - self.assertEqual(minus_one.microsecond, 999999) - - t = fts(-1e-8) - self.assertEqual(t, zero) - t = fts(-9e-7) - self.assertEqual(t, minus_one) - t = fts(-1e-7) - self.assertEqual(t, zero) - t = fts(-1/2**7) - self.assertEqual(t.second, 59) - self.assertEqual(t.microsecond, 992188) + minus_one = fts(-1e-6) + self.assertEqual(minus_one.second, 59) + self.assertEqual(minus_one.microsecond, 999999) + + t = fts(-1e-8) + self.assertEqual(t, zero) + t = fts(-9e-7) + self.assertEqual(t, minus_one) + t = fts(-1e-7) + self.assertEqual(t, zero) + t = fts(-1/2**7) + self.assertEqual(t.second, 59) + self.assertEqual(t.microsecond, 992188) t = fts(1e-7) self.assertEqual(t, zero) @@ -2835,13 +2830,11 @@ def test_insane_utcfromtimestamp(self): self.assertRaises(OverflowError, self.theclass.utcfromtimestamp, insane) - @unittest.skipIf(sys.platform == "win32", "Windows doesn't accept negative timestamps") def test_negative_float_fromtimestamp(self): # The result is tz-dependent; at least test that this doesn't # fail (like it did before bug 1646728 was fixed). self.theclass.fromtimestamp(-1.05) - @unittest.skipIf(sys.platform == "win32", "Windows doesn't accept negative timestamps") def test_negative_float_utcfromtimestamp(self): with self.assertWarns(DeprecationWarning): d = self.theclass.utcfromtimestamp(-1.05) diff --git a/Misc/NEWS.d/next/Windows/2025-05-21-12-29-59.gh-issue-80620.WKel4J.rst b/Misc/NEWS.d/next/Windows/2025-05-21-12-29-59.gh-issue-80620.WKel4J.rst new file mode 100644 index 00000000000000..e3fb7f9c075c0f --- /dev/null +++ b/Misc/NEWS.d/next/Windows/2025-05-21-12-29-59.gh-issue-80620.WKel4J.rst @@ -0,0 +1,4 @@ +Support negative values in :meth:`datetime.date.fromtimestamp` and +:meth:`datetime.datetime.fromtimestamp` on Windows, by substituting 0 and +using :class:`datetime.timedelta` to go back in time. Patch by John Keith +Hohm. diff --git a/Modules/_datetimemodule.c b/Modules/_datetimemodule.c index eb90be81c8d34c..8ba652f1227d80 100644 --- a/Modules/_datetimemodule.c +++ b/Modules/_datetimemodule.c @@ -3250,6 +3250,20 @@ date_fromtimestamp(PyObject *cls, PyObject *obj) if (_PyTime_ObjectToTime_t(obj, &t, _PyTime_ROUND_FLOOR) == -1) return NULL; +#ifdef MS_WINDOWS + if (t < 0) { + if (_PyTime_localtime(0, &tm) != 0) + return NULL; + + PyObject *date = new_date_subclass_ex(tm.tm_year + 1900, + tm.tm_mon + 1, + tm.tm_mday, + cls); + PyObject *delta = new_delta(0, t, 0, 1); + return add_datetime_timedelta(date, delta, 1); + } +#endif + if (_PyTime_localtime(t, &tm) != 0) return NULL; @@ -5529,6 +5543,20 @@ datetime_from_timestamp(PyObject *cls, TM_FUNC f, PyObject *timestamp, time_t timet; long us; +#ifdef MS_WINDOWS + if (PyFloat_Check(timestamp)) { + if (PyFloat_AsDouble(timestamp) < 0) { + if (_PyTime_ObjectToTimeval(timestamp, + &timet, &us, _PyTime_ROUND_HALF_EVEN) == -1) + return NULL; + + PyObject *dt = datetime_from_timet_and_us(cls, f, 0, 0, tzinfo); + PyObject *delta = new_delta(0, timet, us, 1); + return add_datetime_timedelta(date, delta, 1); + } + } +#endif + if (_PyTime_ObjectToTimeval(timestamp, &timet, &us, _PyTime_ROUND_HALF_EVEN) == -1) return NULL; From a3e5749fab90ea1359ceaf50367250556390b81e Mon Sep 17 00:00:00 2001 From: John Keith Hohm Date: Wed, 21 May 2025 12:55:28 -0400 Subject: [PATCH 02/13] Fixes from actually building on Windows. --- Modules/_datetimemodule.c | 34 +++++++++++++++++++--------------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/Modules/_datetimemodule.c b/Modules/_datetimemodule.c index 8ba652f1227d80..e8f4fe230e0c6e 100644 --- a/Modules/_datetimemodule.c +++ b/Modules/_datetimemodule.c @@ -3241,6 +3241,13 @@ date_new(PyTypeObject *type, PyObject *args, PyObject *kw) return self; } +static PyObject *add_datetime_timedelta(PyDateTime_DateTime *date, + PyDateTime_Delta *delta, + int factor); +static PyObject * +add_date_timedelta(PyDateTime_Date *date, PyDateTime_Delta *delta, int negate); + + static PyObject * date_fromtimestamp(PyObject *cls, PyObject *obj) { @@ -3259,8 +3266,9 @@ date_fromtimestamp(PyObject *cls, PyObject *obj) tm.tm_mon + 1, tm.tm_mday, cls); - PyObject *delta = new_delta(0, t, 0, 1); - return add_datetime_timedelta(date, delta, 1); + int normalize = 1, negate = 0; + PyObject *delta = new_delta(0, (int)t, 0, normalize); + return add_date_timedelta(PyDate_CAST(date), PyDelta_CAST(delta), negate); } #endif @@ -4084,9 +4092,6 @@ tzinfo_dst(PyObject *Py_UNUSED(self), PyObject *Py_UNUSED(dt)) } -static PyObject *add_datetime_timedelta(PyDateTime_DateTime *date, - PyDateTime_Delta *delta, - int factor); static PyObject *datetime_utcoffset(PyObject *self, PyObject *); static PyObject *datetime_dst(PyObject *self, PyObject *); @@ -5544,16 +5549,15 @@ datetime_from_timestamp(PyObject *cls, TM_FUNC f, PyObject *timestamp, long us; #ifdef MS_WINDOWS - if (PyFloat_Check(timestamp)) { - if (PyFloat_AsDouble(timestamp) < 0) { - if (_PyTime_ObjectToTimeval(timestamp, - &timet, &us, _PyTime_ROUND_HALF_EVEN) == -1) - return NULL; - - PyObject *dt = datetime_from_timet_and_us(cls, f, 0, 0, tzinfo); - PyObject *delta = new_delta(0, timet, us, 1); - return add_datetime_timedelta(date, delta, 1); - } + if (PyFloat_Check(timestamp) && PyFloat_AsDouble(timestamp) < 0) { + if (_PyTime_ObjectToTimeval(timestamp, + &timet, &us, _PyTime_ROUND_HALF_EVEN) == -1) + return NULL; + + int normalize = 1, factor = 1; + PyObject *dt = datetime_from_timet_and_us(cls, f, 0, 0, tzinfo); + PyObject *delta = new_delta(0, (int)timet, us, normalize); + return add_datetime_timedelta(PyDateTime_CAST(dt), PyDelta_CAST(delta), factor); } #endif From 7453c3cfe859d562294ba46ac796fb4a599b0da2 Mon Sep 17 00:00:00 2001 From: John Keith Hohm Date: Wed, 21 May 2025 14:44:04 -0400 Subject: [PATCH 03/13] Support negative integer timestamps too; skip tests that would generate negative timestamps. --- Lib/test/datetimetester.py | 2 ++ Modules/_datetimemodule.c | 14 +++++--------- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/Lib/test/datetimetester.py b/Lib/test/datetimetester.py index 9699040e6acc16..172e1a6c5bbd1f 100644 --- a/Lib/test/datetimetester.py +++ b/Lib/test/datetimetester.py @@ -2730,6 +2730,7 @@ def test_timestamp_limits(self): # If that assumption changes, this value can change as well self.assertEqual(max_ts, 253402300799.0) + @unittest.skipIf(sys.platform == "win32", "Windows can't generate negative timestamps") def test_fromtimestamp_limits(self): try: self.theclass.fromtimestamp(-2**32 - 1) @@ -2769,6 +2770,7 @@ def test_fromtimestamp_limits(self): # OverflowError, especially on 32-bit platforms. self.theclass.fromtimestamp(ts) + @unittest.skipIf(sys.platform == "win32", "Windows can't generate negative timestamps") def test_utcfromtimestamp_limits(self): with self.assertWarns(DeprecationWarning): try: diff --git a/Modules/_datetimemodule.c b/Modules/_datetimemodule.c index e8f4fe230e0c6e..f530eed6b86be6 100644 --- a/Modules/_datetimemodule.c +++ b/Modules/_datetimemodule.c @@ -5548,12 +5548,12 @@ datetime_from_timestamp(PyObject *cls, TM_FUNC f, PyObject *timestamp, time_t timet; long us; -#ifdef MS_WINDOWS - if (PyFloat_Check(timestamp) && PyFloat_AsDouble(timestamp) < 0) { - if (_PyTime_ObjectToTimeval(timestamp, - &timet, &us, _PyTime_ROUND_HALF_EVEN) == -1) - return NULL; + if (_PyTime_ObjectToTimeval(timestamp, + &timet, &us, _PyTime_ROUND_HALF_EVEN) == -1) + return NULL; +#ifdef MS_WINDOWS + if (timet < 0) { int normalize = 1, factor = 1; PyObject *dt = datetime_from_timet_and_us(cls, f, 0, 0, tzinfo); PyObject *delta = new_delta(0, (int)timet, us, normalize); @@ -5561,10 +5561,6 @@ datetime_from_timestamp(PyObject *cls, TM_FUNC f, PyObject *timestamp, } #endif - if (_PyTime_ObjectToTimeval(timestamp, - &timet, &us, _PyTime_ROUND_HALF_EVEN) == -1) - return NULL; - return datetime_from_timet_and_us(cls, f, timet, (int)us, tzinfo); } From 15c5520d31dbf165bb715804346f12c92958176f Mon Sep 17 00:00:00 2001 From: John Keith Hohm Date: Wed, 21 May 2025 14:55:28 -0400 Subject: [PATCH 04/13] Avoid double-adding microseconds in _pydatetime datetime.fromtimestamp. --- Lib/_pydatetime.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/_pydatetime.py b/Lib/_pydatetime.py index 799ac9692a65a3..4e448e54c2bc66 100644 --- a/Lib/_pydatetime.py +++ b/Lib/_pydatetime.py @@ -1872,7 +1872,7 @@ def _fromtimestamp(cls, t, utc, tz): if t < 0 and sys.platform.startswith("win"): # Windows converters throw an OSError for negative values. y, m, d, hh, mm, ss, weekday, jday, dst = converter(0) - result = cls(y, m, d, hh, mm, ss, us, tz) + result = cls(y, m, d, hh, mm, ss, 0, tz) return result + timedelta(seconds=t, microseconds=us) y, m, d, hh, mm, ss, weekday, jday, dst = converter(t) ss = min(ss, 59) # clamp out leap seconds if the platform has them From 329db665b9130a7c6de592b10b794505ce48dcee Mon Sep 17 00:00:00 2001 From: John Keith Hohm Date: Wed, 21 May 2025 14:59:21 -0400 Subject: [PATCH 05/13] Untabify _datetimemodule.c --- Modules/_datetimemodule.c | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Modules/_datetimemodule.c b/Modules/_datetimemodule.c index f530eed6b86be6..bef09b5557dd2b 100644 --- a/Modules/_datetimemodule.c +++ b/Modules/_datetimemodule.c @@ -3259,16 +3259,16 @@ date_fromtimestamp(PyObject *cls, PyObject *obj) #ifdef MS_WINDOWS if (t < 0) { - if (_PyTime_localtime(0, &tm) != 0) - return NULL; - - PyObject *date = new_date_subclass_ex(tm.tm_year + 1900, - tm.tm_mon + 1, - tm.tm_mday, - cls); - int normalize = 1, negate = 0; - PyObject *delta = new_delta(0, (int)t, 0, normalize); - return add_date_timedelta(PyDate_CAST(date), PyDelta_CAST(delta), negate); + if (_PyTime_localtime(0, &tm) != 0) + return NULL; + + PyObject *date = new_date_subclass_ex(tm.tm_year + 1900, + tm.tm_mon + 1, + tm.tm_mday, + cls); + int normalize = 1, negate = 0; + PyObject *delta = new_delta(0, (int)t, 0, normalize); + return add_date_timedelta(PyDate_CAST(date), PyDelta_CAST(delta), negate); } #endif From 34c999f879b2c2c492fe97e8df125883b0ce5bde Mon Sep 17 00:00:00 2001 From: John Keith Hohm Date: Wed, 21 May 2025 15:03:00 -0400 Subject: [PATCH 06/13] Free intermediate objects in _datetimemodule. --- Modules/_datetimemodule.c | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/Modules/_datetimemodule.c b/Modules/_datetimemodule.c index bef09b5557dd2b..9622fd2e162262 100644 --- a/Modules/_datetimemodule.c +++ b/Modules/_datetimemodule.c @@ -3268,7 +3268,10 @@ date_fromtimestamp(PyObject *cls, PyObject *obj) cls); int normalize = 1, negate = 0; PyObject *delta = new_delta(0, (int)t, 0, normalize); - return add_date_timedelta(PyDate_CAST(date), PyDelta_CAST(delta), negate); + PyObject *result = add_date_timedelta(PyDate_CAST(date), PyDelta_CAST(delta), negate); + Py_XDECREF(delta); + Py_XDECREF(date); + return result; } #endif @@ -5554,10 +5557,13 @@ datetime_from_timestamp(PyObject *cls, TM_FUNC f, PyObject *timestamp, #ifdef MS_WINDOWS if (timet < 0) { - int normalize = 1, factor = 1; - PyObject *dt = datetime_from_timet_and_us(cls, f, 0, 0, tzinfo); - PyObject *delta = new_delta(0, (int)timet, us, normalize); - return add_datetime_timedelta(PyDateTime_CAST(dt), PyDelta_CAST(delta), factor); + int normalize = 1, factor = 1; + PyObject *dt = datetime_from_timet_and_us(cls, f, 0, 0, tzinfo); + PyObject *delta = new_delta(0, (int)timet, us, normalize); + PyObject *result = add_datetime_timedelta(PyDateTime_CAST(dt), PyDelta_CAST(delta), factor); + Py_XDECREF(delta); + Py_XDECREF(dt); + return result; } #endif From 0dd521fc559fc4c61673da0093100041c8cbc5cd Mon Sep 17 00:00:00 2001 From: John Keith Hohm Date: Wed, 21 May 2025 15:07:39 -0400 Subject: [PATCH 07/13] Reorder initialization to be consistent between added code. --- Modules/_datetimemodule.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/_datetimemodule.c b/Modules/_datetimemodule.c index 9622fd2e162262..6d961b723efdee 100644 --- a/Modules/_datetimemodule.c +++ b/Modules/_datetimemodule.c @@ -3262,11 +3262,11 @@ date_fromtimestamp(PyObject *cls, PyObject *obj) if (_PyTime_localtime(0, &tm) != 0) return NULL; + int normalize = 1, negate = 0; PyObject *date = new_date_subclass_ex(tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, cls); - int normalize = 1, negate = 0; PyObject *delta = new_delta(0, (int)t, 0, normalize); PyObject *result = add_date_timedelta(PyDate_CAST(date), PyDelta_CAST(delta), negate); Py_XDECREF(delta); From 44bcfd9fcd5edf1c9db8434fa35d27fc2ccc745c Mon Sep 17 00:00:00 2001 From: John Keith Hohm Date: Thu, 22 May 2025 10:02:40 -0400 Subject: [PATCH 08/13] Handle all errors; switch to more common os.name == 'nt' check --- Lib/_pydatetime.py | 6 +++--- Modules/_datetimemodule.c | 20 ++++++++++++++++++-- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/Lib/_pydatetime.py b/Lib/_pydatetime.py index 4e448e54c2bc66..65fe33316e96c3 100644 --- a/Lib/_pydatetime.py +++ b/Lib/_pydatetime.py @@ -1037,7 +1037,7 @@ def fromtimestamp(cls, t): "Construct a date from a POSIX timestamp (like time.time())." if t is None: raise TypeError("'NoneType' object cannot be interpreted as an integer") - if t < 0 and sys.platform.startswith("win"): + if t < 0 and os.name == 'nt': # Windows converters throw an OSError for negative values. y, m, d, hh, mm, ss, weekday, jday, dst = _time.localtime(0) result = cls(y, m, d) @@ -1869,7 +1869,7 @@ def _fromtimestamp(cls, t, utc, tz): us += 1000000 converter = _time.gmtime if utc else _time.localtime - if t < 0 and sys.platform.startswith("win"): + if t < 0 and os.name == 'nt': # Windows converters throw an OSError for negative values. y, m, d, hh, mm, ss, weekday, jday, dst = converter(0) result = cls(y, m, d, hh, mm, ss, 0, tz) @@ -1887,7 +1887,7 @@ def _fromtimestamp(cls, t, utc, tz): # thus we can't perform fold detection for values of time less # than the max time fold. See comments in _datetimemodule's # version of this method for more details. - if t < max_fold_seconds and sys.platform.startswith("win"): + if t < max_fold_seconds and os.name == 'nt': return result y, m, d, hh, mm, ss = converter(t - max_fold_seconds)[:6] diff --git a/Modules/_datetimemodule.c b/Modules/_datetimemodule.c index 6d961b723efdee..6ee94c57b7add7 100644 --- a/Modules/_datetimemodule.c +++ b/Modules/_datetimemodule.c @@ -3267,9 +3267,17 @@ date_fromtimestamp(PyObject *cls, PyObject *obj) tm.tm_mon + 1, tm.tm_mday, cls); + if (date == NULL) { + return NULL; + } + PyObject *result = NULL; PyObject *delta = new_delta(0, (int)t, 0, normalize); - PyObject *result = add_date_timedelta(PyDate_CAST(date), PyDelta_CAST(delta), negate); + if (delta == NULL) { + goto error; + } + result = add_date_timedelta(PyDate_CAST(date), PyDelta_CAST(delta), negate); Py_XDECREF(delta); + error: Py_XDECREF(date); return result; } @@ -5559,9 +5567,17 @@ datetime_from_timestamp(PyObject *cls, TM_FUNC f, PyObject *timestamp, if (timet < 0) { int normalize = 1, factor = 1; PyObject *dt = datetime_from_timet_and_us(cls, f, 0, 0, tzinfo); + if (dt == NULL) { + return NULL; + } + PyObject *result = NULL; PyObject *delta = new_delta(0, (int)timet, us, normalize); - PyObject *result = add_datetime_timedelta(PyDateTime_CAST(dt), PyDelta_CAST(delta), factor); + if (delta == NULL) { + goto error; + } + result = add_datetime_timedelta(PyDateTime_CAST(dt), PyDelta_CAST(delta), factor); Py_XDECREF(delta); + error: Py_XDECREF(dt); return result; } From e675d635a1d68c6ae6fa0e3fff1130f156181afc Mon Sep 17 00:00:00 2001 From: John Keith Hohm Date: Thu, 22 May 2025 10:05:09 -0400 Subject: [PATCH 09/13] Explain in news that negative timestamps are dates before the epoch. --- .../2025-05-21-12-29-59.gh-issue-80620.WKel4J.rst | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Misc/NEWS.d/next/Windows/2025-05-21-12-29-59.gh-issue-80620.WKel4J.rst b/Misc/NEWS.d/next/Windows/2025-05-21-12-29-59.gh-issue-80620.WKel4J.rst index e3fb7f9c075c0f..15da2968f8fc5e 100644 --- a/Misc/NEWS.d/next/Windows/2025-05-21-12-29-59.gh-issue-80620.WKel4J.rst +++ b/Misc/NEWS.d/next/Windows/2025-05-21-12-29-59.gh-issue-80620.WKel4J.rst @@ -1,4 +1,5 @@ -Support negative values in :meth:`datetime.date.fromtimestamp` and -:meth:`datetime.datetime.fromtimestamp` on Windows, by substituting 0 and -using :class:`datetime.timedelta` to go back in time. Patch by John Keith -Hohm. +Support negative values (dates before the UNIX epoch of 1970-01-01) in +:meth:`datetime.date.fromtimestamp` and +:meth:`datetime.datetime.fromtimestamp` on Windows, by substituting 0 +and using :class:`datetime.timedelta` to go back in time. Patch by +John Keith Hohm. From 02082f720d731beab0c3ff6d7789ec5341affbd3 Mon Sep 17 00:00:00 2001 From: John Keith Hohm Date: Thu, 22 May 2025 10:13:33 -0400 Subject: [PATCH 10/13] Fix OS check to import os. --- Lib/_pydatetime.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/_pydatetime.py b/Lib/_pydatetime.py index 65fe33316e96c3..b5f19f096d5bd7 100644 --- a/Lib/_pydatetime.py +++ b/Lib/_pydatetime.py @@ -8,7 +8,7 @@ import time as _time import math as _math -import sys +import os from operator import index as _index def _cmp(x, y): From cb2f911f553db5267233ca113a2bbb517e40475a Mon Sep 17 00:00:00 2001 From: John Keith Hohm Date: Thu, 22 May 2025 11:03:28 -0400 Subject: [PATCH 11/13] Untabify; copy error handling code instead of goto. --- Modules/_datetimemodule.c | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/Modules/_datetimemodule.c b/Modules/_datetimemodule.c index 6ee94c57b7add7..29468e9837709d 100644 --- a/Modules/_datetimemodule.c +++ b/Modules/_datetimemodule.c @@ -3267,17 +3267,17 @@ date_fromtimestamp(PyObject *cls, PyObject *obj) tm.tm_mon + 1, tm.tm_mday, cls); - if (date == NULL) { - return NULL; - } - PyObject *result = NULL; + if (date == NULL) { + return NULL; + } + PyObject *result = NULL; PyObject *delta = new_delta(0, (int)t, 0, normalize); - if (delta == NULL) { - goto error; - } - result = add_date_timedelta(PyDate_CAST(date), PyDelta_CAST(delta), negate); + if (delta == NULL) { + Py_XDECREF(date); + return NULL; + } + result = add_date_timedelta(PyDate_CAST(date), PyDelta_CAST(delta), negate); Py_XDECREF(delta); - error: Py_XDECREF(date); return result; } @@ -5567,17 +5567,17 @@ datetime_from_timestamp(PyObject *cls, TM_FUNC f, PyObject *timestamp, if (timet < 0) { int normalize = 1, factor = 1; PyObject *dt = datetime_from_timet_and_us(cls, f, 0, 0, tzinfo); - if (dt == NULL) { - return NULL; - } - PyObject *result = NULL; + if (dt == NULL) { + return NULL; + } + PyObject *result = NULL; PyObject *delta = new_delta(0, (int)timet, us, normalize); - if (delta == NULL) { - goto error; - } + if (delta == NULL) { + Py_XDECREF(dt); + return NULL; + } result = add_datetime_timedelta(PyDateTime_CAST(dt), PyDelta_CAST(delta), factor); Py_XDECREF(delta); - error: Py_XDECREF(dt); return result; } From 057dd797a33f57be26f4861ef8caf38674fe776a Mon Sep 17 00:00:00 2001 From: John Keith Hohm Date: Thu, 22 May 2025 11:05:24 -0400 Subject: [PATCH 12/13] No need to XDECREF non-NULL objects. --- Modules/_datetimemodule.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Modules/_datetimemodule.c b/Modules/_datetimemodule.c index 29468e9837709d..8a6748edae5dc4 100644 --- a/Modules/_datetimemodule.c +++ b/Modules/_datetimemodule.c @@ -3273,12 +3273,12 @@ date_fromtimestamp(PyObject *cls, PyObject *obj) PyObject *result = NULL; PyObject *delta = new_delta(0, (int)t, 0, normalize); if (delta == NULL) { - Py_XDECREF(date); + Py_DECREF(date); return NULL; } result = add_date_timedelta(PyDate_CAST(date), PyDelta_CAST(delta), negate); - Py_XDECREF(delta); - Py_XDECREF(date); + Py_DECREF(delta); + Py_DECREF(date); return result; } #endif @@ -5573,12 +5573,12 @@ datetime_from_timestamp(PyObject *cls, TM_FUNC f, PyObject *timestamp, PyObject *result = NULL; PyObject *delta = new_delta(0, (int)timet, us, normalize); if (delta == NULL) { - Py_XDECREF(dt); + Py_DECREF(dt); return NULL; } result = add_datetime_timedelta(PyDateTime_CAST(dt), PyDelta_CAST(delta), factor); - Py_XDECREF(delta); - Py_XDECREF(dt); + Py_DECREF(delta); + Py_DECREF(dt); return result; } #endif From 5d9db9d7ce22e5340eca95b7461fd4c5f9719e47 Mon Sep 17 00:00:00 2001 From: John Keith Hohm Date: Thu, 22 May 2025 13:54:03 -0400 Subject: [PATCH 13/13] Reuse `fromtimestamp` for 0 values, and `timedelta()` instead of casting time_t to int. --- Lib/_pydatetime.py | 13 +++++-------- Modules/_datetimemodule.c | 13 +++++-------- 2 files changed, 10 insertions(+), 16 deletions(-) diff --git a/Lib/_pydatetime.py b/Lib/_pydatetime.py index b5f19f096d5bd7..0eb50bc5ec77a5 100644 --- a/Lib/_pydatetime.py +++ b/Lib/_pydatetime.py @@ -1039,9 +1039,7 @@ def fromtimestamp(cls, t): raise TypeError("'NoneType' object cannot be interpreted as an integer") if t < 0 and os.name == 'nt': # Windows converters throw an OSError for negative values. - y, m, d, hh, mm, ss, weekday, jday, dst = _time.localtime(0) - result = cls(y, m, d) - return result + timedelta(seconds=t) + return cls.fromtimestamp(0) + timedelta(seconds=t) y, m, d, hh, mm, ss, weekday, jday, dst = _time.localtime(t) return cls(y, m, d) @@ -1859,6 +1857,10 @@ def _fromtimestamp(cls, t, utc, tz): A timezone info object may be passed in as well. """ + if t < 0 and os.name == 'nt': + # Windows converters throw an OSError for negative values. + return cls._fromtimestamp(0, utc, tz) + timedelta(seconds=t) + frac, t = _math.modf(t) us = round(frac * 1e6) if us >= 1000000: @@ -1869,11 +1871,6 @@ def _fromtimestamp(cls, t, utc, tz): us += 1000000 converter = _time.gmtime if utc else _time.localtime - if t < 0 and os.name == 'nt': - # Windows converters throw an OSError for negative values. - y, m, d, hh, mm, ss, weekday, jday, dst = converter(0) - result = cls(y, m, d, hh, mm, ss, 0, tz) - return result + timedelta(seconds=t, microseconds=us) y, m, d, hh, mm, ss, weekday, jday, dst = converter(t) ss = min(ss, 59) # clamp out leap seconds if the platform has them result = cls(y, m, d, hh, mm, ss, us, tz) diff --git a/Modules/_datetimemodule.c b/Modules/_datetimemodule.c index 8a6748edae5dc4..0dd1c6ace2e3da 100644 --- a/Modules/_datetimemodule.c +++ b/Modules/_datetimemodule.c @@ -3262,16 +3262,13 @@ date_fromtimestamp(PyObject *cls, PyObject *obj) if (_PyTime_localtime(0, &tm) != 0) return NULL; - int normalize = 1, negate = 0; - PyObject *date = new_date_subclass_ex(tm.tm_year + 1900, - tm.tm_mon + 1, - tm.tm_mday, - cls); + int negate = 0; + PyObject *date = date_fromtimestamp(cls, _PyLong_GetZero()); if (date == NULL) { return NULL; } PyObject *result = NULL; - PyObject *delta = new_delta(0, (int)t, 0, normalize); + PyObject *delta = PyObject_CallFunction((PyObject*)&DELTA_TYPE(NO_STATE), "iO", 0, obj); if (delta == NULL) { Py_DECREF(date); return NULL; @@ -5565,13 +5562,13 @@ datetime_from_timestamp(PyObject *cls, TM_FUNC f, PyObject *timestamp, #ifdef MS_WINDOWS if (timet < 0) { - int normalize = 1, factor = 1; + int factor = 1; PyObject *dt = datetime_from_timet_and_us(cls, f, 0, 0, tzinfo); if (dt == NULL) { return NULL; } PyObject *result = NULL; - PyObject *delta = new_delta(0, (int)timet, us, normalize); + PyObject *delta = PyObject_CallFunction((PyObject*)&DELTA_TYPE(NO_STATE), "iO", 0, timestamp); if (delta == NULL) { Py_DECREF(dt); return NULL;