willow-data-model 0.7.0

The core datatypes of Willow, an eventually consistent data store with improved distributed deletion.
Documentation
use core::fmt;

use core::ops::{Add, AddAssign, Sub, SubAssign};

#[cfg(feature = "dev")]
use arbitrary::Arbitrary;

#[cfg(feature = "std")]
use hifitime::HifitimeError;
use hifitime::{Duration, Epoch, J2000_REF_EPOCH};

use order_theory::{
    GreatestElement, LeastElement, LowerSemilattice, PredecessorExceptForLeast,
    SuccessorExceptForGreatest, TryPredecessor, TrySuccessor, UpperSemilattice,
};

/// A [Willow Timestamp](https://willowprotocol.org/specs/data-model/index.html#Timestamp) is a 64-bit unsigned integer, specifying the number of microseconds which have elapsed since the J2000 reference epoch (January 1, 2000, at noon, i.e., 12:00 TT) according to [International Atomic Time](https://en.wikipedia.org/wiki/International_Atomic_Time) (aka TAI).
///
/// This type is a thin wrapper around `u64`, and provides convenient interoperability with the [`hifitime`] crate.
///
/// Use the [`TryFrom`] impls to convert between [`Timestamp`] and the [`hifitime::Epoch`] and [`hifitime::Duration`] types. Use the [`From`] impls to convert from and to `u64` if you need low-level access.
///
/// ```
/// # #[cfg(feature = "std")] {
/// use willow_data_model::prelude::*;
///
/// let now = Timestamp::now().expect("`std` should provide a valid timestamp");
/// let three_days_later = now + 3.days();
/// assert!(three_days_later > now);
/// # }
/// ```
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default, Hash, Debug)]
#[cfg_attr(feature = "dev", derive(Arbitrary))]
pub struct Timestamp(u64);

impl fmt::Display for Timestamp {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        Epoch::from(*self).fmt(f)
    }
}

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

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

impl AsRef<u64> for Timestamp {
    fn as_ref(&self) -> &u64 {
        &self.0
    }
}

impl AsMut<u64> for Timestamp {
    fn as_mut(&mut self) -> &mut u64 {
        &mut self.0
    }
}

/// Conversion fails if the resulting timestamp would not fit into a `u64`.
impl TryFrom<Epoch> for Timestamp {
    type Error = <i128 as TryInto<u64>>::Error;

    /// Conversion fails if the resulting timestamp would not fit into a `u64`.
    fn try_from(value: Epoch) -> Result<Self, Self::Error> {
        let diff = value - J2000_REF_EPOCH;
        let microseconds = diff.total_nanoseconds() / 1000;
        Ok(Self(microseconds.try_into()?))
    }
}

impl From<Timestamp> for Epoch {
    fn from(value: Timestamp) -> Self {
        let duration_from_j2000 = Duration::from(value);
        J2000_REF_EPOCH + duration_from_j2000
    }
}

/// Creates a timestamp whose value is the number of microseconds in the duration. Conversion fails if the resulting timestamp would not fit into a `u64`.
impl TryFrom<Duration> for Timestamp {
    type Error = <i128 as TryInto<u64>>::Error;

    /// Conversion fails if the resulting timestamp would not fit into a `u64`.
    fn try_from(value: Duration) -> Result<Self, Self::Error> {
        Ok(Self((value.total_nanoseconds() / 1000).try_into()?))
    }
}

/// Creates a duration which corresponds to the number of microsecond represented in the timestamp.
impl From<Timestamp> for Duration {
    fn from(value: Timestamp) -> Self {
        Duration::from_total_nanoseconds((value.0 as i128) * 1000)
    }
}

impl Timestamp {
    /// Creates the timestamp corresponding to the current instant in time. WARNING: This assumes that the system time returns the time in UTC. Uses [`std::time::SystemTime::now`] under the hood.
    #[cfg(feature = "std")]
    pub fn now() -> Result<Self, HifitimeError> {
        Self::try_from(Epoch::now()?).map_err(|_| HifitimeError::SystemTimeError)
    }
}

impl Add<Duration> for Timestamp {
    type Output = Self;

    fn add(self, rhs: Duration) -> Self::Output {
        (Duration::from(self) + rhs)
            .try_into()
            .expect("timestamp overflow")
    }
}

impl AddAssign<Duration> for Timestamp {
    fn add_assign(&mut self, rhs: Duration) {
        *self = *self + rhs;
    }
}

impl Add for Timestamp {
    type Output = Self;

    fn add(self, rhs: Self) -> Self::Output {
        Self(self.0 + rhs.0)
    }
}

impl AddAssign for Timestamp {
    fn add_assign(&mut self, rhs: Self) {
        *self = Self(self.0 + rhs.0);
    }
}

impl Sub<Duration> for Timestamp {
    type Output = Self;

    fn sub(self, rhs: Duration) -> Self::Output {
        (Duration::from(self) - rhs)
            .try_into()
            .expect("timestamp overflow")
    }
}

impl SubAssign<Duration> for Timestamp {
    fn sub_assign(&mut self, rhs: Duration) {
        *self = *self - rhs;
    }
}

impl Sub for Timestamp {
    type Output = Self;

    fn sub(self, rhs: Self) -> Self::Output {
        Self(self.0 - rhs.0)
    }
}

impl SubAssign for Timestamp {
    fn sub_assign(&mut self, rhs: Self) {
        *self = Self(self.0 - rhs.0);
    }
}

impl LeastElement for Timestamp {
    fn least() -> Self {
        u64::least().into()
    }
}

impl GreatestElement for Timestamp {
    fn greatest() -> Self {
        u64::greatest().into()
    }
}

impl LowerSemilattice for Timestamp {
    fn greatest_lower_bound(&self, other: &Self) -> Self {
        self.0.greatest_lower_bound(other.as_ref()).into()
    }
}

impl UpperSemilattice for Timestamp {
    fn least_upper_bound(&self, other: &Self) -> Self {
        self.0.least_upper_bound(other.as_ref()).into()
    }
}

impl TryPredecessor for Timestamp {
    fn try_predecessor(&self) -> Option<Self> {
        self.0.try_predecessor().map(Self)
    }
}

impl TrySuccessor for Timestamp {
    fn try_successor(&self) -> Option<Self> {
        self.0.try_successor().map(Self)
    }
}

impl PredecessorExceptForLeast for Timestamp {}

impl SuccessorExceptForGreatest for Timestamp {}