From f8bbeace5522a23647ed4c54018f09db58a75216 Mon Sep 17 00:00:00 2001 From: Bhuvansh Kataria Date: Wed, 29 Jul 2026 21:36:20 +0000 Subject: [PATCH 1/2] gh-154892: Fix PyLong_AsLong() error checks in _zoneinfo --- Lib/test/test_zoneinfo/test_zoneinfo.py | 31 +++++++++++++++++++ ...-07-29-21-37-39.gh-issue-154892.eQyJ3Z.rst | 3 ++ Modules/_zoneinfo.c | 6 ++-- 3 files changed, 37 insertions(+), 3 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2026-07-29-21-37-39.gh-issue-154892.eQyJ3Z.rst diff --git a/Lib/test/test_zoneinfo/test_zoneinfo.py b/Lib/test/test_zoneinfo/test_zoneinfo.py index a10f434eb27590d..78ab9b59f511719 100644 --- a/Lib/test/test_zoneinfo/test_zoneinfo.py +++ b/Lib/test/test_zoneinfo/test_zoneinfo.py @@ -499,6 +499,37 @@ def __add__(self, other): self.assertEqual(dt_fromutc.fold, 1) self.assertEqual(dt.fold, 0) + 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), + ) + class ZoneInfoDatetimeSubclassTest(DatetimeSubclassMixin, ZoneInfoTest): pass 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 000000000000000..6e3bd6d10a95aed --- /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 2a7ac4498261e08..464e145438ae733 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; } } From 683a1d47adfc374b0ab8d7830da51c2c8ba0c95f Mon Sep 17 00:00:00 2001 From: Bhuvansh Kataria Date: Thu, 30 Jul 2026 05:51:33 +0000 Subject: [PATCH 2/2] gh-154892: Move regression test to ZoneInfoTest --- Lib/test/test_zoneinfo/test_zoneinfo.py | 62 ++++++++++++------------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/Lib/test/test_zoneinfo/test_zoneinfo.py b/Lib/test/test_zoneinfo/test_zoneinfo.py index 78ab9b59f511719..f08095dcc6d327a 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(): @@ -499,37 +530,6 @@ def __add__(self, other): self.assertEqual(dt_fromutc.fold, 1) self.assertEqual(dt.fold, 0) - 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), - ) - class ZoneInfoDatetimeSubclassTest(DatetimeSubclassMixin, ZoneInfoTest): pass