{"record":{"id":"ef437081ce10b6f0","repo":"python/cpython","slug":"day-day-must-be-in-range-1-dim-for-month-mon","errorCode":null,"errorMessage":"day {day} must be in range 1..{dim} for month {month} in year {year}","messagePattern":"day (.+?) must be in range 1\\.\\.(.+?) for month (.+?) in year (.+?)","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"Lib/_pydatetime.py","lineNumber":578,"sourceCode":"    if not isinstance(offset, timedelta):\n        raise TypeError(f\"tzinfo.{name}() must return None \"\n                        f\"or timedelta, not {type(offset).__name__!r}\")\n    if not -timedelta(1) < offset < timedelta(1):\n        raise ValueError(\"offset must be a timedelta \"\n                         \"strictly between -timedelta(hours=24) and \"\n                         f\"timedelta(hours=24), not {offset!r}\")\n\ndef _check_date_fields(year, month, day):\n    year = _index(year)\n    month = _index(month)\n    day = _index(day)\n    if not MINYEAR <= year <= MAXYEAR:\n        raise ValueError(f\"year must be in {MINYEAR}..{MAXYEAR}, not {year}\")\n    if not 1 <= month <= 12:\n        raise ValueError(f\"month must be in 1..12, not {month}\")\n    dim = _days_in_month(year, month)\n    if not 1 <= day <= dim:\n        raise ValueError(f\"day {day} must be in range 1..{dim} for month {month} in year {year}\")\n    return year, month, day\n\ndef _check_time_fields(hour, minute, second, microsecond, fold):\n    hour = _index(hour)\n    minute = _index(minute)\n    second = _index(second)\n    microsecond = _index(microsecond)\n    if not 0 <= hour <= 23:\n        raise ValueError(f\"hour must be in 0..23, not {hour}\")\n    if not 0 <= minute <= 59:\n        raise ValueError(f\"minute must be in 0..59, not {minute}\")\n    if not 0 <= second <= 59:\n        raise ValueError(f\"second must be in 0..59, not {second}\")\n    if not 0 <= microsecond <= 999999:\n        raise ValueError(f\"microsecond must be in 0..999999, not {microsecond}\")\n    if fold not in (0, 1):\n        raise ValueError(f\"fold must be either 0 or 1, not {fold}\")\n    return hour, minute, second, microsecond, fold","sourceCodeStart":560,"sourceCodeEnd":596,"githubUrl":"https://github.com/python/cpython/blob/bc6749cc3b5ae4a5e88a6cc2d5b3bebbe354eae6/Lib/_pydatetime.py#L560-L596","documentation":"Raised by _check_date_fields() when the day argument is outside 1..days_in_month for the given (year, month). The bound is month- and leap-year-aware, computed via _days_in_month, so Feb 29 only passes in leap years. The message itself reports the exact allowed range.","triggerScenarios":"date(2023, 2, 29) (non-leap year); date(2024, 4, 31); date(2024, 1, 0); constructing end-of-month dates by hardcoding 31.","commonSituations":"Hardcoded day=31 for 'end of month' logic; anniversaries scheduled on the 29th/30th hitting shorter months; leap-year miscalculation in hand-rolled calendars.","solutions":["Use calendar.monthrange(year, month)[1] to get the real last day instead of hardcoding","Clamp day: day = min(day, monthrange(year, month)[1])","Use calendar.monthlen or dateutil.relativedelta for month-end arithmetic"],"exampleFix":"// before\nend_of_month = date(y, m, 31)  # fails for 30-day months\n// after\nimport calendar\nend_of_month = date(y, m, calendar.monthrange(y, m)[1])","handlingStrategy":"validation","validationCode":"import calendar\n\nday = min(day, calendar.monthrange(year, month)[1])\nd = date(year, month, day)","typeGuard":"import calendar\n\ndef valid_day(y, m, d) -> bool:\n    return 1 <= d <= calendar.monthrange(y, m)[1]","tryCatchPattern":null,"preventionTips":["Never hardcode 31 for month-end; use calendar.monthrange","Clamp recurring day-of-month schedules (29/30/31) to month length"],"tags":["datetime","valueerror","validation","date","leap-year"],"backgroundTag":null,"analyzedSha":"bc6749cc3b5ae4a5e88a6cc2d5b3bebbe354eae6","analyzedAt":"2026-08-14T22:01:13.976Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}