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
- 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.
- Prefer constructing dates via checked constructors (e.g. `NaiveDate::from_ymd_opt`) which reject invalid months before reaching this helper.
- If the input comes from user data, parse with a strict date format (TO_DATE / strptime) so invalid months error out at parse time.
- 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
- Always normalize month arithmetic (month+/-n) into 1..=12 with year carry
- Build dates via checked constructors like NaiveDate::from_ymd_opt
- Parse user-supplied dates with strict format validation before calendar math
- Range-check month in any struct that stores it before use
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)