nautechsystems/nautilus_trader · error

Failed to add {n} months to {datetime}

Error message

Failed to add {n} months to {datetime}

What it means

add_n_months shifts a jiff Timestamp forward by n months via shift_months and, on failure, raises "Failed to add {n} months to {datetime}". The library throws it when the resulting date is invalid (day-of-month has no counterpart in the target month) or out of jiff's representable range.

Source

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

/// 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> {
    let datetime = unix_nanos.to_datetime_utc();
    let result = subtract_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` months to a given UNIX nanoseconds timestamp.

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Anchor timestamps to day <= 28 (or a month-end-aware rounding) before month arithmetic.
  2. Validate that day-of-month exists in the target month and clamp, e.g. Feb 31 -> Feb 29, before calling.
  3. Reduce or sanity-check n; unreasonably large month counts push the result out of range.
  4. Use jiff's arithmetic directly if you need the underlying cause rather than the flattened message.

Example fix

// before
let next = add_n_months(Timestamp::from_str("2024-01-31T00:00:00Z")?, 1)?; // Feb 31 invalid
// after
let next = add_n_months(Timestamp::from_str("2024-01-28T00:00:00Z")?, 1)?; // safe anchor day
Defensive patterns

Strategy: validation

Validate before calling

// Rust: verify the target month can hold the day before adding
fn target_day_ok(day: i8, months_ahead: i64) -> bool {
    // approximate: days 1-28 always exist in every month
    (1..=28).contains(&day) || months_ahead % 12 != 1 // crude guard; clamp to 28 for safety
}

Try / catch

let next = add_n_months(datetime, n)
    .with_context(|| format!("adding {n} months to {datetime}; avoid month-end anchors like Jan 31"))?;

Prevention

When it happens

Trigger: Calling add_n_months(ts, n) from a month-end anchor like Jan 30/31 (Jan 31 + 1 month -> Feb 31), or from a timestamp near year 9999 where the shift overflows jiff's range; also via start_timer_internal and get_time_bar_start with large n.

Common situations: Setting monthly timer intervals anchored on the last day of a 31-day month; expiry-roll logic adding 1/3/6/12 months to month-end contract dates; misconfigured timer month counts (n in the thousands).

Related errors


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