Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit d4e233e

Browse files
authoredMay 19, 2025··
Merge branch 'python:main' into pdb_help_usage
2 parents d54f748 + 27bd082 commit d4e233e

File tree

14 files changed

+346
-122
lines changed

14 files changed

+346
-122
lines changed
 

‎Lib/_pydatetime.py‎

Lines changed: 35 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -467,6 +467,7 @@ def _parse_isoformat_time(tstr):
467467
hour, minute, second, microsecond = time_comps
468468
became_next_day = False
469469
error_from_components = False
470+
error_from_tz = None
470471
if (hour == 24):
471472
if all(time_comp == 0 for time_comp in time_comps[1:]):
472473
hour = 0
@@ -500,14 +501,22 @@ def _parse_isoformat_time(tstr):
500501
else:
501502
tzsign = -1 if tstr[tz_pos - 1] == '-' else 1
502503

503-
td = timedelta(hours=tz_comps[0], minutes=tz_comps[1],
504-
seconds=tz_comps[2], microseconds=tz_comps[3])
505-
506-
tzi = timezone(tzsign * td)
504+
try:
505+
# This function is intended to validate datetimes, but because
506+
# we restrict time zones to ±24h, it serves here as well.
507+
_check_time_fields(hour=tz_comps[0], minute=tz_comps[1],
508+
second=tz_comps[2], microsecond=tz_comps[3],
509+
fold=0)
510+
except ValueError as e:
511+
error_from_tz = e
512+
else:
513+
td = timedelta(hours=tz_comps[0], minutes=tz_comps[1],
514+
seconds=tz_comps[2], microseconds=tz_comps[3])
515+
tzi = timezone(tzsign * td)
507516

508517
time_comps.append(tzi)
509518

510-
return time_comps, became_next_day, error_from_components
519+
return time_comps, became_next_day, error_from_components, error_from_tz
511520

512521
# tuple[int, int, int] -> tuple[int, int, int] version of date.fromisocalendar
513522
def _isoweek_to_gregorian(year, week, day):
@@ -1633,9 +1642,21 @@ def fromisoformat(cls, time_string):
16331642
time_string = time_string.removeprefix('T')
16341643

16351644
try:
1636-
return cls(*_parse_isoformat_time(time_string)[0])
1637-
except Exception:
1638-
raise ValueError(f'Invalid isoformat string: {time_string!r}')
1645+
time_components, _, error_from_components, error_from_tz = (
1646+
_parse_isoformat_time(time_string)
1647+
)
1648+
except ValueError:
1649+
raise ValueError(
1650+
f'Invalid isoformat string: {time_string!r}') from None
1651+
else:
1652+
if error_from_tz:
1653+
raise error_from_tz
1654+
if error_from_components:
1655+
raise ValueError(
1656+
"Minute, second, and microsecond must be 0 when hour is 24"
1657+
)
1658+
1659+
return cls(*time_components)
16391660

