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
- Clamp or reject pre-epoch datetimes at the caller: return an error or substitute the epoch when nanos < 0.
- Fix the parsing/timezone bug that produced the pre-epoch timestamp (check tz offsets around 1970-01-01).
- 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
- Reject or clamp pre-epoch datetimes at ingestion boundaries.
- Check timezone handling around 1970-01-01 boundaries when parsing historical data.
- Always consume the Result of try_datetime_to_unix_nanos instead of unwrapping.
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
- Invalid {label} '{value}': {e}
- seconds must be finite, was {secs}
- milliseconds must be finite, was {millis}
- microseconds must be finite, was {micros}
- Unsupported `OrderSide` for Binance: {value:?}
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/4302ae9aaf0cf10a.
Report an issue: GitHub.