{"record":{"id":"e3e3fe2c2cef1337","repo":"databendlabs/databend","slug":"month-arithmetic-produced-an-invalid-month","errorCode":null,"errorMessage":"month arithmetic produced an invalid month","messagePattern":"month arithmetic produced an invalid month","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/query/functions/src/scalars/timestamp/src/interval.rs","lineNumber":829,"sourceCode":"        local,\n        LocalTimeResolution::Compatible,\n        preferred_offset,\n    )\n    .ok_or_else(|| \"Invalid date: calendar arithmetic is out of range\".to_string())?;\n    resolved\n        .unix_seconds\n        .checked_mul(1_000_000)\n        .and_then(|seconds| seconds.checked_add(i64::from(micro)))\n        .ok_or_else(|| \"Invalid date: calendar arithmetic is out of range\".to_string())\n}\n\nfn days_in_month(year: i64, month: u8) -> u8 {\n    match month {\n        1 | 3 | 5 | 7 | 8 | 10 | 12 => 31,\n        4 | 6 | 9 | 11 => 30,\n        2 if (year % 4 == 0 && year % 100 != 0) || year % 400 == 0 => 29,\n        2 => 28,\n        _ => unreachable!(\"month arithmetic produced an invalid month\"),\n    }\n}\n\npub(crate) fn civil_date_to_days(year: i64, month: u8, day: u8) -> i128 {\n    let mut year = i128::from(year);\n    let month = i128::from(month);\n    let day = i128::from(day);\n    year -= i128::from(month <= 2);\n    let era = year.div_euclid(400);\n    let year_of_era = year - era * 400;\n    let month_prime = month + if month > 2 { -3 } else { 9 };\n    let day_of_year = (153 * month_prime + 2) / 5 + day - 1;\n    let day_of_era = year_of_era * 365 + year_of_era / 4 - year_of_era / 100 + day_of_year;\n    era * 146_097 + day_of_era - 719_468\n}\n\npub(crate) fn civil_date_from_days(days: i128) -> (i128, u8, u8) {\n    let days = days + 719_468;","sourceCodeStart":811,"sourceCodeEnd":847,"githubUrl":"https://github.com/databendlabs/databend/blob/288d84d76e20a2f8f7173bda9691eb6ece301aa9/src/query/functions/src/scalars/timestamp/src/interval.rs#L811-L847","documentation":"days_in_month (src/query/functions/src/scalars/timestamp/src/interval.rs:829), called by apply_interval_to_civil, returns the day count for months 1-12 with leap-year handling for February. A month value outside 1..=12 hits `unreachable!(\"month arithmetic produced an invalid month\")`. The invariant is that month arithmetic (adding intervals to civil dates with normalization) always yields a month in 1..=12 before day-count lookup.","triggerScenarios":"Applying an interval whose month component drives the civil-date month outside 1..=12 — e.g. a bug or integer overflow in the month-normalization step of apply_interval_to_civil (such as very large month offsets overflowing i64/u8 arithmetic), producing month 0, 13, or beyond.","commonSituations":"Extremely large INTERVAL values (e.g. INTERVAL '999999999999' MONTH) that overflow month arithmetic; a regression in the carry/borrow logic that normalizes month+year after interval application; fuzzer-generated huge interval inputs.","solutions":["Reproduce with the failing interval value and check month normalization in apply_interval_to_civil for overflow or off-by-one carry handling.","Add explicit modulo/checked arithmetic ((m - 1) % 12 + 1 style) when normalizing months before calling days_in_month.","Validate month range at the boundary (1..=12) and return an overflow/query error for extreme intervals instead of panicking.","Add a regression test with boundary interval values (max month/year offsets)."],"exampleFix":"// before\n_ => unreachable!(\"month arithmetic produced an invalid month\"),\n// after\n_ => return Err(ErrorCode::Overflow(format!(\n    \"month arithmetic produced an invalid month: {}\", month))),\n// plus in apply_interval_to_civil:\nlet total = year * 12 + (month as i64 - 1) + delta_months;\nlet (year, month) = (total.div_euclid(12), (total.rem_euclid(12) + 1) as u8);","handlingStrategy":"validation","validationCode":"fn validate_interval_months(delta_months: i64) -> Result<(), String> {\n    // bound interval magnitude so month normalization cannot overflow\n    if delta_months.abs() > 12 * 100_000_000 {\n        Err(format!(\"interval too large: {} months\", delta_months))\n    } else { Ok(()) }\n}","typeGuard":"fn is_valid_month(month: u8) -> bool { (1..=12).contains(&month) }","tryCatchPattern":"// Normalize months defensively before day lookup:\nlet total = year * 12 + (month as i64) - 1 + delta;\nlet (year, month) = (total.div_euclid(12), (total.rem_euclid(12) + 1) as u8);\ndebug_assert!((1..=12).contains(&month));","preventionTips":["Use checked/div_euclid arithmetic when adding month intervals to civil dates.","Test apply_interval_to_civil with extreme INTERVAL values (i64 bounds).","Fuzz interval application with large offsets to surface overflow early.","Keep month normalization in one place so days_in_month always sees 1..=12."],"tags":["timestamps","intervals","rust","panic","integer-overflow"],"backgroundTag":"internal-invariant-violation","analyzedSha":"288d84d76e20a2f8f7173bda9691eb6ece301aa9","analyzedAt":"2026-09-11T11:29:36.208Z","contentChangedAt":"2026-09-11T11:29:36.208Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}