Timestamps & Unix time
Every message in an EGO1 recording is timestamped, but the timestamps are not Unix time by default — they run on the device's monotonic clock (nanoseconds since the device booted). This page explains the format and shows how to convert to Unix (wall-clock) time.
The two clocks
| Clock | What it is | Where you see it |
|---|---|---|
| Monotonic (boot) | Nanoseconds since the device powered on. Never jumps. | Every message timestamp, and the MCAP framing |
| Realtime (Unix) | Seconds + nanoseconds since 1970 (wall-clock). | Only inside /ego/system_health messages |
A fresh recording typically starts at a monotonic value of a few hundred seconds (time since boot), not the ~1.7 billion seconds you'd expect from a Unix timestamp — that's the tell-tale sign you're looking at the monotonic clock.
Where the timestamp lives
Both the MCAP framing (log_time / publish_time) and the message payload carry the same monotonic value
in nanoseconds. In the payload it's a { seconds, nanos } pair (combine as seconds * 1e9 + nanos):
| Topic | Timestamp field | Notes |
|---|---|---|
/ego/camera/0, 1 | timestamp | Per video frame |
/ego/imu/0/quat | timestamp | Fused orientation sample |
/ego/imu/0/raw | first_sample_time | Time of the first batched IMU sample |
/ego/system_health | timestamp | Plus realtime — see below |
Sensor timestamps come from a microsecond clock, so the nanosecond values end in 000. Only host-generated
messages carry full-nanosecond precision. This doesn't affect the conversion below.
Converting to Unix time
/ego/system_health messages (emitted at ~1 Hz) carry both clocks at once: the monotonic timestamp and
the Unix-epoch realtime. The difference between them is a fixed offset that maps one clock onto the
other:
offset = realtime_ns − monotonic_ns (constant across the recording)
unix_ns = any_monotonic_ns + offset
So the recipe is: read one /ego/system_health message, compute the offset once, then add it to any other
message's timestamp.
from visio_schema import read_mcap
def to_ns(t): # a { seconds, nanos } timestamp -> nanoseconds
return t.seconds * 1_000_000_000 + t.nanos
# 1. Find the monotonic -> Unix offset from any system_health message.
offset_ns = None
for msg, channel in read_mcap("capture.mcap"):
if channel.topic == "/ego/system_health":
offset_ns = to_ns(msg.realtime) - to_ns(msg.timestamp)
break
# 2. Apply it to any message timestamp.
for msg, channel in read_mcap("capture.mcap"):
if channel.topic == "/ego/imu/0/quat":
mono_ns = to_ns(msg.timestamp)
unix_ns = mono_ns + offset_ns
unix_s = unix_ns / 1e9
print(unix_s) # e.g. 1782802423.004 (a Unix timestamp)
break
To turn a Unix timestamp into a human-readable UTC time:
from datetime import datetime, timezone
print(datetime.fromtimestamp(unix_s, tz=timezone.utc).isoformat())
# 2026-06-30T06:53:43.004+00:00
The offset is stable to within a couple of microseconds. If you want to cancel that jitter, average the
offset over several /ego/system_health messages instead of using just the first one.
Caveats
- Wall-clock accuracy is only as good as the device's clock. EGO1 has no battery-backed real-time clock,
so its
realtimereflects whatever time it was given (or an unset clock). Absolute timestamps may be off by minutes, hours, or more if the clock was never synced — but timing within a recording (frame pacing, IMU intervals, camera-to-IMU alignment) is always accurate, because it all rides the same monotonic clock. /device_infois not an anchor. Itsboot_unix_secondsfield is0when the clock wasn't synced at boot — use the/ego/system_healthrealtimepairs instead.- No
system_health, no conversion. If a recording contains no/ego/system_healthmessages there's no in-file link to Unix time, and you can only work in boot-relative time.
For the full list of topics and schemas, see Data streams and the channel & network reference.