https://github.com/python/cpython/commit/9f8f8d95bd846321ba934cd81dce33e12413f611
commit: 9f8f8d95bd846321ba934cd81dce33e12413f611
branch: main
author: rajorin <[email protected]>
committer: gpshead <[email protected]>
date: 2026-10-22T00:43:53Z
summary:

gh-158501: Reject stray text before a UTC offset in fromisoformat (GH-159224)

The C parser treated the timezone designator as leftover input, then ignored 
that leftover whenever an offset was present. Strings such as '12:30x+05:00' 
were accepted even though the pure-Python implementation rejects them.

---------

Co-authored-by: Gregory P. Smith <[email protected]>

files:
A Misc/NEWS.d/next/Library/2026-10-12-13-05-00.gh-issue-158501.n7Qp3k.rst
M Lib/test/datetimetester.py
M Modules/_datetimemodule.c

diff --git a/Lib/test/datetimetester.py b/Lib/test/datetimetester.py
index 09a0ef5d223d2b..df7d540cb362f7 100644
--- a/Lib/test/datetimetester.py
+++ b/Lib/test/datetimetester.py
@@ -3770,6 +3770,10 @@ def test_fromisoformat_fails_datetime(self):
             '2009-04-19T12:30:45.400 +02:30',  # Space between ms and timezone 
(gh-130959)
             '2009-04-19T12:30:45.400 ',        # Trailing space (gh-130959)
             '2009-04-19T12:30:45. 400',        # Space before fraction 
(gh-130959)
+            '2009-04-19T12:30x+05:00',         # Stray text before offset 
(gh-158501)
+            '2009-04-19T12:30:45 +02:00',      # Space before offset 
(gh-158501)
+            '2009-04-19T12:30:45.123456 junk Z',  # Stray text before Z 
(gh-158501)
+            '20090419T1230:Z',                 # Stray separator before Z 
(gh-158501)
             '2009-04-19T12:30:45+00:90:00', # Time zone field out from range
             '2009-04-19T12:30:45+00:00:90', # Time zone field out from range
             '2009-04-19T12:30:45-00:90:00', # Time zone field out from range
@@ -5094,6 +5098,10 @@ def test_fromisoformat_fails(self):
             '12:30:45.400 +02:30',      # Space between ms and timezone 
(gh-130959)
             '12:30:45.400 ',            # Trailing space (gh-130959)
             '12:30:45. 400',            # Space before fraction (gh-130959)
+            '12:30x+05:00',             # Stray text before offset (gh-158501)
+            '12:30:45 +02:00',          # Space before offset (gh-158501)
+            '12:30:45.123456 junk Z',   # Stray text before Z (gh-158501)
+            '1230:Z',                   # Stray separator before Z (gh-158501)
             '24:00:00.000001',          # Has non-zero microseconds on 24:00
             '24:00:01.000000',          # Has non-zero seconds on 24:00
             '24:01:00.000000',          # Has non-zero minutes on 24:00
diff --git 
a/Misc/NEWS.d/next/Library/2026-10-12-13-05-00.gh-issue-158501.n7Qp3k.rst 
b/Misc/NEWS.d/next/Library/2026-10-12-13-05-00.gh-issue-158501.n7Qp3k.rst
new file mode 100644
index 00000000000000..e47c5da38c8e86
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2026-10-12-13-05-00.gh-issue-158501.n7Qp3k.rst
@@ -0,0 +1,4 @@
+:meth:`datetime.time.fromisoformat` and
+:meth:`datetime.datetime.fromisoformat` now reject stray text between the
+time and a UTC offset. The C implementation previously accepted strings
+that the pure-Python implementation rejected.
diff --git a/Modules/_datetimemodule.c b/Modules/_datetimemodule.c
index 511b444037ceac..cd2dd94c8164a5 100644
--- a/Modules/_datetimemodule.c
+++ b/Modules/_datetimemodule.c
@@ -1046,8 +1046,11 @@ parse_hh_mm_ss_ff(const char *tstr, const char 
*tstr_end, int *hour,
             }
             break;
         }
-        else if (p >= p_end) {
-            return c != '\0';
+        else if (p > p_end) {
+            return 0;  // Stopped at p_end: an offset designator or NUL
+        }
+        else if (p == p_end) {
+            return 1;  // Stray character just before p_end
         }
         else if (has_separator && (c == ':')) {
             if (i == 2) {
@@ -1087,8 +1090,8 @@ parse_hh_mm_ss_ff(const char *tstr, const char *tstr_end, 
int *hour,
         ++p; // skip truncated digits
     }
 
-    // Return 1 if it's not the end of the string
-    return *p != '\0';
+    // Return 1 if there is unparsed text before p_end
+    return p != p_end;
 }
 
 static int
@@ -1122,15 +1125,11 @@ parse_isoformat_time(const char *dtstr, size_t dtlen, 
int *hour, int *minute,
     if (rv < 0) {
         return rv;
     }
+    else if (rv == 1) {
+        return -5;  // Unparsed text after the time
+    }
     else if (tzinfo_pos == p_end) {
-        // We know that there's no time zone, so if there's stuff at the
-        // end of the string it's an error.
-        if (rv == 1) {
-            return -5;
-        }
-        else {
-            return 0;
-        }
+        return 0;
     }
 
     // Special case UTC / Zulu time.

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to