nautechsystems/nautilus_trader · error

Failed to subtract {n} years from {datetime}

Error message

Failed to subtract {n} years from {datetime}

What it means

subtract_n_years delegates to shift_months with a negative month delta; if the resulting date would be invalid or precedes the representable Timestamp minimum (before the UNIX epoch / i64 nanosecond floor), shift_months fails and this contextual error is returned. It signals the requested subtraction is not representable.

Source

Thrown at crates/core/src/datetime.rs:626

        anyhow::anyhow!("Failed to add {n} years to {datetime}: month count overflow")
    })?;

    shift_months(datetime, i64::from(months))
        .map_err(|_| anyhow::anyhow!("Failed to add {n} years to {datetime}"))
}

/// Subtract `n` years from a Jiff [`Timestamp`].
///
/// # Errors
///
/// Returns an error if the resulting date would be invalid or out of range.
pub fn subtract_n_years(datetime: Timestamp, n: u32) -> anyhow::Result<Timestamp> {
    let months = n.checked_mul(12).ok_or_else(|| {
        anyhow::anyhow!("Failed to subtract {n} years from {datetime}: month count overflow")
    })?;

    shift_months(datetime, -i64::from(months))
        .map_err(|_| anyhow::anyhow!("Failed to subtract {n} years from {datetime}"))
}

/// Add `n` years to a given UNIX nanoseconds timestamp.
///
/// # Errors
///
/// Returns an error if the resulting timestamp is out of range or invalid.
pub fn add_n_years_nanos(unix_nanos: UnixNanos, n: u32) -> anyhow::Result<UnixNanos> {
    let datetime = unix_nanos.to_datetime_utc();
    let result = add_n_years(datetime, n)?;
    let timestamp = result.as_nanosecond();

    let nanos =
        u64::try_from(timestamp).map_err(|_| anyhow::anyhow!("Negative timestamp not allowed"))?;
    Ok(UnixNanos::from(nanos))
}

/// Subtract `n` years from a given UNIX nanoseconds timestamp.

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Check the resulting date stays above the timestamp minimum before subtracting; clamp the lookback.
  2. Reduce the year offset to keep the result representable.
  3. Propagate the Result instead of unwrapping, and log the offending datetime/n.

Example fix

// before
let start = subtract_n_years(end, lookback_years)?;
// after
let lookback_years = lookback_years.min(50);
let start = match subtract_n_years(end, lookback_years) {
    Ok(ts) => ts,
    Err(_) => end, // clamp to epoch-ish floor
};
Defensive patterns

Strategy: try-catch

Validate before calling

// ensure result stays above timestamp minimum
if datetime < jiff::Timestamp::MIN + jiff::Span::new().years(n as i32) { /* clamp */ }

Try / catch

let start = subtract_n_years(end, lookback).unwrap_or_else(|_| jiff::Timestamp::MIN);

Prevention

When it happens

Trigger: subtract_n_years(datetime, n) where datetime - n*12 months falls outside the valid Timestamp range (e.g. subtracting many years from a timestamp near the epoch), or yields an invalid calendar date.

Common situations: Backfill/lookback code subtracting large lookback periods from timestamps already near the epoch; tests exercising pre-epoch subtraction; bad config giving an oversized lookback.

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 nautechsystems/nautilus_trader@18893faf8b (2026-09-08). Data as JSON: /api/errors/4ab1932a68a2356a. Report an issue: GitHub.