{"record":{"id":"28989ea7a870bf60","repo":"nautechsystems/nautilus_trader","slug":"failed-to-add-n-years-to-datetime-month-count","errorCode":null,"errorMessage":"Failed to add {n} years to {datetime}: month count overflow","messagePattern":"Failed to add (.+?) years to (.+?): month count overflow","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/core/src/datetime.rs","lineNumber":608,"sourceCode":"/// Returns an error if the resulting timestamp is out of range or invalid.\npub fn add_n_months_nanos(unix_nanos: UnixNanos, n: u32) -> anyhow::Result<UnixNanos> {\n    let datetime = unix_nanos.to_datetime_utc();\n    let result = add_n_months(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/// Add `n` years to a Jiff [`Timestamp`].\n///\n/// # Errors\n///\n/// Returns an error if the resulting date would be invalid or out of range.\npub fn add_n_years(datetime: Timestamp, n: u32) -> anyhow::Result<Timestamp> {\n    let months = n.checked_mul(12).ok_or_else(|| {\n        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}\"))","sourceCodeStart":590,"sourceCodeEnd":626,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/18893faf8b356be3320add8de2f861b0b647cf06/crates/core/src/datetime.rs#L590-L626","documentation":"add_n_years converts the requested year count into a total month count before shifting the timestamp. If n years exceeds what fits in u32 when multiplied by 12 (n > ~357 million), checked_mul fails and this error is returned instead of wrapping around silently. It guards against nonsensical year offsets that would corrupt date arithmetic.","triggerScenarios":"Calling add_n_years(datetime, n) with n > 357,913,941 so that n.checked_mul(12) overflows u32. Only reachable with absurdly large year counts; normal date offsets never hit it.","commonSituations":"Passing an uninitialized/garbage u32 (e.g. a value decoded from malformed config or bad data) as the year offset; unit tests exercising overflow; a deserialization bug feeding a huge n into time arithmetic in scheduling code such as start_timer_internal.","solutions":["Reduce the year offset n to a sane value (n <= 357,913,941; realistically <= a few thousand).","Validate n at the API boundary before calling add_n_years, rejecting values above a practical maximum.","If n comes from config or data, fix the source parsing so it cannot yield an unbounded u32."],"exampleFix":"// before\nlet n: u32 = raw_value; // may be u32::MAX\nlet ts = add_n_years(ts, n)?;\n// after\nlet n: u32 = raw_value;\nanyhow::ensure!(n <= 100_000, \"year offset too large: {n}\");\nlet ts = add_n_years(ts, n)?;","handlingStrategy":"validation","validationCode":"fn valid_year_offset(n: u32) -> bool { n <= 357_913_941 } // checked_mul(12) fits u32\n// call: if !valid_year_offset(n) { return Err(...) }","typeGuard":"fn is_sane_years(n: u32) -> bool { n <= 10_000 }","tryCatchPattern":null,"preventionTips":["Validate year offsets at config/data boundaries with a practical maximum.","Never pass raw u32 values from deserialization straight into date 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-14T00:17:10.932Z"}