nautechsystems/nautilus_trader · error

valid UTC day start

Error message

valid UTC day start

What it means

`find_closest_smaller_time` floors the current time `now` to the start of its UTC calendar day, then offsets by `daily_time_origin` to find the closest time-bar origin. The `.expect("valid UTC day start")` panics if the timezone library cannot convert a midnight datetime to a `Timestamp`. This is an internal invariant: a UTC midnight date should always convert, so a panic here means a corrupt `now` timestamp or a library bug.

Source

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

            "Aggregation type {} not supported for time bars",
            spec.aggregation
        ),
    }
}

/// Finds the closest smaller time based on a daily time origin and period.
///
/// This function calculates the most recent time that is aligned with the given period
/// and is less than or equal to the current time.
fn find_closest_smaller_time(
    now: Timestamp,
    daily_time_origin: SignedDuration,
    period: SignedDuration,
) -> Timestamp {
    // Floor to start of day
    let day_start = Offset::UTC
        .to_timestamp(Offset::UTC.to_datetime(now).date().at(0, 0, 0, 0))
        .expect("valid UTC day start");
    let base_time = day_start + daily_time_origin;

    let time_difference = base_time.duration_until(now);
    let period_ns = period.as_nanos();
    debug_assert_ne!(period_ns, 0, "bar period must be non-zero");

    // Use div_euclid for floor division (rounds toward -inf, not zero)
    // so negative deltas (now before origin) yield the previous period boundary
    let num_periods = time_difference.as_nanos().div_euclid(period_ns);

    base_time + SignedDuration::from_nanos_i128(num_periods * period_ns)
}

fn duration_days(days: i64) -> SignedDuration {
    try_duration_days(days).unwrap_or_else(|e| panic!("{e}"))
}

fn try_duration_days(days: i64) -> anyhow::Result<SignedDuration> {

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Verify the `now` Timestamp passed to get_time_bar_start is a real timestamp within a sane range (e.g. 1970-2100) before aggregation
  2. Check the source of the timestamp for corruption (data files, fixtures, deserialization)
  3. Clamp or sanitize out-of-range timestamps upstream of time-bar aggregation
  4. If it reproduces with a valid timestamp, report as a bug to the nautilus_trader maintainers

Example fix

// before
let start = get_time_bar_start(ts_event, daily_time_origin, period);
// after
const MIN_TS: u64 = 0;
const MAX_TS: u64 = 4_102_444_800_000_000_000; // ~2100-01-01 in ns
assert!(ts_event.as_u64() > MIN_TS && ts_event.as_u64() < MAX_TS, "timestamp out of range");
let start = get_time_bar_start(ts_event, daily_time_origin, period);
Defensive patterns

Strategy: validation

Validate before calling

def assert_valid_timestamp(ts_ns: int) -> int:
    if not (0 < ts_ns < 4_102_444_800_000_000_000):  # ~2100-01-01 ns
        raise ValueError(f"timestamp {ts_ns} outside sane range")
    return ts_ns

Type guard

def is_valid_unix_nanos(ts_ns: int) -> bool:
    return isinstance(ts_ns, int) and 0 < ts_ns < 4_102_444_800_000_000_000

Prevention

When it happens

Trigger: Calling `get_time_bar_start` (directly or via time-bar aggregation) with a `now` timestamp outside the representable date range of the chrono/Offset conversion, or with a `Timestamp` value that produces an out-of-range date when floored to midnight.

Common situations: Loading or synthesizing bars with corrupt extreme nanosecond timestamps (e.g. 0 or u64::MAX) from a bad data file; feeding sentinel timestamps into time-based aggregation; upgrading nautilus versions where timestamp range checks changed.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


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