python/cpython · error · ValueError
day {day} must be in range 1..{dim} for month {month} in yea
Error message
day {day} must be in range 1..{dim} for month {month} in year {year} What it means
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.
Source
Thrown at Lib/_pydatetime.py:578
if not isinstance(offset, timedelta):
raise TypeError(f"tzinfo.{name}() must return None "
f"or timedelta, not {type(offset).__name__!r}")
if not -timedelta(1) < offset < timedelta(1):
raise ValueError("offset must be a timedelta "
"strictly between -timedelta(hours=24) and "
f"timedelta(hours=24), not {offset!r}")
def _check_date_fields(year, month, day):
year = _index(year)
month = _index(month)
day = _index(day)
if not MINYEAR <= year <= MAXYEAR:
raise ValueError(f"year must be in {MINYEAR}..{MAXYEAR}, not {year}")
if not 1 <= month <= 12:
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 {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):
hour = _index(hour)
minute = _index(minute)
second = _index(second)
microsecond = _index(microsecond)
if not 0 <= hour <= 23:
raise ValueError(f"hour must be in 0..23, not {hour}")
if not 0 <= minute <= 59:
raise ValueError(f"minute must be in 0..59, not {minute}")
if not 0 <= second <= 59:
raise ValueError(f"second must be in 0..59, not {second}")
if not 0 <= microsecond <= 999999:
raise ValueError(f"microsecond must be in 0..999999, not {microsecond}")
if fold not in (0, 1):
raise ValueError(f"fold must be either 0 or 1, not {fold}")
return hour, minute, second, microsecond, foldView on GitHub (pinned to bc6749cc3b)
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
Example fix
// before end_of_month = date(y, m, 31) # fails for 30-day months // after import calendar end_of_month = date(y, m, calendar.monthrange(y, m)[1])
Defensive patterns
Strategy: validation
Validate before calling
import calendar day = min(day, calendar.monthrange(year, month)[1]) d = date(year, month, day)
Type guard
import calendar
def valid_day(y, m, d) -> bool:
return 1 <= d <= calendar.monthrange(y, m)[1] Prevention
- Never hardcode 31 for month-end; use calendar.monthrange
- Clamp recurring day-of-month schedules (29/30/31) to month length
When it happens
Trigger: date(2023, 2, 29) (non-leap year); date(2024, 4, 31); date(2024, 1, 0); constructing end-of-month dates by hardcoding 31.
Common situations: Hardcoded day=31 for 'end of month' logic; anniversaries scheduled on the 29th/30th hitting shorter months; leap-year miscalculation in hand-rolled calendars.
Related errors
- month must be in 1..12, not {month}
- hour must be in 0..23, not {hour}
- minute must be in 0..59, not {minute}
- second must be in 0..59, not {second}
- microsecond must be in 0..999999, not {microsecond}
AI-assisted analysis of python/cpython@bc6749cc3b (2026-08-14).
Data as JSON: /api/errors/ef437081ce10b6f0.
Report an issue: GitHub.