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
- Anchor the datetime to a safe day (e.g. day <= 28 or month end via jiff's rounding) before subtracting months.
- Check the input day-of-month against the target month's length and clamp before calling.
- 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.
- 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
- Never do month arithmetic anchored on day 29-31; clamp to 28 or use month-end-aware rounding.
- Bound iterative lookback loops so results stay in jiff's supported range.
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
- Failed to add {n} months to {datetime}
- DateTime timestamp out of range for UnixNanos: {nanos}
- seconds must be finite, was {secs}
- seconds {secs} is out of range for `u64` nanoseconds
- seconds {secs} is out of range for `u64` milliseconds
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/2509527897cf40ce.
Report an issue: GitHub.