nautechsystems/nautilus_trader · error

millisecond timestamp {millis} overflows when scaled to nano

Error message

millisecond timestamp {millis} overflows when scaled to nanoseconds

What it means

parse_millis_to_nanos scales a millisecond timestamp to nanoseconds using checked multiplication. If millis * 1_000_000 would overflow u64 the conversion is rejected rather than silently wrapping in release builds. Real venue timestamps are nowhere near this bound, so this almost always indicates malformed input.

Source

Thrown at crates/adapters/lighter/src/common/parse.rs:173

        "size precision {precision} exceeds maximum {MAX_DECIMALS}",
    );
    anyhow::ensure!(value.is_sign_positive(), "negative quantity `{value}`");
    Quantity::from_decimal_dp(value, precision)
        .map_err(|e| anyhow::anyhow!("invalid quantity `{value}` at precision {precision}: {e}"))
}

/// Converts a Unix millisecond timestamp into [`UnixNanos`].
///
/// # Errors
///
/// Returns an error if `millis * 1_000_000` would overflow `u64`. Realistic
/// venue timestamps are nowhere near this bound; the check rejects malformed
/// payloads instead of silently wrapping in release builds.
pub fn parse_millis_to_nanos(millis: u64) -> anyhow::Result<UnixNanos> {
    let nanos = millis
        .checked_mul(NANOSECONDS_IN_MILLISECOND)
        .ok_or_else(|| {
            anyhow::anyhow!("millisecond timestamp {millis} overflows when scaled to nanoseconds")
        })?;
    Ok(UnixNanos::from(nanos))
}

/// Converts a Unix microsecond timestamp into [`UnixNanos`].
///
/// # Errors
///
/// Returns an error if `micros * 1_000` would overflow `u64`.
pub fn parse_micros_to_nanos(micros: u64) -> anyhow::Result<UnixNanos> {
    let nanos = micros.checked_mul(1_000).ok_or_else(|| {
        anyhow::anyhow!("microsecond timestamp {micros} overflows when scaled to nanoseconds")
    })?;
    Ok(UnixNanos::from(nanos))
}

/// Converts a Unix second timestamp into [`UnixNanos`].
///

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Verify the timestamp field is actually in milliseconds; if the venue sends microseconds/nanoseconds use parse_micros_to_nanos / parse_secs_to_nanos instead.
  2. Inspect the raw payload for the offending value; treat it as a malformed message and drop/resynchronize.
  3. If intentionally probing limits, keep values below ~1.8e13 milliseconds.

Example fix

// before
let ts = parse_millis_to_nanos(venue_nanos_field)?; // field is actually nanos
// after
let ts = UnixNanos::from(venue_nanos_field); // already nanoseconds — no scaling
Defensive patterns

Strategy: validation

Validate before calling

const MAX_MS: u64 = u64::MAX / 1_000_000; // ~1.8e13
fn millis_plausible(ms: u64) -> bool { ms < MAX_MS && ms > 1_400_000_000_000 }

Type guard

fn is_millis(v: u64) -> bool { (1_400_000_000_000..u64::MAX / 1_000_000).contains(&v) }

Try / catch

let ts = parse_millis_to_nanos(millis)
    .or_else(|_| parse_micros_to_nanos(millis).map(|_| unreachable))
    .unwrap_or_else(|_| { warn!("bad timestamp {millis}"); UnixNanos::default() });

Prevention

When it happens

Trigger: Calling parse_millis_to_nanos or its callers (parse_optional_millis_to_nanos, parse_trade_tick, parse_candle_bar, parse_ws_order_book_deltas, parse_ws_order_book_depth10) with a millis value greater than u64::MAX / 1_000_000 (~1.8e13 ms, year ~2262+) — e.g. a garbage or corrupted timestamp field in a venue payload.

Common situations: Corrupted websocket frames; a venue field misinterpreted (e.g. micros or nanos passed where millis expected, inflating the number); unit tests probing overflow behavior (see parse_millis_to_nanos_rejects_overflow).

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/82204404b25e40c4. Report an issue: GitHub.