Skip to main content

motorcortex_rust/
time_spec.rs

1//! Wire-level timestamp embedded in subscription payloads.
2//!
3//! 16 bytes, two little-endian `i64`s (seconds + nanoseconds). The
4//! on-wire layout is fixed by the server; decoding is explicit
5//! rather than a native-layout pointer read so the library stays
6//! portable to big-endian hosts.
7
8use chrono::{DateTime, Local, Utc};
9
10/// Size of the wire representation: two `i64` fields packed
11/// back-to-back.
12pub(crate) const TIMESPEC_WIRE_SIZE: usize = 16;
13
14#[derive(Debug, Clone, Copy)]
15pub struct TimeSpec {
16    pub sec: i64,  // Seconds
17    pub nsec: i64, // Nanoseconds
18}
19
20impl TimeSpec {
21    /// Decode a [`TimeSpec`] from the first 16 bytes of `buffer`.
22    /// Returns `None` if the buffer is shorter than that.
23    pub fn from_buffer(buffer: &[u8]) -> Option<Self> {
24        if buffer.len() < TIMESPEC_WIRE_SIZE {
25            return None;
26        }
27        // Explicit little-endian decode of each field. Fixed-size
28        // array slicing gives us `TryFrom` for the two 8-byte
29        // chunks; `i64::from_le_bytes` is endian-safe and matches
30        // what the server writes.
31        let sec = i64::from_le_bytes(buffer[0..8].try_into().ok()?);
32        let nsec = i64::from_le_bytes(buffer[8..16].try_into().ok()?);
33        Some(Self { sec, nsec })
34    }
35
36    pub fn to_date_time(&self) -> DateTime<Local> {
37        self.to_utc_date_time().with_timezone(&Local)
38    }
39
40    pub fn to_utc_date_time(&self) -> DateTime<Utc> {
41        DateTime::from_timestamp(self.sec, self.nsec as u32).expect("Invalid TimeSpec")
42    }
43}