{"record":{"id":"c5dcee0cc0e49916","repo":"nautechsystems/nautilus_trader","slug":"milliseconds-must-be-finite-was-millis","errorCode":null,"errorMessage":"milliseconds must be finite, was {millis}","messagePattern":"milliseconds must be finite, was (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/core/src/datetime.rs","lineNumber":311,"sourceCode":"    mins.checked_mul(SECONDS_IN_MINUTE)\n}\n\n/// Converts milliseconds (ms) to nanoseconds (ns).\n///\n/// Casting f64 to u64 by truncating the fractional part is intentional for unit conversion,\n/// which may lose precision and drop negative values after clamping.\n///\n/// # Errors\n///\n/// Returns an error if `millis` is non-finite or cannot be represented as `u64` nanoseconds.\n#[expect(\n    clippy::cast_possible_truncation,\n    clippy::cast_sign_loss,\n    clippy::cast_precision_loss,\n    reason = \"Intentional for unit conversion, may lose precision after clamping\"\n)]\npub fn millis_to_nanos(millis: f64) -> anyhow::Result<u64> {\n    anyhow::ensure!(\n        millis.is_finite(),\n        \"milliseconds must be finite, was {millis}\"\n    );\n\n    if millis <= 0.0 {\n        return Ok(0);\n    }\n    let nanos = millis * NANOSECONDS_IN_MILLISECOND as f64;\n    anyhow::ensure!(\n        nanos < U64_UPPER_BOUND_F64,\n        \"milliseconds {millis} is out of range for `u64` nanoseconds\"\n    );\n    Ok(nanos.trunc() as u64)\n}\n\n/// Converts milliseconds (ms) to nanoseconds (ns), panicking on invalid input.\n///\n/// # Panics","sourceCodeStart":293,"sourceCodeEnd":329,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/18893faf8b356be3320add8de2f861b0b647cf06/crates/core/src/datetime.rs#L293-L329","documentation":"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.","triggerScenarios":"Calling millis_to_nanos(f64::NAN) or millis_to_nanos(±f64::INFINITY).","commonSituations":"NaN from dividing interval values by zero; infinity from unchecked accumulation of latency measurements; placeholder values in market-data feeds marked missing as inf.","solutions":["Check millis.is_finite() before calling and handle invalid values at the call site.","Fix the upstream computation producing NaN/inf.","Represent unknown durations as Option<u64>/Option<f64> instead of NaN/inf sentinels."],"exampleFix":"// before\nlet ns = millis_to_nanos(latency_ms)?;\n// after\nanyhow::ensure!(latency_ms.is_finite(), \"latency_ms not finite: {latency_ms}\");\nlet ns = millis_to_nanos(latency_ms)?;","handlingStrategy":"validation","validationCode":"if !millis.is_finite() {\n    return Err(format!(\"milliseconds not finite: {millis}\"));\n}","typeGuard":"fn is_convertible_millis(x: f64) -> bool { x.is_finite() && x > 0.0 && x < 1.8446744e13 }","tryCatchPattern":"let ns = match millis_to_nanos(millis) {\n    Ok(ns) => ns,\n    Err(e) if e.to_string().contains(\"must be finite\") => { log::warn!(\"non-finite millis input\"); 0 },\n    Err(e) => return Err(e),\n};","preventionTips":["Filter NaN/inf latency or interval measurements before conversion.","Use Option<u64> for missing durations instead of NaN/inf floats.","Add property tests covering non-finite inputs to conversion helpers."],"tags":["rust","datetime","floating-point","validation"],"backgroundTag":"invalid-argument-value","analyzedSha":"18893faf8b356be3320add8de2f861b0b647cf06","analyzedAt":"2026-09-08T20:49:34.690Z","contentChangedAt":"2026-09-08T20:49:34.690Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}