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