{"record":{"id":"19b40efca8284a16","repo":"nautechsystems/nautilus_trader","slug":"failed-to-add-n-months-to-datetime","errorCode":null,"errorMessage":"Failed to add {n} months to {datetime}","messagePattern":"Failed to add (.+?) months to (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/core/src/datetime.rs","lineNumber":568,"sourceCode":"\n/// Subtract `n` months 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_months(datetime: Timestamp, n: u32) -> anyhow::Result<Timestamp> {\n    shift_months(datetime, -i64::from(n))\n        .map_err(|_| anyhow::anyhow!(\"Failed to subtract {n} months from {datetime}\"))\n}\n\n/// Add `n` months 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_months(datetime: Timestamp, n: u32) -> anyhow::Result<Timestamp> {\n    shift_months(datetime, i64::from(n))\n        .map_err(|_| anyhow::anyhow!(\"Failed to add {n} months to {datetime}\"))\n}\n\n/// Subtract `n` months from 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 subtract_n_months_nanos(unix_nanos: UnixNanos, n: u32) -> anyhow::Result<UnixNanos> {\n    let datetime = unix_nanos.to_datetime_utc();\n    let result = subtract_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` months to a given UNIX nanoseconds timestamp.","sourceCodeStart":550,"sourceCodeEnd":586,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/18893faf8b356be3320add8de2f861b0b647cf06/crates/core/src/datetime.rs#L550-L586","documentation":"add_n_months shifts a jiff Timestamp forward by n months via shift_months and, on failure, raises \"Failed to add {n} months to {datetime}\". The library throws it when the resulting date is invalid (day-of-month has no counterpart in the target month) or out of jiff's representable range.","triggerScenarios":"Calling add_n_months(ts, n) from a month-end anchor like Jan 30/31 (Jan 31 + 1 month -> Feb 31), or from a timestamp near year 9999 where the shift overflows jiff's range; also via start_timer_internal and get_time_bar_start with large n.","commonSituations":"Setting monthly timer intervals anchored on the last day of a 31-day month; expiry-roll logic adding 1/3/6/12 months to month-end contract dates; misconfigured timer month counts (n in the thousands).","solutions":["Anchor timestamps to day <= 28 (or a month-end-aware rounding) before month arithmetic.","Validate that day-of-month exists in the target month and clamp, e.g. Feb 31 -> Feb 29, before calling.","Reduce or sanity-check n; unreasonably large month counts push the result out of range.","Use jiff's arithmetic directly if you need the underlying cause rather than the flattened message."],"exampleFix":"// before\nlet next = add_n_months(Timestamp::from_str(\"2024-01-31T00:00:00Z\")?, 1)?; // Feb 31 invalid\n// after\nlet next = add_n_months(Timestamp::from_str(\"2024-01-28T00:00:00Z\")?, 1)?; // safe anchor day","handlingStrategy":"validation","validationCode":"// Rust: verify the target month can hold the day before adding\nfn target_day_ok(day: i8, months_ahead: i64) -> bool {\n    // approximate: days 1-28 always exist in every month\n    (1..=28).contains(&day) || months_ahead % 12 != 1 // crude guard; clamp to 28 for safety\n}","typeGuard":null,"tryCatchPattern":"let next = add_n_months(datetime, n)\n    .with_context(|| format!(\"adding {n} months to {datetime}; avoid month-end anchors like Jan 31\"))?;","preventionTips":["Anchor timer/bar schedules on day <= 28 to keep month arithmetic total.","Sanity-check n from configuration; unbounded month counts overflow jiff's range."],"tags":["datetime","calendar-arithmetic","rust"],"backgroundTag":"invalid-date-format","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"}