nautechsystems/nautilus_trader · error

Negative timestamp not allowed

Error message

Negative timestamp not allowed

What it means

subtract_n_months_nanos converts the result back to u64 nanoseconds for UnixNanos; "Negative timestamp not allowed" means the shifted timestamp is before the UNIX epoch (negative i128/i64 nanos), which the unsigned UnixNanos type cannot hold. The library throws it because nautilus timestamps are non-negative by definition.

Source

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

///
/// 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.
///
/// # Errors
///
/// 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))
}

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Check before calling that unix_nanos is large enough: elapsed nanos since epoch >= n months of nanoseconds.
  2. Clamp the result to 0 (epoch) if a pre-epoch floor is acceptable for your logic.
  3. Reduce n or use a different anchoring timestamp further from the epoch.
  4. If pre-epoch values are legitimate for your use case, use jiff Timestamp/i64 arithmetic instead of UnixNanos-based APIs.

Example fix

// before
let anchored = subtract_n_months_nanos(unix_nanos, n)?; // may go pre-epoch
// after
let months_ns: u64 = n as u64 * 30 * 24 * 3600 * 1_000_000_000;
let anchored = if unix_nanos.as_u64() > months_ns {
    subtract_n_months_nanos(unix_nanos, n)?
} else {
    UnixNanos::from(0u64)
};
Defensive patterns

Strategy: validation

Validate before calling

// Rust: ensure the lookback cannot cross the epoch
const NS_PER_MONTH: u64 = 30 * 24 * 3600 * 1_000_000_000;
fn can_subtract(unix_nanos: UnixNanos, n: u32) -> bool {
    unix_nanos.as_u64() > n as u64 * NS_PER_MONTH
}

Try / catch

match subtract_n_months_nanos(unix_nanos, n) {
    Ok(ns) => ns,
    Err(_) => UnixNanos::from(0u64), // clamp to epoch as floor
}

Prevention

When it happens

Trigger: Calling subtract_n_months_nanos(ts, n) where ts is within n months after 1970-01-01, e.g. subtract_n_months_nanos(UnixNanos::from(1970-06-15), 12) or any timer start near the epoch with a large lookback; also from start_timer_internal with a too-large n.

Common situations: Historical backtests replaying data very close to the epoch; subtracting a lookback interval larger than the elapsed time since 1970; misconfigured lookback months (n) in timers.

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