16401661
def strftime(self, format):
16411662
"""Format using strftime(). The date part of the timestamp passed
@@ -1947,11 +1968,16 @@ def fromisoformat(cls, date_string):
19471968

19481969
if tstr:
19491970
try:
1950-
time_components, became_next_day, error_from_components = _parse_isoformat_time(tstr)
1971+
(time_components,
1972+
became_next_day,
1973+
error_from_components,
1974+
error_from_tz) = _parse_isoformat_time(tstr)
19511975
except ValueError:
19521976
raise ValueError(
19531977
f'Invalid isoformat string: {date_string!r}') from None
19541978
else:
1979+
if error_from_tz:
1980+
raise error_from_tz
19551981
if error_from_components:
19561982
raise ValueError("minute, second, and microsecond must be 0 when hour is 24")
19571983

‎Lib/test/datetimetester.py‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3571,6 +3571,10 @@ def test_fromisoformat_fails_datetime(self):
35713571
'2009-04-19T12:30:45.400 +02:30', # Space between ms and timezone (gh-130959)
35723572
'2009-04-19T12:30:45.400 ', # Trailing space (gh-130959)
35733573
'2009-04-19T12:30:45. 400', # Space before fraction (gh-130959)
3574+
'2009-04-19T12:30:45+00:90:00', # Time zone field out from range
3575+
'2009-04-19T12:30:45+00:00:90', # Time zone field out from range
3576+
'2009-04-19T12:30:45-00:90:00', # Time zone field out from range
3577+
'2009-04-19T12:30:45-00:00:90', # Time zone field out from range
35743578
]
35753579

35763580
for bad_str in bad_strs:
@@ -4795,6 +4799,11 @@ def test_fromisoformat_fails(self):
47954799
'12:30:45.400 +02:30', # Space between ms and timezone (gh-130959)
47964800
'12:30:45.400 ', # Trailing space (gh-130959)
47974801
'12:30:45. 400', # Space before fraction (gh-130959)
4802+
'24:00:00.000001', # Has non-zero microseconds on 24:00
4803+
'24:00:01.000000', # Has non-zero seconds on 24:00
4804+
'24:01:00.000000', # Has non-zero minutes on 24:00
4805+
'12:30:45+00:90:00', # Time zone field out from range
4806+
'12:30:45+00:00:90', # Time zone field out from range
47984807
]
47994808

48004809
for bad_str in bad_strs:

‎Lib/test/test_interpreters/test_api.py‎

Lines changed: 2 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -647,59 +647,6 @@ def test_created_with_capi(self):
647647
self.interp_exists(interpid))
648648

649649

650-
def test_remaining_threads(self):
651-
r_interp, w_interp = self.pipe()
652-
653-
FINISHED = b'F'
654-
655-
# It's unlikely, but technically speaking, it's possible
656-
# that the thread could've finished before interp.close() is
657-
# reached, so this test might not properly exercise the case.
658-
# However, it's quite unlikely and I'm too lazy to deal with it.
659-
interp = interpreters.create()
660-
interp.exec(f"""if True:
661-
import os
662-
import threading
663-
import time
664-
665-
def task():
666-
time.sleep(1)
667-
os.write({w_interp}, {FINISHED!r})
668-
669-
threads = [threading.Thread(target=task) for _ in range(3)]
670-
for t in threads:
671-
t.start()
672-
""")
673-
interp.close()
674-
675-
self.assertEqual(os.read(r_interp, 1), FINISHED)
676-
677-
def test_remaining_daemon_threads(self):
678-
interp = _interpreters.create(
679-
types.SimpleNamespace(
680-
use_main_obmalloc=False,
681-
allow_fork=False,
682-
allow_exec=False,
683-
allow_threads=True,
684-
allow_daemon_threads=True,
685-
check_multi_interp_extensions=True,
686-
gil='own',
687-
)
688-
)
689-
_interpreters.exec(interp, f"""if True:
690-
import threading
691-
import time
692-
693-
def task():
694-
time.sleep(100)
695-
696-
threads = [threading.Thread(target=task, daemon=True) for _ in range(3)]
697-
for t in threads:
698-
t.start()
699-
""")
700-
_interpreters.destroy(interp)
701-
702-
703650
class TestInterpreterPrepareMain(TestBase):
704651

705652
def test_empty(self):
@@ -808,10 +755,7 @@ def script():
808755
spam.eggs()
809756
810757
interp = interpreters.create()
811-
try:
812-
interp.exec(script)
813-
finally:
814-
interp.close()
758+
interp.exec(script)
815759
""")
816760

817761
stdout, stderr = self.assert_python_failure(scriptfile)
@@ -820,7 +764,7 @@ def script():
820764
# File "{interpreters.__file__}", line 179, in exec
821765
self.assertEqual(stderr, dedent(f"""\
822766
Traceback (most recent call last):
823-
File "{scriptfile}", line 10, in <module>
767+
File "{scriptfile}", line 9, in <module>
824768
interp.exec(script)
825769
~~~~~~~~~~~^^^^^^^^
826770
{interpmod_line.strip()}

‎Lib/test/test_interpreters/test_lifecycle.py‎

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,6 @@ def test_sys_path_0(self):
132132
'sub': sys.path[0],
133133
}}, indent=4), flush=True)
134134
""")
135-
interp.close()
136135
'''
137136
# <tmp>/
138137
# pkg/
@@ -173,10 +172,7 @@ def test_gh_109793(self):
173172
argv = [sys.executable, '-c', '''if True:
174173
from test.support import interpreters
175174
interp = interpreters.create()
176-
try:
177-
raise Exception
178-
finally:
179-
interp.close()
175+
raise Exception
180176
''']
181177
proc = subprocess.run(argv, capture_output=True, text=True)
182178
self.assertIn('Traceback', proc.stderr)
There was a problem loading the remainder of the diff.

0 commit comments

Comments
 (0)
Please sign in to comment.

Follow Lee on X/Twitter - Father, Husband, Serial builder creating AI, crypto, games & web tools. We are friends :) AI Will Come To Life!

Check out: eBank.nz (Art Generator) | Netwrck.com (AI Tools) | Text-Generator.io (AI API) | BitBank.nz (Crypto AI) | ReadingTime (Kids Reading) | RewordGame | BigMultiplayerChess | WebFiddle | How.nz | Helix AI Assistant