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, fold

View on GitHub (pinned to bc6749cc3b)

Solutions

  1. Use calendar.monthrange(year, month)[1] to get the real last day instead of hardcoding
  2. Clamp day: day = min(day, monthrange(year, month)[1])
  3. 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

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


AI-assisted analysis of python/cpython@bc6749cc3b (2026-08-14). Data as JSON: /api/errors/ef437081ce10b6f0. Report an issue: GitHub.