diff --git a/Lib/test/test_zoneinfo/test_zoneinfo.py b/Lib/test/test_zoneinfo/test_zoneinfo.py index a10f434eb27590..f08095dcc6d327 100644 --- a/Lib/test/test_zoneinfo/test_zoneinfo.py +++ b/Lib/test/test_zoneinfo/test_zoneinfo.py @@ -316,6 +316,37 @@ def test_unambiguous(self): self.assertEqual(dt.utcoffset(), offset.utcoffset, dt) self.assertEqual(dt.dst(), offset.dst, dt) + def test_datetime_subclass_negative_components(self): + """Regression test for gh-154892. + + Datetime subclasses may return -1 for time components. The C + accelerator should treat -1 as a valid PyLong_AsLong() result unless + an exception is set, matching the pure-Python implementation. + """ + class MinusOneDateTime(datetime): + @property + def hour(self): + return -1 + + @property + def minute(self): + return -1 + + @property + def second(self): + return -1 + + zi = self.zone_from_key("UTC") + dt = MinusOneDateTime(2024, 1, 1, tzinfo=zi) + + self.assertEqual(dt.utcoffset(), ZERO) + self.assertEqual(dt.dst(), ZERO) + self.assertEqual(dt.tzname(), "UTC") + self.assertEqual( + zi.fromutc(dt), + datetime(2024, 1, 1, tzinfo=zi), + ) + def test_folds_and_gaps(self): test_cases = [] for key in self.zones(): diff --git a/Misc/NEWS.d/next/Library/2026-07-29-21-37-39.gh-issue-154892.eQyJ3Z.rst b/Misc/NEWS.d/next/Library/2026-07-29-21-37-39.gh-issue-154892.eQyJ3Z.rst new file mode 100644 index 00000000000000..6e3bd6d10a95ae --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-07-29-21-37-39.gh-issue-154892.eQyJ3Z.rst @@ -0,0 +1,3 @@ +Fixed a bug in the C accelerator for :mod:`zoneinfo` where ``datetime`` +subclasses returning ``-1`` for ``hour``, ``minute``, or ``second`` could +incorrectly raise an error due to ``PyLong_AsLong()`` sentinel handling. diff --git a/Modules/_zoneinfo.c b/Modules/_zoneinfo.c index 2a7ac4498261e0..464e145438ae73 100644 --- a/Modules/_zoneinfo.c +++ b/Modules/_zoneinfo.c @@ -2311,7 +2311,7 @@ get_local_timestamp(PyObject *dt, int64_t *local_ts) } hour = PyLong_AsLong(num); Py_DECREF(num); - if (hour == -1) { + if (hour == -1 && PyErr_Occurred()) { return -1; } @@ -2321,7 +2321,7 @@ get_local_timestamp(PyObject *dt, int64_t *local_ts) } minute = PyLong_AsLong(num); Py_DECREF(num); - if (minute == -1) { + if (minute == -1 && PyErr_Occurred()) { return -1; } @@ -2331,7 +2331,7 @@ get_local_timestamp(PyObject *dt, int64_t *local_ts) } second = PyLong_AsLong(num); Py_DECREF(num); - if (second == -1) { + if (second == -1 && PyErr_Occurred()) { return -1; } }