i0 0.1.5

thei.rs/i0 reference implementation written in rust
Documentation
use std::cmp::Ordering;
use std::fmt::{self, Display, Formatter, LowerHex};
use time::{UtcDateTime, macros::utc_datetime};

const EPOCH: UtcDateTime = utc_datetime!(2025-06-01 00:00:00);

/// A point in time (in seconds) past the i0 epoch (2025-06-01 00:00:00)
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct Timestamp {
    seconds: u64,
}

impl Timestamp {
    /// Creates a new `Timestamp` from the current time
    ///
    /// # Panics
    ///
    /// This function panics if the system clock is before the epoch.
    /// Unless your system clock is misconfigured, that should never happen.
    pub fn new() -> Self {
        let now = UtcDateTime::now();
        let diff: u64 = (now - EPOCH)
            .whole_seconds()
            .try_into()
            .expect("clock is before timestamp epoch; your clock is likely wrong.");

        Self::from_seconds(diff)
    }

    /// Returns the seconds since epoch as a byte array in big-endian byte order
    pub fn to_bytes(self) -> [u8; 8] {
        self.as_seconds().to_be_bytes()
    }

    /// Creates a `Timestamp` from a byte array representing the seconds since epoch as a big-endian `u64`
    pub fn from_bytes(bytes: [u8; 8]) -> Self {
        u64::from_be_bytes(bytes).into()
    }

    /// Creates a `Timestamp` from the seconds since epoch as a big-endian unsigned integer
    pub fn from_seconds(seconds: u64) -> Self {
        Self { seconds }
    }

    /// Returns the seconds since epoch as an integer
    pub fn into_seconds(self) -> u64 {
        self.seconds
    }

    /// Extracts the seconds since epoch as an integer
    pub fn as_seconds(&self) -> &u64 {
        &self.seconds
    }

    /// Attempts to parse the hex-encoded seconds since epoch (as a u64) into a `Timestamp`
    pub fn try_from_hex(s: &str) -> Result<Self, std::num::ParseIntError> {
        u64::from_str_radix(s, 16).map(Self::from_seconds)
    }
}

impl Default for Timestamp {
    fn default() -> Self {
        Self::new()
    }
}

impl From<u64> for Timestamp {
    fn from(sec: u64) -> Self {
        Timestamp::from_seconds(sec)
    }
}

impl From<[u8; 8]> for Timestamp {
    fn from(bytes: [u8; 8]) -> Self {
        Self::from_bytes(bytes)
    }
}

impl TryFrom<&[u8]> for Timestamp {
    type Error = std::array::TryFromSliceError;

    fn try_from(slice: &[u8]) -> Result<Self, Self::Error> {
        let bytes: [u8; 8] = slice.try_into()?;
        Ok(bytes.into())
    }
}

impl TryFrom<Vec<u8>> for Timestamp {
    type Error = std::array::TryFromSliceError;

    fn try_from(vec: Vec<u8>) -> Result<Self, Self::Error> {
        vec.as_slice().try_into()
    }
}

impl From<Timestamp> for u64 {
    fn from(ts: Timestamp) -> Self {
        ts.into_seconds()
    }
}

impl From<Timestamp> for [u8; 8] {
    fn from(ts: Timestamp) -> Self {
        ts.to_bytes()
    }
}

impl From<Timestamp> for Vec<u8> {
    fn from(ts: Timestamp) -> Self {
        ts.to_bytes().into()
    }
}

impl LowerHex for Timestamp {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        write!(f, "{:x}", self.as_seconds())?;
        Ok(())
    }
}

impl Display for Timestamp {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        LowerHex::fmt(&self, f)
    }
}

impl Ord for Timestamp {
    fn cmp(&self, other: &Self) -> Ordering {
        self.as_seconds().cmp(other.as_seconds())
    }
}

impl PartialOrd for Timestamp {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}