databendlabs/databend · error

invalid month

Error message

invalid month: {month}

What it means

`last_day_of_month` in the timezone crate panics with `unreachable!("invalid month: {month}")` (civil.rs:132) when given a month outside 1..=12. The function computes the last calendar day (28/29/30/31) and treats any other month value as an internal caller bug, so it aborts with a panic carrying the offending month in the message.

Solutions

  1. Validate the month is in 1..=12 before calling `last_day_of_month`, or normalize with `((month - 1).rem_euclid(12) + 1)` plus year adjustment.
  2. Prefer constructing dates via checked constructors (e.g. `NaiveDate::from_ymd_opt`) which reject invalid months before reaching this helper.
  3. If the input comes from user data, parse with a strict date format (TO_DATE / strptime) so invalid months error out at parse time.
  4. When extending calendar arithmetic, use month-add helpers that carry overflow into the year instead of raw addition.

Example fix

// before
let last_day = last_day_of_month(year, month); // month may be 0 or 13

// after
assert!((1..=12).contains(&month), "month out of range: {month}");
let last_day = last_day_of_month(year, month);

// or normalize overflowing arithmetic
let (year, month) = if month > 12 { (year + 1, month - 12) } else { (year, month) };
Defensive patterns

Strategy: validation

Validate before calling

fn is_valid_month(month: u8) -> bool { (1..=12).contains(&month) }
// call site:
if !is_valid_month(month) { return Err(format!("invalid month: {month}")); }
let last = last_day_of_month(year, month);

Type guard

fn valid_month(m: u8) -> Option<u8> { matches!(m, 1..=12).then_some(m) }

Try / catch

// Rust panic; contain at task boundary
let result = std::panic::catch_unwind(|| last_day_of_month(year, month));

Prevention

When it happens

Trigger: Calling `last_day_of_month(year, month)` (directly or via day_index_for_date / date constructors) with `month == 0`, `month > 12`, or a month computed from unchecked arithmetic (e.g. month + offset wrapping) instead of validating the date first.

Common situations: Date arithmetic that produces month 0 or 13+ (manual `month +/- n` without normalization), parsing dates from untrusted data where month bounds were not checked before calling this internal helper, or integer overflow in custom calendar code.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


AI-assisted analysis of databendlabs/databend@288d84d76e (2026-09-11). Data as JSON: /api/errors/3c58094eaeecc2ce. Report an issue: GitHub.

Appendix: source

Thrown at src/common/timezone/src/civil.rs:132

    let mut ordinal = CUMULATIVE_DAYS[(month - 1) as usize] + day as u16;
    if month > 2 && is_leap_year(year) {
        ordinal += 1;
    }
    ordinal
}

pub(crate) fn last_day_of_month(year: i32, month: u8) -> u8 {
    match month {
        1 | 3 | 5 | 7 | 8 | 10 | 12 => 31,
        4 | 6 | 9 | 11 => 30,
        2 => {
            if is_leap_year(year) {
                29
            } else {
                28
            }
        }
        _ => unreachable!("invalid month: {month}"),
    }
}

pub(crate) fn is_leap_year(year: i32) -> bool {
    (year % 4 == 0 && year % 100 != 0) || year % 400 == 0
}

fn weeks_in_year(year: i32) -> u32 {
    let Some(first_day) = NaiveDate::from_ymd_opt(year, 1, 1) else {
        return 52;
    };
    match first_day.weekday() {
        Weekday::Thu => 53,
        Weekday::Wed if is_leap_year(year) => 53,
        _ => 52,
    }
}

View on GitHub (pinned to 288d84d76e)