GitoxideLabs/gitoxide · error

Date lies out of range

Error message

Date lies out of range: {}-{:02}

What it means

In gix-date's relative-date parsing, `Fields::normalize()` rebuilds a point in time from year/month/day fields. `civil::Date::new(year, month, 1)` fails when the year or month values themselves are outside jiff's supported civil-date range; the code then raises Error 'Date lies out of range: {year}-{month}' as context over the jiff error.

Solutions

  1. Validate/clamp the requested period count so the resulting date stays within the supported calendar range (roughly years -9999..=9999)
  2. Catch the Error and reject the relative date expression with a user-facing message about the supported range
  3. Parse to an absolute timestamp instead of extreme relative offsets

Example fix

// before
parse("now", "999999 years ago")
// after
let years = count.min(9999);
parse("now", &format!("{years} years ago"))
Defensive patterns

Strategy: validation

Validate before calling

fn within_calendar_range(year: i32, month: u8) -> bool {
    (-9999..=9999).contains(&year) && (1..=12).contains(&month)
}

Try / catch

match relative.parse(...) {
    Ok(t) => t,
    Err(e) => return Err(format!("relative date out of supported range: {e}")),
}

Prevention

When it happens

Trigger: `subtract_pairs` (reached via relative-date `parse`) producing year/month fields outside the supported calendar range — e.g. adding or subtracting huge period counts (like '90000 years ago') that push the computed year/month below jiff's minimum (-9999) or above its maximum year.

Common situations: Parsing user input like '999999 years ago' or extreme relative dates in commit-date filters or `git log --since`-style expressions implemented on gix-date.

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 GitoxideLabs/gitoxide@e73179060b (2026-09-08). Data as JSON: /api/errors/6b9b5c58f35713f9. Report an issue: GitHub.

Appendix: source

Thrown at gix-date/src/parse/relative.rs:170

    impl From<Zoned> for Fields {
        fn from(zdt: Zoned) -> Self {
            Fields {
                year: zdt.year(),
                month: zdt.month(),
                day: zdt.day(),
                time: zdt.time(),
                timezone: zdt.time_zone().clone(),
            }
        }
    }

    impl Fields {
        /// Turn the fields back into a point in time: a day beyond the end of the month rolls over into the
        /// following month. One month before May 31st is thus May 1st, a day after April 30th.
        fn normalize(&self) -> Result<Zoned, Exn<Error>> {
            let first_of_month = civil::Date::new(self.year, self.month, 1)
                .or_raise(|| Error::new(format!("Date lies out of range: {}-{:02}", self.year, self.month)))?;
            let days_beyond_first = SignedDuration::from_secs((i64::from(self.day) - 1) * 24 * 60 * 60);
            first_of_month
                .checked_add(days_beyond_first)
                .or_raise(|| Error::new(format!("Day {} lies out of range", self.day)))?
                .to_datetime(self.time)
                .to_zoned(self.timezone.clone())
                .or_raise(|| Error::new("Could not convert date to a point in time"))
        }
    }

    let now = now.ok_or(ValidationError::new("Missing current time"))?;
    let mut fields = Fields::from(now);
    for Pair { period, count, unit } in pairs {
        let err = || Error::new(format!("Couldn't parse span from '{period} {count}'"));
        match unit {
            Unit::Seconds(factor) => {
                let seconds = count
                    .checked_mul(*factor)

View on GitHub (pinned to e73179060b)