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