From dcc80e01666bc4a3651fe54fdbb279e0e7429f06 Mon Sep 17 00:00:00 2001 From: John Keith Hohm Date: Wed, 21 May 2025 15:30:28 -0400 Subject: [PATCH 1/2] Support negative `datetime.datetime.timestamp` values from naive datetimes on Windows --- Lib/_pydatetime.py | 8 ++++++-- Lib/test/datetimetester.py | 4 ++++ .../2025-05-22-10-33-54.gh-issue-81708.xBa0wa.rst | 2 ++ Modules/_datetimemodule.c | 14 ++++++++++++++ 4 files changed, 26 insertions(+), 2 deletions(-) create mode 100644 Misc/NEWS.d/next/Windows/2025-05-22-10-33-54.gh-issue-81708.xBa0wa.rst diff --git a/Lib/_pydatetime.py b/Lib/_pydatetime.py index 71f619024e570d..bec8b8ee2b8d16 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): @@ -1877,7 +1877,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] @@ -2050,6 +2050,9 @@ def local(u): def timestamp(self): "Return POSIX timestamp as float" if self._tzinfo is None: + if self < _NAIVE_EPOCH and os.name == 'nt': + # Windows converters throw an OSError for negative values. + return (self - _NAIVE_EPOCH).total_seconds() s = self._mktime() return s + self.microsecond / 1e6 else: @@ -2561,6 +2564,7 @@ def _name_from_offset(delta): timezone.min = timezone._create(-timedelta(hours=23, minutes=59)) timezone.max = timezone._create(timedelta(hours=23, minutes=59)) _EPOCH = datetime(1970, 1, 1, tzinfo=timezone.utc) +_NAIVE_EPOCH = datetime(1970, 1, 1) # Some time zone algebra. For a datetime x, let # x.n = x stripped of its timezone -- its naive time. diff --git a/Lib/test/datetimetester.py b/Lib/test/datetimetester.py index df5e45f5f20e32..a5055248602e40 100644 --- a/Lib/test/datetimetester.py +++ b/Lib/test/datetimetester.py @@ -2671,6 +2671,10 @@ def test_timestamp_aware(self): self.assertEqual(t.timestamp(), 18000 + 3600 + 2*60 + 3 + 4*1e-6) + def test_naive_timestamp_before_epoch(self): + # Before #81708 this raised OSError on Windows. + self.assertLess(self.theclass(1969,1,1).timestamp(), 0) + @support.run_with_tz('MSK-03') # Something east of Greenwich def test_microsecond_rounding(self): def utcfromtimestamp(*args, **kwargs): diff --git a/Misc/NEWS.d/next/Windows/2025-05-22-10-33-54.gh-issue-81708.xBa0wa.rst b/Misc/NEWS.d/next/Windows/2025-05-22-10-33-54.gh-issue-81708.xBa0wa.rst new file mode 100644 index 00000000000000..7a4648735ce008 --- /dev/null +++ b/Misc/NEWS.d/next/Windows/2025-05-22-10-33-54.gh-issue-81708.xBa0wa.rst @@ -0,0 +1,2 @@ +Support negative :meth:`datetime.datetime.timestamp` values from naive +datetimes on Windows by subtracting a naive epoch. Patch by John Keith Hohm. diff --git a/Modules/_datetimemodule.c b/Modules/_datetimemodule.c index eb90be81c8d34c..24d979ccf368ed 100644 --- a/Modules/_datetimemodule.c +++ b/Modules/_datetimemodule.c @@ -6869,6 +6869,20 @@ datetime_timestamp(PyObject *op, PyObject *Py_UNUSED(dummy)) result = delta_total_seconds(delta, NULL); Py_DECREF(delta); } +#ifdef MS_WINDOWS + else if (GET_YEAR(self) < 1970) { + PyObject *naive_epoch = new_datetime(1970, 1, 1, 0, 0, 0, 0, Py_None, 0); + if (naive_epoch == NULL) { + return NULL; + } + PyObject *delta = datetime_subtract(op, naive_epoch); + Py_DECREF(naive_epoch); + if (delta == NULL) + return NULL; + result = delta_total_seconds(delta, NULL); + Py_DECREF(delta); + } +#endif else { long long seconds; seconds = local_to_seconds(GET_YEAR(self), From 55fe40904c554d593c62813d2f61fb1cafe1d884 Mon Sep 17 00:00:00 2001 From: John Keith Hohm Date: Thu, 22 May 2025 11:13:38 -0400 Subject: [PATCH 2/2] Elaborate on which values result in negative timestamps. --- .../next/Windows/2025-05-22-10-33-54.gh-issue-81708.xBa0wa.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Windows/2025-05-22-10-33-54.gh-issue-81708.xBa0wa.rst b/Misc/NEWS.d/next/Windows/2025-05-22-10-33-54.gh-issue-81708.xBa0wa.rst index 7a4648735ce008..50645908df2573 100644 --- a/Misc/NEWS.d/next/Windows/2025-05-22-10-33-54.gh-issue-81708.xBa0wa.rst +++ b/Misc/NEWS.d/next/Windows/2025-05-22-10-33-54.gh-issue-81708.xBa0wa.rst @@ -1,2 +1,3 @@ Support negative :meth:`datetime.datetime.timestamp` values from naive -datetimes on Windows by subtracting a naive epoch. Patch by John Keith Hohm. +datetimes (before the UNIX epoch of 1970-01-01) on Windows by +subtracting a naive epoch. Patch by John Keith Hohm.