1use std::fmt::Display;
5use std::hash::Hash as StdHash;
6use std::num::ParseIntError;
7use std::ops::Add;
8use std::str::FromStr;
9#[cfg(not(test))]
10use std::time::{SystemTime, SystemTimeError, UNIX_EPOCH};
11
12#[cfg(test)]
13use mock_instant::SystemTimeError;
14#[cfg(test)]
15use mock_instant::thread_local::{SystemTime, UNIX_EPOCH};
16use serde::{Deserialize, Serialize};
17use thiserror::Error;
18
19#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, StdHash, Serialize, Deserialize)]
24pub struct Timestamp(u64);
25
26impl Timestamp {
27 #[cfg(test)]
28 pub fn new(value: u64) -> Self {
29 Self(value)
30 }
31
32 pub fn now() -> Self {
33 let now = SystemTime::now();
34 now.try_into().expect("system time went backwards")
35 }
36}
37
38impl From<Timestamp> for u64 {
39 fn from(value: Timestamp) -> Self {
40 value.0
41 }
42}
43
44#[cfg(test)]
45impl From<u64> for Timestamp {
46 fn from(value: u64) -> Self {
47 Self(value)
48 }
49}
50
51impl TryFrom<SystemTime> for Timestamp {
52 type Error = SystemTimeError;
53
54 fn try_from(system_time: SystemTime) -> Result<Self, Self::Error> {
55 let duration = system_time.duration_since(UNIX_EPOCH)?;
56 Ok(Self(duration.as_micros() as u64))
58 }
59}
60
61impl Display for Timestamp {
62 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
63 write!(f, "{}", self.0)
64 }
65}
66
67#[derive(
71 Copy, Clone, Default, Debug, PartialEq, Eq, PartialOrd, Ord, StdHash, Serialize, Deserialize,
72)]
73pub struct LamportTimestamp(u64);
74
75impl LamportTimestamp {
76 pub fn new(value: u64) -> Self {
77 Self(value)
78 }
79
80 pub fn increment(self) -> Self {
81 Self(self.0 + 1)
82 }
83}
84
85impl Display for LamportTimestamp {
86 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
87 write!(f, "{}", self.0)
88 }
89}
90
91impl Add<u64> for LamportTimestamp {
92 type Output = LamportTimestamp;
93
94 fn add(self, rhs: u64) -> Self::Output {
95 Self(self.0.saturating_add(rhs))
96 }
97}
98
99impl From<u64> for LamportTimestamp {
100 fn from(value: u64) -> Self {
101 Self(value)
102 }
103}
104
105#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, StdHash, Serialize, Deserialize)]
116pub struct HybridTimestamp(Timestamp, LamportTimestamp);
117
118impl HybridTimestamp {
119 pub fn from_parts(timestamp: Timestamp, logical: LamportTimestamp) -> Self {
120 Self(timestamp, logical)
121 }
122
123 pub fn now() -> Self {
124 Self(Timestamp::now(), LamportTimestamp::default())
125 }
126
127 pub fn increment(self) -> Self {
128 let timestamp = Timestamp::now();
129 if timestamp == self.0 {
130 Self(timestamp, self.1.increment())
131 } else {
132 Self(timestamp, LamportTimestamp::default())
133 }
134 }
135}
136
137const SEPARATOR: char = '/';
138
139impl FromStr for HybridTimestamp {
140 type Err = HybridTimestampError;
141
142 fn from_str(s: &str) -> Result<Self, Self::Err> {
143 let parts: Vec<_> = s.split(SEPARATOR).collect();
144 if parts.len() != 2 {
145 return Err(HybridTimestampError::Size(parts.len()));
146 }
147
148 let unix: u64 = u64::from_str(parts[0]).map_err(HybridTimestampError::ParseInt)?;
149 let logical: u64 = u64::from_str(parts[1]).map_err(HybridTimestampError::ParseInt)?;
150
151 Ok(Self(Timestamp(unix), LamportTimestamp::new(logical)))
152 }
153}
154
155impl Display for HybridTimestamp {
156 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
157 write!(f, "{}{SEPARATOR}{}", self.0, self.1)
158 }
159}
160
161#[cfg(test)]
162impl From<u64> for HybridTimestamp {
163 fn from(value: u64) -> Self {
164 Self(Timestamp::new(value), LamportTimestamp::default())
165 }
166}
167
168#[derive(Debug, Error)]
169pub enum HybridTimestampError {
170 #[error("invalid size, expected 2, given: {0}")]
171 Size(usize),
172
173 #[error(transparent)]
174 ParseInt(#[from] ParseIntError),
175}
176
177#[cfg(test)]
178mod tests {
179 use std::str::FromStr;
180 use std::time::Duration;
181
182 use mock_instant::thread_local::MockClock;
183
184 use super::{HybridTimestamp, LamportTimestamp};
185
186 #[test]
187 fn convert_and_compare() {
188 assert!(LamportTimestamp(5) > 3.into());
189 }
190
191 #[test]
192 fn add_u64_with_max() {
193 assert_eq!(LamportTimestamp(3) + 3u64, LamportTimestamp(6));
194 assert_eq!(
195 LamportTimestamp(u64::MAX) + 3u64,
196 LamportTimestamp(u64::MAX)
197 );
198 }
199
200 #[test]
201 fn increment_hybrid() {
202 MockClock::set_system_time(Duration::from_secs(0));
203
204 let timestamp_1 = HybridTimestamp::now();
205 let timestamp_2 = timestamp_1.increment();
206 assert!(timestamp_2 > timestamp_1);
207
208 MockClock::advance_system_time(Duration::from_secs(1));
209
210 let timestamp_3 = HybridTimestamp::now();
211 let timestamp_4 = timestamp_3.increment();
212 assert!(timestamp_3 > timestamp_2);
213 assert!(timestamp_4 > timestamp_3);
214
215 MockClock::advance_system_time(Duration::from_secs(1));
216
217 let timestamp_5 = HybridTimestamp::now();
218 let timestamp_6 = HybridTimestamp::now();
219
220 assert!(timestamp_5 > timestamp_4);
221 assert!(timestamp_6 > timestamp_4);
222 assert_eq!(timestamp_5, timestamp_6);
223 }
224
225 #[test]
226 fn hybrid_from_str() {
227 let timestamp = HybridTimestamp::now().increment().increment();
228 let timestamp_str = timestamp.to_string();
229 assert_eq!(
230 HybridTimestamp::from_str(×tamp_str).unwrap(),
231 timestamp
232 );
233 }
234}