nautechsystems/nautilus_trader · error

Failed to add {n} years to {datetime}: month count overflow

Error message

Failed to add {n} years to {datetime}: month count overflow

What it means

add_n_years converts the requested year count into a total month count before shifting the timestamp. If n years exceeds what fits in u32 when multiplied by 12 (n > ~357 million), checked_mul fails and this error is returned instead of wrapping around silently. It guards against nonsensical year offsets that would corrupt date arithmetic.

Source

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

/// Returns an error if the resulting timestamp is out of range or invalid.
pub fn add_n_months_nanos(unix_nanos: UnixNanos, n: u32) -> anyhow::Result<UnixNanos> {
    let datetime = unix_nanos.to_datetime_utc();
    let result = add_n_months(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))
}

/// 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}"))

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Reduce the year offset n to a sane value (n <= 357,913,941; realistically <= a few thousand).
  2. Validate n at the API boundary before calling add_n_years, rejecting values above a practical maximum.
  3. If n comes from config or data, fix the source parsing so it cannot yield an unbounded u32.

Example fix

// before
let n: u32 = raw_value; // may be u32::MAX
let ts = add_n_years(ts, n)?;
// after
let n: u32 = raw_value;
anyhow::ensure!(n <= 100_000, "year offset too large: {n}");
let ts = add_n_years(ts, n)?;
Defensive patterns

Strategy: validation

Validate before calling

fn valid_year_offset(n: u32) -> bool { n <= 357_913_941 } // checked_mul(12) fits u32
// call: if !valid_year_offset(n) { return Err(...) }

Type guard

fn is_sane_years(n: u32) -> bool { n <= 10_000 }

Prevention

When it happens

Trigger: Calling add_n_years(datetime, n) with n > 357,913,941 so that n.checked_mul(12) overflows u32. Only reachable with absurdly large year counts; normal date offsets never hit it.

Common situations: Passing an uninitialized/garbage u32 (e.g. a value decoded from malformed config or bad data) as the year offset; unit tests exercising overflow; a deserialization bug feeding a huge n into time arithmetic in scheduling code such as start_timer_internal.

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/28989ea7a870bf60. Report an issue: GitHub.