mirror of https://github.com/python/cpython
GH-90750: Use datetime.fromisocalendar in _strptime (#103802)
Use datetime.fromisocalendar in _strptime This unifies the ISO → Gregorian conversion logic and improves handling of invalid ISO weeks.
This commit is contained in:
parent
b701dce340
commit
a5308e188b
|
@ -290,22 +290,6 @@ def _calc_julian_from_U_or_W(year, week_of_year, day_of_week, week_starts_Mon):
|
|||
return 1 + days_to_week + day_of_week
|
||||
|
||||
|
||||
def _calc_julian_from_V(iso_year, iso_week, iso_weekday):
|
||||
"""Calculate the Julian day based on the ISO 8601 year, week, and weekday.
|
||||
ISO weeks start on Mondays, with week 01 being the week containing 4 Jan.
|
||||
ISO week days range from 1 (Monday) to 7 (Sunday).
|
||||
"""
|
||||
correction = datetime_date(iso_year, 1, 4).isoweekday() + 3
|
||||
ordinal = (iso_week * 7) + iso_weekday - correction
|
||||
# ordinal may be negative or 0 now, which means the date is in the previous
|
||||
# calendar year
|
||||
if ordinal < 1:
|
||||
ordinal += datetime_date(iso_year, 1, 1).toordinal()
|
||||
iso_year -= 1
|
||||
ordinal -= datetime_date(iso_year, 1, 1).toordinal()
|
||||
return iso_year, ordinal
|
||||
|
||||
|
||||
def _strptime(data_string, format="%a %b %d %H:%M:%S %Y"):
|
||||
"""Return a 2-tuple consisting of a time struct and an int containing
|
||||
the number of microseconds based on the input string and the
|
||||
|
@ -483,7 +467,8 @@ def _strptime(data_string, format="%a %b %d %H:%M:%S %Y"):
|
|||
else:
|
||||
tz = value
|
||||
break
|
||||
# Deal with the cases where ambiguities arize
|
||||
|
||||
# Deal with the cases where ambiguities arise
|
||||
# don't assume default values for ISO week/year
|
||||
if year is None and iso_year is not None:
|
||||
if iso_week is None or weekday is None:
|
||||
|
@ -511,7 +496,6 @@ def _strptime(data_string, format="%a %b %d %H:%M:%S %Y"):
|
|||
elif year is None:
|
||||
year = 1900
|
||||
|
||||
|
||||
# If we know the week of the year and what day of that week, we can figure
|
||||
# out the Julian day of the year.
|
||||
if julian is None and weekday is not None:
|
||||
|
@ -520,7 +504,10 @@ def _strptime(data_string, format="%a %b %d %H:%M:%S %Y"):
|
|||
julian = _calc_julian_from_U_or_W(year, week_of_year, weekday,
|
||||
week_starts_Mon)
|
||||
elif iso_year is not None and iso_week is not None:
|
||||
year, julian = _calc_julian_from_V(iso_year, iso_week, weekday + 1)
|
||||
datetime_result = datetime_date.fromisocalendar(iso_year, iso_week, weekday + 1)
|
||||
year = datetime_result.year
|
||||
month = datetime_result.month
|
||||
day = datetime_result.day
|
||||
if julian is not None and julian <= 0:
|
||||
year -= 1
|
||||
yday = 366 if calendar.isleap(year) else 365
|
||||
|
|
|
@ -242,6 +242,16 @@ class StrptimeTests(unittest.TestCase):
|
|||
# 5. Julian/ordinal day (%j) is specified with %G, but not %Y
|
||||
with self.assertRaises(ValueError):
|
||||
_strptime._strptime("1999 256", "%G %j")
|
||||
# 6. Invalid ISO weeks
|
||||
invalid_iso_weeks = [
|
||||
"2019-00-1",
|
||||
"2019-54-1",
|
||||
"2021-53-1",
|
||||
]
|
||||
for invalid_iso_dtstr in invalid_iso_weeks:
|
||||
with self.subTest(invalid_iso_dtstr):
|
||||
with self.assertRaises(ValueError):
|
||||
_strptime._strptime(invalid_iso_dtstr, "%G-%V-%u")
|
||||
|
||||
|
||||
def test_strptime_exception_context(self):
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
Use :meth:`datetime.datetime.fromisocalendar` in the implementation of
|
||||
:meth:`datetime.datetime.strptime`, which should now accept only valid ISO
|
||||
dates. (Patch by Paul Ganssle)
|
Loading…
Reference in New Issue