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..50645908df2573 --- /dev/null +++ b/Misc/NEWS.d/next/Windows/2025-05-22-10-33-54.gh-issue-81708.xBa0wa.rst @@ -0,0 +1,3 @@ +Support negative :meth:`datetime.datetime.timestamp` values from naive +datetimes (before the UNIX epoch of 1970-01-01) 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),