{"record":{"id":"4ab1932a68a2356a","repo":"nautechsystems/nautilus_trader","slug":"failed-to-subtract-n-years-from-datetime","errorCode":null,"errorMessage":"Failed to subtract {n} years from {datetime}","messagePattern":"Failed to subtract (.+?) years from (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/core/src/datetime.rs","lineNumber":626,"sourceCode":"        anyhow::anyhow!(\"Failed to add {n} years to {datetime}: month count overflow\")\n    })?;\n\n    shift_months(datetime, i64::from(months))\n        .map_err(|_| anyhow::anyhow!(\"Failed to add {n} years to {datetime}\"))\n}\n\n/// Subtract `n` years from a Jiff [`Timestamp`].\n///\n/// # Errors\n///\n/// Returns an error if the resulting date would be invalid or out of range.\npub fn subtract_n_years(datetime: Timestamp, n: u32) -> anyhow::Result<Timestamp> {\n    let months = n.checked_mul(12).ok_or_else(|| {\n        anyhow::anyhow!(\"Failed to subtract {n} years from {datetime}: month count overflow\")\n    })?;\n\n    shift_months(datetime, -i64::from(months))\n        .map_err(|_| anyhow::anyhow!(\"Failed to subtract {n} years from {datetime}\"))\n}\n\n/// Add `n` years to a given UNIX nanoseconds timestamp.\n///\n/// # Errors\n///\n/// Returns an error if the resulting timestamp is out of range or invalid.\npub fn add_n_years_nanos(unix_nanos: UnixNanos, n: u32) -> anyhow::Result<UnixNanos> {\n    let datetime = unix_nanos.to_datetime_utc();\n    let result = add_n_years(datetime, n)?;\n    let timestamp = result.as_nanosecond();\n\n    let nanos =\n        u64::try_from(timestamp).map_err(|_| anyhow::anyhow!(\"Negative timestamp not allowed\"))?;\n    Ok(UnixNanos::from(nanos))\n}\n\n/// Subtract `n` years from a given UNIX nanoseconds timestamp.","sourceCodeStart":608,"sourceCodeEnd":644,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/18893faf8b356be3320add8de2f861b0b647cf06/crates/core/src/datetime.rs#L608-L644","documentation":"subtract_n_years delegates to shift_months with a negative month delta; if the resulting date would be invalid or precedes the representable Timestamp minimum (before the UNIX epoch / i64 nanosecond floor), shift_months fails and this contextual error is returned. It signals the requested subtraction is not representable.","triggerScenarios":"subtract_n_years(datetime, n) where datetime - n*12 months falls outside the valid Timestamp range (e.g. subtracting many years from a timestamp near the epoch), or yields an invalid calendar date.","commonSituations":"Backfill/lookback code subtracting large lookback periods from timestamps already near the epoch; tests exercising pre-epoch subtraction; bad config giving an oversized lookback.","solutions":["Check the resulting date stays above the timestamp minimum before subtracting; clamp the lookback.","Reduce the year offset to keep the result representable.","Propagate the Result instead of unwrapping, and log the offending datetime/n."],"exampleFix":"// before\nlet start = subtract_n_years(end, lookback_years)?;\n// after\nlet lookback_years = lookback_years.min(50);\nlet start = match subtract_n_years(end, lookback_years) {\n    Ok(ts) => ts,\n    Err(_) => end, // clamp to epoch-ish floor\n};","handlingStrategy":"try-catch","validationCode":"// ensure result stays above timestamp minimum\nif datetime < jiff::Timestamp::MIN + jiff::Span::new().years(n as i32) { /* clamp */ }","typeGuard":null,"tryCatchPattern":"let start = subtract_n_years(end, lookback).unwrap_or_else(|_| jiff::Timestamp::MIN);","preventionTips":["Clamp lookbacks near the epoch to a safe floor.","Verify timestamps are in nanoseconds before multi-year arithmetic."],"tags":["datetime","overflow","rust"],"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"}