databendlabs/databend · error

date day count is inside the chrono civil range

Error message

date day count is inside the chrono civil range

What it means

date_from_days adds the given epoch-day count to the 1970-01-01 base date. This panic fires when the day count is outside chrono's supported civil range (roughly years -262144..=262143 in checked arithmetic, and practically outside DATE_MIN/DATE_MAX of year ~-4713 to 9999). SQL inputs are expected to pass check_date first, so hitting this means an unchecked or computed value escaped validation.

Solutions

  1. Call check_date (or clamp_date) on the day value before date_from_days.
  2. Validate user-supplied intervals in date arithmetic so results stay within DATE_MIN..=DATE_MAX (days -719162..=2932896).
  3. Replace expect with a graceful error mapping to NULL/ErrorCode for out-of-range inputs in expression evaluation.

Example fix

// before
let date = date_from_days(days);
// after
if !(DATE_MIN as i64..=DATE_MAX as i64).contains(&days) {
    return Err(ErrorCode::BadArguments("date out of range"));
}
let date = date_from_days(days);
Defensive patterns

Strategy: validation

Validate before calling

if !(DATE_MIN as i64..=DATE_MAX as i64).contains(&days) {
    return Err(ErrorCode::BadArguments(format!("date out of range: {days}")));
}
let d = date_from_days(days);

Type guard

fn in_date_range(days: i64) -> bool { (DATE_MIN as i64..=DATE_MAX as i64).contains(&days) }

Prevention

When it happens

Trigger: Calling date_from_days with days outside the valid date range — e.g. i64 values from unvalidated user input, arithmetic results of DATE_ADD/DATE_DIFF overflowing the calendar, or paths that bypass check_date.

Common situations: User SQL producing extreme dates (huge DATE_ADD intervals); internal arithmetic like date arithmetic in eval_date_diff feeding unchecked results; data files containing out-of-range day numbers loaded without validation.

Understand the failure class

Background: "value must be between 0 and 1" / "out of range" / "must not be negative" errors: fixing range-validation failures across open-source libraries — this error's family across 42 libraries.

Related errors


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

Appendix: source

Thrown at src/query/expression/src/types/date.rs:52

use crate::ScalarRef;
use crate::property::Domain;
use crate::values::Column;
use crate::values::Scalar;

pub const DATE_FORMAT: &str = "%Y-%m-%d";
/// SQL DATE bounds, represented as days since 1970-01-01.
/// Calendar inputs and computed DATE values both use years 0001..=9999.
/// 0001-01-01
pub const DATE_MIN: i32 = -719_162;
/// 9999-12-31
pub const DATE_MAX: i32 = 2_932_896;

/// Converts internal epoch days. SQL inputs must pass `check_date` first.
pub fn date_from_days(days: impl AsPrimitive<i64>) -> NaiveDate {
    NaiveDate::from_ymd_opt(1970, 1, 1)
        .expect("epoch date is valid")
        .checked_add_signed(TimeDelta::days(days.as_()))
        .expect("date day count is inside the chrono civil range")
}

/// Preserve the legacy conversion policy: either bound overflow maps to DATE_MIN.
#[inline]
pub fn clamp_date(days: i64) -> i32 {
    if (DATE_MIN as i64..=DATE_MAX as i64).contains(&days) {
        days as i32
    } else {
        DATE_MIN
    }
}

/// Validate the SQL DATE range without silently changing the value.
#[inline]
pub fn check_date(days: i64) -> Result<i32, String> {
    if (i64::from(DATE_MIN)..=i64::from(DATE_MAX)).contains(&days) {
        Ok(days as i32)
    } else {

View on GitHub (pinned to 288d84d76e)