]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-70647: Raise a more informative error for when date is out of range (GH-131335)
authorStan Ulbrych <89152624+StanFromIreland@users.noreply.github.com>
Fri, 21 Mar 2025 03:47:09 +0000 (03:47 +0000)
committerGitHub <noreply@github.com>
Fri, 21 Mar 2025 03:47:09 +0000 (20:47 -0700)
More informative error messages mean less debugging what went wrong.

Lib/_pydatetime.py
Lib/test/datetimetester.py
Misc/NEWS.d/next/Library/2025-03-16-18-30-00.gh-issue-70647.1qq2r3.rst [new file with mode: 0644]
Modules/_datetimemodule.c

index 26bcd1e491d78ced4b5654236256e5d2e530f7a0..50e21a123356117a2c23f49d17d0314b4ef0e91b 100644 (file)
@@ -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):
index 84eb872f964ba10d7ca0459fb8e92e8327758a6e..f9d20ef9c626a94beb41ae4f06d6b799bdd5188c 100644 (file)
@@ -1988,8 +1988,6 @@ class TestDate(HarmlessMixedComparison, unittest.TestCase):
             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 @@ class TestDate(HarmlessMixedComparison, unittest.TestCase):
                 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 = [
@@ -3259,7 +3262,6 @@ class TestDateTime(TestDate):
             (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 @@ class TestDateTime(TestDate):
                 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 @@ class TestDateTime(TestDate):
             "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 @@ class TestDateTime(TestDate):
                 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 (file)
index 0000000..33fb244
--- /dev/null
@@ -0,0 +1,2 @@
+When creating a :mod:`datetime` object with an out of range date a more informative
+error is raised.
index 70b59d67f56bda9a636ae31e61f1a94e95069765..9bba0e3354b26ba7c9144ff7b6b064d03866fdeb 100644 (file)
@@ -664,7 +664,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;