nautechsystems/nautilus_trader · error

Failed to subtract {n} months from {datetime}

Error message

Failed to subtract {n} months from {datetime}

What it means

subtract_n_months shifts a jiff Timestamp backwards by n months via shift_months and, if that fails, discards the specific cause and raises "Failed to subtract {n} months from {datetime}". The underlying failure is an invalid or out-of-range resulting date — e.g. Feb 31 does not exist — or exceeding jiff's representable timestamp range.

Source

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

    }

    Ok(now_ns - timestamp_ns <= NANOSECONDS_IN_DAY)
}

fn shift_months(datetime: Timestamp, months: i64) -> anyhow::Result<Timestamp> {
    let span = Span::new().try_months(months)?;
    let result = datetime.to_zoned(TimeZone::UTC).checked_add(span)?;
    Ok(result.timestamp())
}

/// Subtract `n` months from a Jiff [`Timestamp`].
///
/// # Errors
///
/// Returns an error if the resulting date would be invalid or out of range.
pub fn subtract_n_months(datetime: Timestamp, n: u32) -> anyhow::Result<Timestamp> {
    shift_months(datetime, -i64::from(n))
        .map_err(|_| anyhow::anyhow!("Failed to subtract {n} months from {datetime}"))
}

/// Add `n` months to a Jiff [`Timestamp`].
///
/// # Errors
///
/// Returns an error if the resulting date would be invalid or out of range.
pub fn add_n_months(datetime: Timestamp, n: u32) -> anyhow::Result<Timestamp> {
    shift_months(datetime, i64::from(n))
        .map_err(|_| anyhow::anyhow!("Failed to add {n} months to {datetime}"))
}

/// Subtract `n` months from a given UNIX nanoseconds timestamp.
///
/// # Errors
///
/// Returns an error if the resulting timestamp is out of range or invalid.
pub fn subtract_n_months_nanos(unix_nanos: UnixNanos, n: u32) -> anyhow::Result<UnixNanos> {

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Anchor the datetime to a safe day (e.g. day <= 28 or month end via jiff's rounding) before subtracting months.
  2. Check the input day-of-month against the target month's length and clamp before calling.
  3. If you need the cause, call the internal shift_months equivalent or jiff's arithmetic directly to get the detailed error instead of the flattened message.
  4. Bound iterative subtractions so the result stays within jiff's date range.

Example fix

// before
let prev = subtract_n_months(Timestamp::from_str("2024-03-31T00:00:00Z")?, 1)?; // Feb 31 invalid
// after
let ts = Timestamp::from_str("2024-03-31T00:00:00Z")?.round(Unit::Day, TimestampRound::new().smallest(Unit::Day))?;
// or subtract from a month-anchored (day <= 28) timestamp
let prev = subtract_n_months(Timestamp::from_str("2024-03-28T00:00:00Z")?, 1)?;
Defensive patterns

Strategy: try-catch

Validate before calling

// Rust: anchor to a safe day-of-month before month arithmetic
fn safe_anchor(ts: &Timestamp) -> anyhow::Result<Timestamp> {
    let dt = ts.to_zoned(jiff::tz::TimeZone::UTC)?;
    let day = dt.day().min(28);
    Ok(dt.with().day(day).build()?.timestamp())
}

Try / catch

let prev = subtract_n_months(datetime, n)
    .with_context(|| format!("subtracting {n} months from month-end timestamp {datetime}; anchor to day<=28 first"))?;

Prevention

When it happens

Trigger: Calling subtract_n_months(ts, n) where the source day-of-month has no counterpart in the target month (Jan 31 minus 1 month -> Feb 31), or where repeated subtraction leaves jiff's supported year range (~-9999..9999).

Common situations: Monthly bar/timer scheduling anchored on month-end timestamps (Jan 31, Mar 31, May 31); generating historical lookback windows from month-end data; long loops subtracting months until they underflow the range.

Related errors


AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08). Data as JSON: /api/errors/2509527897cf40ce. Report an issue: GitHub.