nautechsystems/nautilus_trader · error

DateTime timestamp cannot be negative: {nanos}

Error message

DateTime timestamp cannot be negative: {nanos}

What it means

try_datetime_to_unix_nanos converts a chrono Timestamp to UnixNanos, which is an unsigned nanosecond count since the UNIX epoch. Timestamps before 1970-01-01 yield a negative nanosecond count and are rejected with this error. The library throws it because UnixNanos cannot represent pre-epoch times.

Source

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

pub fn datetime_to_unix_nanos(value: Option<Timestamp>) -> Option<UnixNanos> {
    value
        .map(Timestamp::as_nanosecond)
        .and_then(|nanos| u64::try_from(nanos).ok())
        .map(UnixNanos::from)
}

/// Converts a `Timestamp` to `UnixNanos`.
///
/// Unlike `UnixNanos::from(Timestamp)` which panics, this returns an error.
///
/// # Errors
///
/// Returns an error if the timestamp is before the UNIX epoch or out of range for `UnixNanos`.
pub fn try_datetime_to_unix_nanos(value: Timestamp) -> anyhow::Result<UnixNanos> {
    let nanos = value.as_nanosecond();

    if nanos < 0 {
        anyhow::bail!("DateTime timestamp cannot be negative: {nanos}");
    }
    let nanos = u64::try_from(nanos)
        .map_err(|_| anyhow::anyhow!("DateTime timestamp out of range for UnixNanos: {nanos}"))?;

    Ok(UnixNanos::from(nanos))
}

#[cfg(test)]
// `allow` not `expect`: nightly clippy does not fire `float_cmp` inside `assert_eq!`
#[allow(
    clippy::float_cmp,
    reason = "Exact float comparisons acceptable in tests"
)]
mod tests {
    use jiff::SignedDuration;
    use proptest::prelude::*;
    use rstest::rstest;

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Clamp or reject pre-epoch datetimes at the caller: return an error or substitute the epoch when nanos < 0.
  2. Fix the parsing/timezone bug that produced the pre-epoch timestamp (check tz offsets around 1970-01-01).
  3. Use try_datetime_to_unix_nanos in a match/Result context and handle the Err branch instead of unwrapping.

Example fix

// before
let ts = try_datetime_to_unix_nanos(value).unwrap();
// after
let ts = match try_datetime_to_unix_nanos(value) {
    Ok(ts) => ts,
    Err(e) => { log::warn!("skipping pre-epoch timestamp: {e}"); return Ok(()); }
};
Defensive patterns

Strategy: try-catch

Validate before calling

if dt.timestamp() < 0:
    raise ValueError(f"datetime {dt} is before the UNIX epoch and cannot be a UnixNanos timestamp")

Type guard

def is_post_epoch(dt) -> bool:
    return dt.timestamp() >= 0

Try / catch

match try_datetime_to_unix_nanos(value) {
    Ok(nanos) => use(nanos),
    Err(e) => log::warn!("invalid timestamp {value}: {e}"),
}

Prevention

When it happens

Trigger: Calling try_datetime_to_unix_nanos with a datetime earlier than 1970-01-01T00:00:00Z — e.g. from set_time_alert with a pre-epoch alert time, or parsing historical timestamps with negative epoch offsets.

Common situations: Historical market data with dates mis-parsed (e.g. year 1969 from a two-digit-year format or timezone shifting a 1970-01-01T00:00 boundary to the previous day); config files containing 0/epoch-like values interpreted in a west-of-UTC timezone making them negative.

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