Skip to content

gh-70647: Raise a more informative error for when date is out of range #131335

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Mar 21, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Lib/_pydatetime.py
Original file line number Diff line number Diff line change
Expand Up @@ -576,7 +576,7 @@ def _check_date_fields(year, month, day):
raise ValueError(f"month must be in 1..12, not {month}")
dim = _days_in_month(year, month)
if not 1 <= day <= dim:
raise ValueError(f"day must be in 1..{dim}, not {day}")
raise ValueError(f"day {day} must be in range 1..{dim} for month {month} in year {year}")
return year, month, day

def _check_time_fields(hour, minute, second, microsecond, fold):
Expand Down
19 changes: 15 additions & 4 deletions Lib/test/datetimetester.py
Original file line number Diff line number Diff line change
Expand Up @@ -1988,8 +1988,6 @@ def test_valuerror_messages(self):
r"(year|month|day) must be in \d+\.\.\d+, not \d+"
)
test_cases = [
(2009, 1, 32), # Day out of range
(2009, 2, 31), # Day out of range
(2009, 13, 1), # Month out of range
(2009, 0, 1), # Month out of range
(10000, 12, 31), # Year out of range
Expand All @@ -2000,6 +1998,11 @@ def test_valuerror_messages(self):
with self.assertRaisesRegex(ValueError, pattern):
self.theclass(*case)

# days out of range have their own error message, see issue 70647
with self.assertRaises(ValueError) as msg:
self.theclass(2009, 1, 32)
self.assertIn(f"day 32 must be in range 1..31 for month 1 in year 2009", str(msg.exception))

def test_fromisoformat(self):
# Test that isoformat() is reversible
base_dates = [
Expand Down Expand Up @@ -3259,7 +3262,6 @@ def test_valuerror_messages(self):
(2009, 4, 1, 12, 30, 90), # Second out of range
(2009, 4, 1, 12, 90, 45), # Minute out of range
(2009, 4, 1, 25, 30, 45), # Hour out of range
(2009, 4, 32, 24, 0, 0), # Day out of range
(2009, 13, 1, 24, 0, 0), # Month out of range
(9999, 12, 31, 24, 0, 0), # Year out of range
]
Expand All @@ -3268,6 +3270,11 @@ def test_valuerror_messages(self):
with self.assertRaisesRegex(ValueError, pattern):
self.theclass(*case)

# days out of range have their own error message, see issue 70647
with self.assertRaises(ValueError) as msg:
self.theclass(2009, 4, 32, 24, 0, 0)
self.assertIn(f"day 32 must be in range 1..30 for month 4 in year 2009", str(msg.exception))

def test_fromisoformat_datetime(self):
# Test that isoformat() is reversible
base_dates = [
Expand Down Expand Up @@ -3575,7 +3582,6 @@ def test_fromisoformat_fails_datetime_valueerror(self):
"2009-04-01T12:30:90", # Second out of range
"2009-04-01T12:90:45", # Minute out of range
"2009-04-01T25:30:45", # Hour out of range
"2009-04-32T24:00:00", # Day out of range
"2009-13-01T24:00:00", # Month out of range
"9999-12-31T24:00:00", # Year out of range
]
Expand All @@ -3585,6 +3591,11 @@ def test_fromisoformat_fails_datetime_valueerror(self):
with self.assertRaisesRegex(ValueError, pattern):
self.theclass.fromisoformat(bad_str)

# days out of range have their own error message, see issue 70647
with self.assertRaises(ValueError) as msg:
self.theclass.fromisoformat("2009-04-32T24:00:00")
self.assertIn(f"day 32 must be in range 1..30 for month 4 in year 2009", str(msg.exception))

def test_fromisoformat_fails_surrogate(self):
# Test that when fromisoformat() fails with a surrogate character as
# the separator, the error message contains the original string
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
When creating a :mod:`datetime` object with an out of range date a more informative
error is raised.
3 changes: 2 additions & 1 deletion Modules/_datetimemodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -663,7 +663,8 @@ check_date_args(int year, int month, int day)
int dim = days_in_month(year, month);
if (day < 1 || day > dim) {
PyErr_Format(PyExc_ValueError,
"day must be in 1..%d, not %d", dim, day);
"day %i must be in range 1..%d for month %i in year %i",
day, dim, month, year);
return -1;
}
return 0;
Expand Down
Loading