nautechsystems/nautilus_trader · error

Failed to add months in loop

Error message

Failed to add months in loop

What it means

After stepping the Month anchor forward by months_step until it passes `now`, the function subtracts months_step once to land on the last aligned start. If add_n_months returns Err during the loop (timestamp pushed beyond jiff's supported range), the expect panics. This is effectively a timestamp-range overflow, not a normal business condition.

Source

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

                .to_timestamp(
                    Date::new(now_civil.year(), 1, 1)
                        .expect("valid year start date")
                        .at(0, 0, 0, 0),
                )
                .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),

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Sanity-check the `now` Timestamp; it should be a realistic current time.
  2. Reduce the Month step to a sane value (e.g. < 1200 months) before constructing the BarType.
  3. If synthetic/backtesting time is used, keep it within jiff's supported year range.
  4. Report an issue if it reproduces with normal inputs.

Example fix

// before
get_time_bar_start(now_ns_far_future, &month_bar_type, None)
// after
assert!(now.as_unix_nanos() > 0 && now.as_unix_nanos() < 4_102_444_800_000_000_000); // year 2100
get_time_bar_start(now, &month_bar_type, None)
Defensive patterns

Strategy: validation

Validate before calling

if now.as_unix_nanos() <= 0 || now.as_unix_nanos() >= 4_102_444_800_000_000_000 {
    return Err("timestamp outside sane range (1970-2100)".into());
}
let start = get_time_bar_start(now, &bar_type, origin);

Type guard

fn sane_timestamp(ts: Timestamp) -> bool {
    ts.as_unix_nanos() > 0 && ts.as_unix_nanos() < 4_102_444_800_000_000_000
}

Try / catch

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

Prevention

When it happens

Trigger: get_time_bar_start with a Month bar where repeatedly adding months_step from the year anchor exceeds the maximum representable jiff timestamp — only reachable with an extreme `now`, huge months_step, or out-of-range origin offset.

Common situations: A `now` timestamp far in the future (bad clock, fake/synthetic time misconfigured); a very large month step near u32 bounds combined with an in-range `now`.

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/76100a054996071a. Report an issue: GitHub.