use std::fmt::Display;
use std::hash::Hash as StdHash;
use std::num::ParseIntError;
use std::ops::Add;
use std::str::FromStr;
#[cfg(not(any(test, feature = "test_utils")))]
use std::time::{SystemTime, SystemTimeError, UNIX_EPOCH};
#[cfg(any(test, feature = "test_utils"))]
use mock_instant::SystemTimeError;
#[cfg(any(test, feature = "test_utils"))]
use mock_instant::thread_local::{SystemTime, UNIX_EPOCH};
use serde::{Deserialize, Serialize};
use thiserror::Error;
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, StdHash, Serialize, Deserialize)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[serde(transparent)]
pub struct Timestamp(u64);
impl Timestamp {
pub fn new(value: u64) -> Self {
Self(value)
}
pub fn now() -> Self {
let now = SystemTime::now();
now.try_into().expect("system time went backwards")
}
}
impl From<Timestamp> for u64 {
fn from(value: Timestamp) -> Self {
value.0
}
}
impl From<u64> for Timestamp {
fn from(value: u64) -> Self {
Self(value)
}
}
impl TryFrom<SystemTime> for Timestamp {
type Error = SystemTimeError;
fn try_from(system_time: SystemTime) -> Result<Self, Self::Error> {
let duration = system_time.duration_since(UNIX_EPOCH)?;
Ok(Self(duration.as_micros() as u64))
}
}
impl Display for Timestamp {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
#[derive(
Copy, Clone, Default, Debug, PartialEq, Eq, PartialOrd, Ord, StdHash, Serialize, Deserialize,
)]
#[serde(transparent)]
pub struct LamportTimestamp(u64);
impl LamportTimestamp {
pub fn new(value: u64) -> Self {
Self(value)
}
pub fn increment(self) -> Self {
Self(self.0 + 1)
}
}
impl Display for LamportTimestamp {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
impl Add<u64> for LamportTimestamp {
type Output = LamportTimestamp;
fn add(self, rhs: u64) -> Self::Output {
Self(self.0.saturating_add(rhs))
}
}
impl From<u64> for LamportTimestamp {
fn from(value: u64) -> Self {
Self(value)
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, StdHash, Serialize, Deserialize)]
pub struct HybridTimestamp(Timestamp, LamportTimestamp);
impl HybridTimestamp {
pub fn from_parts(timestamp: Timestamp, logical: LamportTimestamp) -> Self {
Self(timestamp, logical)
}
pub fn now() -> Self {
Self(Timestamp::now(), LamportTimestamp::default())
}
pub fn increment(self) -> Self {
let timestamp = Timestamp::now();
if timestamp == self.0 {
Self(timestamp, self.1.increment())
} else {
Self(timestamp, LamportTimestamp::default())
}
}
pub fn to_parts(&self) -> (Timestamp, LamportTimestamp) {
(self.0, self.1)
}
}
const SEPARATOR: char = '/';
impl FromStr for HybridTimestamp {
type Err = HybridTimestampError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let parts: Vec<_> = s.split(SEPARATOR).collect();
if parts.len() != 2 {
return Err(HybridTimestampError::Size(parts.len()));
}
let unix: u64 = u64::from_str(parts[0]).map_err(HybridTimestampError::ParseInt)?;
let logical: u64 = u64::from_str(parts[1]).map_err(HybridTimestampError::ParseInt)?;
Ok(Self(Timestamp(unix), LamportTimestamp::new(logical)))
}
}
impl Display for HybridTimestamp {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}{SEPARATOR}{}", self.0, self.1)
}
}
impl From<u64> for HybridTimestamp {
fn from(value: u64) -> Self {
Self(Timestamp::new(value), LamportTimestamp::default())
}
}
#[derive(Debug, Error)]
pub enum HybridTimestampError {
#[error("invalid size, expected 2, given: {0}")]
Size(usize),
#[error(transparent)]
ParseInt(#[from] ParseIntError),
}
#[cfg(test)]
mod tests {
use std::str::FromStr;
use std::time::Duration;
use mock_instant::thread_local::MockClock;
use super::{HybridTimestamp, LamportTimestamp};
#[test]
fn convert_and_compare() {
assert!(LamportTimestamp(5) > 3.into());
}
#[test]
fn add_u64_with_max() {
assert_eq!(LamportTimestamp(3) + 3u64, LamportTimestamp(6));
assert_eq!(
LamportTimestamp(u64::MAX) + 3u64,
LamportTimestamp(u64::MAX)
);
}
#[test]
fn increment_hybrid() {
MockClock::set_system_time(Duration::from_secs(0));
let timestamp_1 = HybridTimestamp::now();
let timestamp_2 = timestamp_1.increment();
assert!(timestamp_2 > timestamp_1);
MockClock::advance_system_time(Duration::from_secs(1));
let timestamp_3 = HybridTimestamp::now();
let timestamp_4 = timestamp_3.increment();
assert!(timestamp_3 > timestamp_2);
assert!(timestamp_4 > timestamp_3);
MockClock::advance_system_time(Duration::from_secs(1));
let timestamp_5 = HybridTimestamp::now();
let timestamp_6 = HybridTimestamp::now();
assert!(timestamp_5 > timestamp_4);
assert!(timestamp_6 > timestamp_4);
assert_eq!(timestamp_5, timestamp_6);
}
#[test]
fn hybrid_from_str() {
let timestamp = HybridTimestamp::now().increment().increment();
let timestamp_str = timestamp.to_string();
assert_eq!(
HybridTimestamp::from_str(×tamp_str).unwrap(),
timestamp
);
}
}