clockworklabs/SpacetimeDB · error

SystemTime predates the Unix epoch

Error message

SystemTime predates the Unix epoch

What it means

`Timestamp::from_system_time` calls `system_time.duration_since(SystemTime::UNIX_EPOCH).expect("SystemTime predates the Unix epoch")`, so converting any SystemTime earlier than 1970-01-01 panics — even though Timestamp itself can represent pre-epoch times via signed micros. The trigger is a clock reading before 1970 or code that computes UNIX_EPOCH minus a duration.

Source

Thrown at crates/sats/src/timestamp.rs:110

            Ok(positive) => SystemTime::UNIX_EPOCH
                .checked_add(positive)
                .expect("Timestamp with i64 microseconds since Unix epoch overflows SystemTime"),
            Err(negative) => SystemTime::UNIX_EPOCH
                .checked_sub(negative)
                .expect("Timestamp with i64 microseconds before Unix epoch overflows SystemTime"),
        }
    }

    /// Convert a [`SystemTime`] into a [`Timestamp`] which refers to approximately the same point in time.
    ///
    /// This conversion may panic if `system_time` is out of bounds for [`Duration`].
    /// [`SystemTime`]'s range is larger than [`Timestamp`] on both Unix and Windows targets,
    /// so times in the far past or far future may panic.
    /// [`Timestamp`]'s range is approximately 292 years before and after the Unix epoch.
    pub fn from_system_time(system_time: SystemTime) -> Self {
        let duration = system_time
            .duration_since(SystemTime::UNIX_EPOCH)
            .expect("SystemTime predates the Unix epoch");
        Self::from_duration_since_unix_epoch(duration)
    }

    /// Returns the [`Duration`] delta between `self` and `earlier`, if `earlier` predates `self`.
    ///
    /// Returns `None` if `earlier` is strictly greater than `self`,
    /// or if the difference between `earlier` and `self` overflows an `i64`.
    pub fn duration_since(self, earlier: Timestamp) -> Option<Duration> {
        self.time_duration_since(earlier)?.to_duration().ok()
    }

    /// Returns the [`TimeDuration`] delta between `self` and `earlier`.
    ///
    /// The result may be negative if `earlier` is actually later than `self`.
    ///
    /// Returns `None` if the subtraction overflows or underflows `i64` microseconds.
    pub fn time_duration_since(self, earlier: Timestamp) -> Option<TimeDuration> {
        let delta = self

View on GitHub (pinned to 524b4487d9)

Solutions

  1. Handle pre-epoch times explicitly: match on duration_since's Err and build `Timestamp::from_micros_since_unix_epoch(-micros)` instead of converting via from_system_time.
  2. Fix the host clock (NTP/systemd-timesyncd) if it genuinely reads before 1970.
  3. Avoid computing UNIX_EPOCH - dur and feeding the result to from_system_time.

Example fix

// before: panics because the value predates the epoch
let ts = Timestamp::from_system_time(SystemTime::UNIX_EPOCH - Duration::from_secs(60));

// after: construct pre-epoch values from signed micros
let ts = match SystemTime::UNIX_EPOCH.checked_sub(Duration::from_secs(60)) {
    Some(t) if t >= SystemTime::UNIX_EPOCH => Timestamp::from_system_time(t),
    _ => Timestamp::from_micros_since_unix_epoch(-60_000_000),
};
Defensive patterns

Strategy: validation

Validate before calling

if system_time >= SystemTime::UNIX_EPOCH {
    let ts = Timestamp::from_system_time(system_time);
} else {
    // build from signed micros: Timestamp::from_micros_since_unix_epoch(-micros)
}

Prevention

When it happens

Trigger: Passing `SystemTime::UNIX_EPOCH - Duration::from_secs(...)` (e.g. tests simulating past dates), or a machine clock erroneously set pre-1970 (dead RTC battery, unsynced VM), into from_system_time.

Common situations: Unit tests of date logic that subtract from the epoch; VMs or embedded boards with dead/unsynchronized RTCs; accidental negative durations in time math.

Related errors


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