From 661e8c2bd03d1dc1578e83fd54f96bbedf491c81 Mon Sep 17 00:00:00 2001 From: Stan Ulbrych Date: Sun, 16 Mar 2025 18:33:17 +0000 Subject: [PATCH 1/4] Initial --- Lib/_pydatetime.py | 2 +- Lib/test/datetimetester.py | 19 +++++++++++++++---- ...5-03-16-18-30-00.gh-issue-70647.1qq2r3.rst | 2 ++ Modules/_datetimemodule.c | 3 ++- 4 files changed, 20 insertions(+), 6 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2025-03-16-18-30-00.gh-issue-70647.1qq2r3.rst diff --git a/Lib/_pydatetime.py b/Lib/_pydatetime.py index 26bcd1e491d78c..50e21a12335611 100644 --- a/Lib/_pydatetime.py +++ b/Lib/_pydatetime.py @@ -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): diff --git a/Lib/test/datetimetester.py b/Lib/test/datetimetester.py index 84eb872f964ba1..581211017e6f9b 100644 --- a/Lib/test/datetimetester.py +++ b/Lib/test/datetimetester.py @@ -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 @@ -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..30 for month 4 in year 2009", str(msg.exception)) + def test_fromisoformat(self): # Test that isoformat() is reversible base_dates = [ @@ -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 ] @@ -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 = [ @@ -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 ] @@ -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 diff --git a/Misc/NEWS.d/next/Library/2025-03-16-18-30-00.gh-issue-70647.1qq2r3.rst b/Misc/NEWS.d/next/Library/2025-03-16-18-30-00.gh-issue-70647.1qq2r3.rst new file mode 100644 index 00000000000000..33fb244cd563ba --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-03-16-18-30-00.gh-issue-70647.1qq2r3.rst @@ -0,0 +1,2 @@ +When creating a :mod:`datetime` object with an out of range date a more informative +error is raised. diff --git a/Modules/_datetimemodule.c b/Modules/_datetimemodule.c index 83b92fe606984c..905636dceb842d 100644 --- a/Modules/_datetimemodule.c +++ b/Modules/_datetimemodule.c @@ -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; From cea6cf3bc7ac6630e0e08ee0e31ff968f1cd149d Mon Sep 17 00:00:00 2001 From: Stan Ulbrych Date: Sun, 16 Mar 2025 18:37:32 +0000 Subject: [PATCH 2/4] Remove accidental backtick --- Modules/_datetimemodule.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/_datetimemodule.c b/Modules/_datetimemodule.c index 905636dceb842d..2961a52b92f877 100644 --- a/Modules/_datetimemodule.c +++ b/Modules/_datetimemodule.c @@ -663,7 +663,7 @@ 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 %i must be in range 1..%d for month %i in year %i", + "day %i must be in range 1..%d for month %i in year %i", day, dim, month, year); return -1; } From afb8fcf5d3330d03881b122b87a44909bd22de80 Mon Sep 17 00:00:00 2001 From: Stan Ulbrych Date: Sun, 16 Mar 2025 19:55:20 +0000 Subject: [PATCH 3/4] Requested changes --- Lib/test/datetimetester.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Lib/test/datetimetester.py b/Lib/test/datetimetester.py index 581211017e6f9b..051c7fde04b556 100644 --- a/Lib/test/datetimetester.py +++ b/Lib/test/datetimetester.py @@ -2001,7 +2001,7 @@ def test_valuerror_messages(self): # 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..30 for month 4 in year 2009", str(msg.exception)) + self.assertIn(f"day 32 must be in range 1..30 for month 4 in year 2009", str(msg.exception)) def test_fromisoformat(self): # Test that isoformat() is reversible @@ -3273,7 +3273,7 @@ def test_valuerror_messages(self): # 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)) + 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 @@ -3594,7 +3594,7 @@ def test_fromisoformat_fails_datetime_valueerror(self): # 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)) + 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 From cb8d1bb5d3725332547416908146f87edcacdbf2 Mon Sep 17 00:00:00 2001 From: Stan Ulbrych Date: Mon, 17 Mar 2025 08:54:52 +0000 Subject: [PATCH 4/4] Fix test --- Lib/test/datetimetester.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/datetimetester.py b/Lib/test/datetimetester.py index 051c7fde04b556..f9d20ef9c626a9 100644 --- a/Lib/test/datetimetester.py +++ b/Lib/test/datetimetester.py @@ -2001,7 +2001,7 @@ def test_valuerror_messages(self): # 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..30 for month 4 in year 2009", str(msg.exception)) + 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