clockworklabs/SpacetimeDB · error

Duration overflows i64 microseconds

Error message

Duration overflows i64 microseconds

What it means

`TimeDuration` stores microseconds in an i64 (range about ±292,471 years). `from_duration` converts a std Duration and panics when `duration.as_micros()` (a u128) does not fit in i64. Realistically only unit-math bugs reach this — e.g. multiplying by 1_000_000 twice, or treating nanoseconds as microseconds — since no legitimate measured duration spans ~292k years.

Source

Thrown at crates/sats/src/time_duration.rs:78

    /// Converts `self` to `Duration`, clamping to 0 if negative.
    pub fn to_duration_saturating(self) -> Duration {
        self.to_duration().unwrap_or(Duration::ZERO)
    }

    /// Returns a positive `TimeDuration` with the magnitude of `self`.
    pub fn abs(self) -> Self {
        Self::from_micros(self.to_micros().saturating_abs())
    }

    /// Return a [`TimeDuration`] which represents the same span as `duration`.
    ///
    /// Panics if `duration.as_micros` overflows an `i64`
    pub fn from_duration(duration: Duration) -> Self {
        Self::from_micros(
            duration
                .as_micros()
                .try_into()
                .expect("Duration overflows i64 microseconds"),
        )
    }

    /// Returns `Some(self + other)`, or `None` if that value would be out of bounds for [`TimeDuration`].
    pub fn checked_add(self, other: Self) -> Option<Self> {
        self.to_micros().checked_add(other.to_micros()).map(Self::from_micros)
    }

    /// Returns `Some(self - other)`, or `None` if that value would be out of bounds for [`TimeDuration`].
    pub fn checked_sub(self, other: Self) -> Option<Self> {
        self.to_micros().checked_sub(other.to_micros()).map(Self::from_micros)
    }

    /// Generate an `iso8601` format string.
    ///
    /// This is the better supported format for use for the `pg wire protocol`.
    ///
    /// Example:

View on GitHub (pinned to 524b4487d9)

Solutions

  1. Fix the unit math producing the oversized Duration — usually a duplicated *1_000_000 factor or nanos/micros confusion.
  2. Validate at the boundary: reject durations where `as_micros() > i64::MAX as u128` instead of converting.
  3. If huge spans are legitimate, clamp with `min(i64::MAX as u128)` or use checked conversion and handle the error.

Example fix

// before
let td = TimeDuration::from_duration(huge); // panics beyond ~292k years

// after: validate, then convert explicitly
if d.as_micros() > i64::MAX as u128 { return Err("duration too large"); }
let td = TimeDuration::from_micros(d.as_micros() as i64);
Defensive patterns

Strategy: validation

Validate before calling

fn fits_i64_micros(d: Duration) -> bool { d.as_micros() <= i64::MAX as u128 }
if !fits_i64_micros(d) { return Err("duration too large".into()); }
let td = TimeDuration::from_duration(d);

Prevention

When it happens

Trigger: Calling `TimeDuration::from_duration(d)` where d was built by mistaken unit arithmetic (e.g. Duration::from_micros(nanos * 1_000_000)), or by accumulating durations in a loop without overflow checks until the value explodes.

Common situations: Timing/metrics code that multiplies the wrong unit constants; summing durations over long-lived processes; converting untrusted Duration input from wire formats or config.

Related errors


AI-assisted analysis of clockworklabs/SpacetimeDB@524b4487d9 (2026-08-16). Data as JSON: /api/errors/ed2915bb16133d00. Report an issue: GitHub.