{"record":{"id":"4302ae9aaf0cf10a","repo":"nautechsystems/nautilus_trader","slug":"datetime-timestamp-cannot-be-negative-nanos","errorCode":null,"errorMessage":"DateTime timestamp cannot be negative: {nanos}","messagePattern":"DateTime timestamp cannot be negative: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/core/src/datetime.rs","lineNumber":678,"sourceCode":"pub fn datetime_to_unix_nanos(value: Option<Timestamp>) -> Option<UnixNanos> {\n    value\n        .map(Timestamp::as_nanosecond)\n        .and_then(|nanos| u64::try_from(nanos).ok())\n        .map(UnixNanos::from)\n}\n\n/// Converts a `Timestamp` to `UnixNanos`.\n///\n/// Unlike `UnixNanos::from(Timestamp)` which panics, this returns an error.\n///\n/// # Errors\n///\n/// Returns an error if the timestamp is before the UNIX epoch or out of range for `UnixNanos`.\npub fn try_datetime_to_unix_nanos(value: Timestamp) -> anyhow::Result<UnixNanos> {\n    let nanos = value.as_nanosecond();\n\n    if nanos < 0 {\n        anyhow::bail!(\"DateTime timestamp cannot be negative: {nanos}\");\n    }\n    let nanos = u64::try_from(nanos)\n        .map_err(|_| anyhow::anyhow!(\"DateTime timestamp out of range for UnixNanos: {nanos}\"))?;\n\n    Ok(UnixNanos::from(nanos))\n}\n\n#[cfg(test)]\n// `allow` not `expect`: nightly clippy does not fire `float_cmp` inside `assert_eq!`\n#[allow(\n    clippy::float_cmp,\n    reason = \"Exact float comparisons acceptable in tests\"\n)]\nmod tests {\n    use jiff::SignedDuration;\n    use proptest::prelude::*;\n    use rstest::rstest;\n","sourceCodeStart":660,"sourceCodeEnd":696,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/18893faf8b356be3320add8de2f861b0b647cf06/crates/core/src/datetime.rs#L660-L696","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nlet ts = try_datetime_to_unix_nanos(value).unwrap();\n// after\nlet ts = match try_datetime_to_unix_nanos(value) {\n    Ok(ts) => ts,\n    Err(e) => { log::warn!(\"skipping pre-epoch timestamp: {e}\"); return Ok(()); }\n};","handlingStrategy":"try-catch","validationCode":"if dt.timestamp() < 0:\n    raise ValueError(f\"datetime {dt} is before the UNIX epoch and cannot be a UnixNanos timestamp\")","typeGuard":"def is_post_epoch(dt) -> bool:\n    return dt.timestamp() >= 0","tryCatchPattern":"match try_datetime_to_unix_nanos(value) {\n    Ok(nanos) => use(nanos),\n    Err(e) => log::warn!(\"invalid timestamp {value}: {e}\"),\n}","preventionTips":["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."],"tags":["datetime","validation","unix-nanos","out-of-range"],"backgroundTag":"value-out-of-range","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"}