nautechsystems/nautilus_trader · error

Negative nanosecond timestamp from: {timestamp}

Error message

Negative nanosecond timestamp from: {timestamp}

What it means

parse_rfc3339_timestamp parses an RFC 3339 datetime string to UnixNanos. If the parsed timestamp precedes the Unix epoch, the nanosecond count is negative and cannot be stored in the u64-backed UnixNanos, so the function bails with the offending string included in the message.

Source

Thrown at crates/adapters/okx/src/common/parse.rs:396

/// Converts a millisecond-based timestamp (as returned by OKX) into
/// [`UnixNanos`].
#[must_use]
pub fn parse_millisecond_timestamp(timestamp_ms: u64) -> UnixNanos {
    UnixNanos::from(timestamp_ms * NANOSECONDS_IN_MILLISECOND)
}

/// Parses an RFC 3339 timestamp string into [`UnixNanos`].
///
/// # Errors
///
/// Returns an error if the string is not a valid RFC 3339 datetime or if the
/// timestamp cannot be represented in nanoseconds.
pub fn parse_rfc3339_timestamp(timestamp: &str) -> anyhow::Result<UnixNanos> {
    let dt = timestamp.parse::<Timestamp>()?;
    let nanos = dt.as_nanosecond();
    if nanos < 0 {
        anyhow::bail!("Negative nanosecond timestamp from: {timestamp}");
    }
    let nanos = u64::try_from(nanos)
        .with_context(|| format!("Timestamp is outside the UnixNanos range: {timestamp}"))?;
    Ok(UnixNanos::from(nanos))
}

/// Converts a textual price to a [`Price`] using the given precision.
///
/// # Errors
///
/// Returns an error if the string fails to parse into `Decimal` or if the number
/// of decimal places exceeds `precision`.
pub fn parse_price(value: &str, precision: u8) -> anyhow::Result<Price> {
    let decimal = Decimal::from_str(value)?;
    Price::from_decimal_dp(decimal, precision).map_err(Into::into)
}

/// Converts a textual quantity to a [`Quantity`].

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Validate/clamp the timestamp to >= 1970-01-01T00:00:00Z before parsing
  2. Fix the upstream data source emitting the pre-epoch value
  3. Use a signed time representation if pre-epoch timestamps are legitimately required

Example fix

// before
let ts = parse_rfc3339_timestamp(raw)?;
// after
let dt = raw.parse::<Timestamp>()?;
if dt.as_nanosecond() < 0 { dt = EPOCH; } // clamp before parsing
let ts = parse_rfc3339_timestamp(raw)?;
Defensive patterns

Strategy: validation

Validate before calling

// Rust
fn is_epoch_or_later(rfc3339: &str) -> bool {
    rfc3339.parse::<time::OffsetDateTime>()
        .map(|dt| dt.unix_timestamp_nanos() >= 0)
        .unwrap_or(false)
}
assert!(is_epoch_or_later(ts_str));

Try / catch

// Rust
let ts = match parse_rfc3339_timestamp(raw) {
    Err(e) if e.to_string().contains("Negative nanosecond") => {
        warn!("pre-epoch timestamp {raw}; defaulting to epoch");
        UnixNanos::default()
    }
    other => other?,
};

Prevention

When it happens

Trigger: Passing a pre-1970 date string (e.g. '1969-12-31T23:59:59Z') to parse_rfc3339_timestamp, typically from exchange fields like creation or listing timestamps.

Common situations: Venue APIs returning zero or placeholder dates that get formatted as pre-epoch times; timezone bugs producing negative offsets before epoch; historical data loaders reading legacy records.

Related errors


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