nautechsystems/nautilus_trader · error

Failed to subtract months_step

Error message

Failed to subtract months_step

What it means

The final subtraction of months_step to return the aligned bar start fails (subtract_n_months returns Err) and the expect panics. After a successful forward loop this should not normally fail, but an origin offset near the lower boundary of jiff's range can push the subtract below the representable minimum.

Solutions

  1. Ensure `now` and the resulting bar start stay well within jiff's supported range (roughly year -9999 to 9999).
  2. Reduce time_bars_origin magnitude in the bar type configuration.
  3. Clamp inputs to a safe window (e.g. 1900-2200) before calling.
  4. File an issue with reproducing inputs if normal dates trigger it.

Example fix

// before
let start = get_time_bar_start(Timestamp::from_unix_nanos(-9_000_000_000_000_000_000), &bar_type, origin);
// after
let clamped = Timestamp::from_unix_nanos(now_ns.max(0));
let start = get_time_bar_start(clamped, &bar_type, origin);
Defensive patterns

Strategy: validation

Validate before calling

if now.as_unix_nanos() < 0 {
    return Err("pre-epoch timestamps unsupported for month bars".into());
}
let start = get_time_bar_start(now, &bar_type, origin);

Type guard

fn post_epoch(ts: Timestamp) -> bool { ts.as_unix_nanos() >= 0 }

Try / catch

let start = std::panic::catch_unwind(|| get_time_bar_start(now, &bar_type, origin))
    .map_err(|_| anyhow::anyhow!("month bar start subtraction underflowed"))?;

Prevention

When it happens

Trigger: get_time_bar_start with a Month bar where the loop result sits exactly one months_step above the minimum representable timestamp — e.g. `now` near the epoch lower bound with a large months_step or large negative origin offset.

Common situations: Negative/before-1970 timestamps in backtests or data imports; extremely large origin offset in the bar spec; corrupted timestamp data.

Understand the failure class

Background: "value must be between 0 and 1" / "out of range" / "must not be negative" errors: fixing range-validation failures across open-source libraries — this error's family across 42 libraries.

Related errors


AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08). Data as JSON: /api/errors/af608b80bc4bf967. Report an issue: GitHub.

Appendix: source

Thrown at crates/model/src/data/bar.rs:282

                )
                .expect("valid UTC year start");
            start_time += origin_offset;

            if now < start_time {
                start_time =
                    subtract_n_months(start_time, 12).expect("Failed to subtract 12 months");
            }

            let months_step =
                u32::try_from(step).expect("`step` exceeds u32 range for month arithmetic");

            while start_time <= now {
                start_time =
                    add_n_months(start_time, months_step).expect("Failed to add months in loop");
            }

            start_time =
                subtract_n_months(start_time, months_step).expect("Failed to subtract months_step");
            start_time
        }
        BarAggregation::Year => {
            let step_i32 =
                i32::try_from(step).expect("`step` exceeds i32 range for year arithmetic");

            // Reconstruct from Jan 1 + origin each time to avoid leap-day drift
            let year_start = |year: i32| {
                let year = i16::try_from(year).expect("year exceeds Jiff supported range");
                Offset::UTC
                    .to_timestamp(
                        Date::new(year, 1, 1)
                            .expect("valid year start date")
                            .at(0, 0, 0, 0),
                    )
                    .expect("valid UTC year start")
                    + origin_offset
            };

View on GitHub (pinned to 18893faf8b)