nautechsystems/nautilus_trader · error

milliseconds must be finite, was {millis}

Error message

milliseconds must be finite, was {millis}

What it means

millis_to_nanos received a non-finite f64 (NaN or infinity); such a value has no nanosecond representation after clamping and casting, so the conversion rejects it before producing a meaningless timestamp.

Source

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

    mins.checked_mul(SECONDS_IN_MINUTE)
}

/// Converts milliseconds (ms) to nanoseconds (ns).
///
/// Casting f64 to u64 by truncating the fractional part is intentional for unit conversion,
/// which may lose precision and drop negative values after clamping.
///
/// # Errors
///
/// Returns an error if `millis` is non-finite or cannot be represented as `u64` nanoseconds.
#[expect(
    clippy::cast_possible_truncation,
    clippy::cast_sign_loss,
    clippy::cast_precision_loss,
    reason = "Intentional for unit conversion, may lose precision after clamping"
)]
pub fn millis_to_nanos(millis: f64) -> anyhow::Result<u64> {
    anyhow::ensure!(
        millis.is_finite(),
        "milliseconds must be finite, was {millis}"
    );

    if millis <= 0.0 {
        return Ok(0);
    }
    let nanos = millis * NANOSECONDS_IN_MILLISECOND as f64;
    anyhow::ensure!(
        nanos < U64_UPPER_BOUND_F64,
        "milliseconds {millis} is out of range for `u64` nanoseconds"
    );
    Ok(nanos.trunc() as u64)
}

/// Converts milliseconds (ms) to nanoseconds (ns), panicking on invalid input.
///
/// # Panics

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Check millis.is_finite() before calling and handle invalid values at the call site.
  2. Fix the upstream computation producing NaN/inf.
  3. Represent unknown durations as Option<u64>/Option<f64> instead of NaN/inf sentinels.

Example fix

// before
let ns = millis_to_nanos(latency_ms)?;
// after
anyhow::ensure!(latency_ms.is_finite(), "latency_ms not finite: {latency_ms}");
let ns = millis_to_nanos(latency_ms)?;
Defensive patterns

Strategy: validation

Validate before calling

if !millis.is_finite() {
    return Err(format!("milliseconds not finite: {millis}"));
}

Type guard

fn is_convertible_millis(x: f64) -> bool { x.is_finite() && x > 0.0 && x < 1.8446744e13 }

Try / catch

let ns = match millis_to_nanos(millis) {
    Ok(ns) => ns,
    Err(e) if e.to_string().contains("must be finite") => { log::warn!("non-finite millis input"); 0 },
    Err(e) => return Err(e),
};

Prevention

When it happens

Trigger: Calling millis_to_nanos(f64::NAN) or millis_to_nanos(±f64::INFINITY).

Common situations: NaN from dividing interval values by zero; infinity from unchecked accumulation of latency measurements; placeholder values in market-data feeds marked missing as inf.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


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