nautechsystems/nautilus_trader · error

DateTime timestamp out of range for UnixNanos: {nanos}

Error message

DateTime timestamp out of range for UnixNanos: {nanos}

What it means

`try_datetime_to_unix_nanos` converts a DateTime into the library's `UnixNanos` (u64 nanoseconds since epoch). It rejects negative timestamps outright, and errors with this message when the nanosecond value is positive but exceeds what a u64 can represent (or otherwise cannot be converted from i64). The library throws it to prevent silently wrapping/overflowing the internal u64 timestamp representation.

Source

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

        .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;

    use super::*;

    fn timestamp(value: &str) -> Timestamp {

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Clamp or validate the DateTime to a sane range (e.g. before year 2554 / u64::MAX ns) before converting
  2. Check unit conversions: ensure you are not multiplying milliseconds or seconds by an extra 1e9 factor
  3. Use a smaller sentinel value (e.g. a few years in the future) for 'max' expiry semantics
  4. If you need beyond-u64-nanosecond ranges, handle the error and switch to a different representation

Example fix

// before
let nanos = try_datetime_to_unix_nanos(far_future_dt)?;
// after
let max_dt = DateTime::from_timestamp(9223372036, 0).unwrap(); // well within u64 ns
let nanos = try_datetime_to_unix_nanos(dt.min(max_dt))?;
Defensive patterns

Strategy: validation

Validate before calling

let nanos = value.as_nanosecond();
if nanos < 0 || nanos > i64::from(u64::MAX) {
    // clamp or reject before calling try_datetime_to_unix_nanos
}
let unix_nanos = try_datetime_to_unix_nanos(value)?;

Type guard

fn is_representable_as_unix_nanos(value: &DateTime) -> bool {
    let nanos = value.as_nanosecond();
    nanos >= 0 && u64::try_from(nanos).is_ok()
}

Try / catch

match try_datetime_to_unix_nanos(dt) {
    Ok(nanos) => use(nanos),
    Err(e) => tracing::error!("timestamp out of range: {e}"), // clamp/fallback
}

Prevention

When it happens

Trigger: Calling `try_datetime_to_unix_nanos` with a DateTime whose `as_nanosecond()` value is greater than u64::MAX nanoseconds (roughly year 2554). Negative timestamps get a different message, so this one specifically fires on far-future dates.

Common situations: Constructing expiry or alert times with an overly large year (e.g. misparsing a year like '99999' or unit confusion, milliseconds treated as nanoseconds multiplied incorrectly); using a far-future sentinel 'never expires' timestamp; passing a datetime built from unvalidated user input.

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