{"record":{"id":"2509527897cf40ce","repo":"nautechsystems/nautilus_trader","slug":"failed-to-subtract-n-months-from-datetime","errorCode":null,"errorMessage":"Failed to subtract {n} months from {datetime}","messagePattern":"Failed to subtract (.+?) months from (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/core/src/datetime.rs","lineNumber":558,"sourceCode":"    }\n\n    Ok(now_ns - timestamp_ns <= NANOSECONDS_IN_DAY)\n}\n\nfn shift_months(datetime: Timestamp, months: i64) -> anyhow::Result<Timestamp> {\n    let span = Span::new().try_months(months)?;\n    let result = datetime.to_zoned(TimeZone::UTC).checked_add(span)?;\n    Ok(result.timestamp())\n}\n\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> {","sourceCodeStart":540,"sourceCodeEnd":576,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/18893faf8b356be3320add8de2f861b0b647cf06/crates/core/src/datetime.rs#L540-L576","documentation":"subtract_n_months shifts a jiff Timestamp backwards by n months via shift_months and, if that fails, discards the specific cause and raises \"Failed to subtract {n} months from {datetime}\". The underlying failure is an invalid or out-of-range resulting date — e.g. Feb 31 does not exist — or exceeding jiff's representable timestamp range.","triggerScenarios":"Calling subtract_n_months(ts, n) where the source day-of-month has no counterpart in the target month (Jan 31 minus 1 month -> Feb 31), or where repeated subtraction leaves jiff's supported year range (~-9999..9999).","commonSituations":"Monthly bar/timer scheduling anchored on month-end timestamps (Jan 31, Mar 31, May 31); generating historical lookback windows from month-end data; long loops subtracting months until they underflow the range.","solutions":["Anchor the datetime to a safe day (e.g. day <= 28 or month end via jiff's rounding) before subtracting months.","Check the input day-of-month against the target month's length and clamp before calling.","If you need the cause, call the internal shift_months equivalent or jiff's arithmetic directly to get the detailed error instead of the flattened message.","Bound iterative subtractions so the result stays within jiff's date range."],"exampleFix":"// before\nlet prev = subtract_n_months(Timestamp::from_str(\"2024-03-31T00:00:00Z\")?, 1)?; // Feb 31 invalid\n// after\nlet ts = Timestamp::from_str(\"2024-03-31T00:00:00Z\")?.round(Unit::Day, TimestampRound::new().smallest(Unit::Day))?;\n// or subtract from a month-anchored (day <= 28) timestamp\nlet prev = subtract_n_months(Timestamp::from_str(\"2024-03-28T00:00:00Z\")?, 1)?;","handlingStrategy":"try-catch","validationCode":"// Rust: anchor to a safe day-of-month before month arithmetic\nfn safe_anchor(ts: &Timestamp) -> anyhow::Result<Timestamp> {\n    let dt = ts.to_zoned(jiff::tz::TimeZone::UTC)?;\n    let day = dt.day().min(28);\n    Ok(dt.with().day(day).build()?.timestamp())\n}","typeGuard":null,"tryCatchPattern":"let prev = subtract_n_months(datetime, n)\n    .with_context(|| format!(\"subtracting {n} months from month-end timestamp {datetime}; anchor to day<=28 first\"))?;","preventionTips":["Never do month arithmetic anchored on day 29-31; clamp to 28 or use month-end-aware rounding.","Bound iterative lookback loops so results stay in jiff's supported 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"}