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
- Validate/clamp the timestamp to >= 1970-01-01T00:00:00Z before parsing
- Fix the upstream data source emitting the pre-epoch value
- 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
- Clamp or reject zero/placeholder dates from venue payloads
- Check timezone handling when formatting timestamps
- Validate historical data timestamps before ingestion
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
- Timestamp out of range for candle at {}
- Failed to parse execution timestamp '{time_str}': {e}
- Invalid funding_interval, cannot be negative
- Invalid `UnixNanos` for 'start'
- Invalid `UnixNanos` for 'end'
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/96ed0ae899936e1d.
Report an issue: GitHub.