nautechsystems/nautilus_trader · warning

FX session date must be representable

Error message

FX session date must be representable

What it means

In `fx_next_boundary`, when the current local time is past the session time, the code advances the date by one day via `checked_add` and panics if the resulting date is not representable. `jiff`'s `CivilDate` has bounded min/max years, so this only fires at the extreme edge of the representable calendar range. In practice it is an unreachable defensive panic for normal timestamps.

Source

Thrown at crates/trading/src/sessions.rs:142

}

/// Returns the previous session end time in UTC.
#[must_use]
pub fn fx_prev_end(session: ForexSession, time_now: Timestamp) -> Timestamp {
    let local_now = fx_local_from_utc(session, time_now);
    let (_, end_time) = session.session_times();

    fx_prev_boundary(&local_now, end_time)
}

fn fx_next_boundary(local_now: &Zoned, session_time: Time) -> Timestamp {
    let timezone = local_now.time_zone().clone();
    let mut date = local_now.date();

    if local_now.time() > session_time {
        date = date
            .checked_add(Span::new().days(1))
            .expect("FX session date must be representable");
    }

    let weekend_days = match date.weekday() {
        Weekday::Saturday => 2,
        Weekday::Sunday => 1,
        _ => 0,
    };
    date = date
        .checked_add(Span::new().days(weekend_days))
        .expect("FX session date must be representable");

    timezone
        .to_ambiguous_timestamp(date.to_datetime(session_time))
        .unambiguous()
        .expect("FX session boundary must be a unique local time")
}

fn fx_prev_boundary(local_now: &Zoned, session_time: Time) -> Timestamp {

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Ensure the system clock and any injected timestamps are within realistic (jiff-representable, well-below year 9999) ranges.
  2. Clamp or validate `local_now` before calling session helpers if timestamps come from external/untrusted data.
  3. If you legitimately operate near the boundary, compute the boundary with `checked_add` and handle `None` yourself instead of the panicking helper.
  4. Report upstream if reachable with a sane clock — it indicates an unexpected input path.

Example fix

// before (caller)
let next = fx_next_start(ForexSession::London, &now); // panics on overflow

// after (defensive pre-check)
let now = Zoned::now();
assert!(now.year() < 9999, "system clock far out of range: {}", now.year());
let next = fx_next_start(ForexSession::London, &now);
Defensive patterns

Strategy: validation

Validate before calling

// Reject absurd timestamps before calling session helpers
fn timestamp_in_reasonable_range(now: &jiff::Zoned) -> bool {
    (-2200..=2200).contains(&now.year())
}

Try / catch

// Rust panics are not catchable; pre-validate instead
let now = jiff::Zoned::now();
assert!(timestamp_in_reasonable_range(&now), "clock outside sane range");

Prevention

When it happens

Trigger: Calling `fx_next_start`/`fx_next_end` (via `fx_next_boundary`) while the current local date is at `CivilDate`'s maximum (year 9999) and the local time is past the session time, forcing a +1 day overflow.

Common situations: Only with fabricated/system clock values at the far end of the supported date range; mocked `Zoned` inputs in tests or corrupted clock data far beyond realistic trading dates.

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