nautechsystems/nautilus_trader · error

Failed to add {n} years to {datetime}

Error message

Failed to add {n} years to {datetime}

What it means

After computing the month count, add_n_years delegates to shift_months. When the shifted date is invalid or overflows the Timestamp range (e.g. adding years pushes past year 9999 or the i64 nanosecond max), shift_months fails and this error wraps the failure with context. It means the requested addition cannot produce a representable valid timestamp.

Source

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

    let timestamp = result.as_nanosecond();

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

/// Add `n` years to a Jiff [`Timestamp`].
///
/// # Errors
///
/// Returns an error if the resulting date would be invalid or out of range.
pub fn add_n_years(datetime: Timestamp, n: u32) -> anyhow::Result<Timestamp> {
    let months = n.checked_mul(12).ok_or_else(|| {
        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.
///

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Check the target date stays in range before adding: compare datetime with the max supported timestamp minus n years.
  2. Reduce the year offset or clamp the result to a maximum representable timestamp.
  3. Handle the Result with context rather than unwrapping; log the datetime and n to identify the bad input.

Example fix

// before
let expiry = add_n_years(now, years).unwrap();
// after
let expiry = add_n_years(now, years)
    .unwrap_or_else(|_| add_n_years(now, 10_000.min(years)).expect("clamped range"));
Defensive patterns

Strategy: try-catch

Validate before calling

// pre-check: target must stay in range
let max = jiff::Timestamp::MAX;
if datetime + jiff::Span::new().years(n as i32) > max { /* clamp or reject */ }

Try / catch

match add_n_years(datetime, n) {
    Ok(ts) => ts,
    Err(e) => { log::warn!("year shift out of range: {e}"); clamp_to_max(datetime) }
}

Prevention

When it happens

Trigger: add_n_years(datetime, n) where datetime + n*12 months lands outside the representable Timestamp range (beyond ~year 9999 / i64 nanosecond bounds) or produces an invalid calendar date.

Common situations: Clamping or extrapolating expiry/schedule dates from bad data; adding years to a timestamp already near the max; tests verifying overflow behavior; timer setup (start_timer_internal) with a far-future offset.

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/385f54b5c0874e94. Report an issue: GitHub.