1#![doc = include_str!("../README.md")]
2#![doc = document_features::document_features!()]
4#[cfg(feature = "serde")]
7use serde::{Deserialize, Serialize};
8use std::array::TryFromSliceError;
9use std::error::Error;
10use std::fmt::Display;
11use std::time::{Duration, SystemTime};
12use std::{
13 ops::{Add, AddAssign, Sub, SubAssign},
14 sync::Mutex,
15};
16
17use once_cell::sync::Lazy;
18
19const CLOCK_MASK: u64 = (1 << 8) - 1;
21const TIME_MASK: u64 = !0 >> 8;
22
23pub struct TimestampFactory {
24 clock_id: u64,
25 last_time: u64,
26}
27
28impl TimestampFactory {
29 pub fn new() -> Self {
32 let mut bytes = [0; 8];
33 let _ = getrandom::getrandom(&mut bytes);
34
35 Self {
36 clock_id: u64::from_le_bytes(bytes) & CLOCK_MASK,
37 last_time: system_time() & TIME_MASK,
38 }
39 }
40
41 pub fn clock_id(mut self, clock_id: u8) -> TimestampFactory {
43 self.clock_id = clock_id as u64;
44 self
45 }
46
47 pub fn now(&mut self) -> Timestamp {
49 self.last_time = (system_time() & TIME_MASK).max(self.last_time + CLOCK_MASK + 1);
51
52 Timestamp(self.last_time | self.clock_id)
54 }
55}
56
57impl Default for TimestampFactory {
58 fn default() -> Self {
59 Self::new()
60 }
61}
62
63pub static DEFAULT_FACTORY: Lazy<Mutex<TimestampFactory>> =
64 Lazy::new(|| Mutex::new(TimestampFactory::default()));
65
66#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Hash, Eq, Ord)]
82pub struct Timestamp(u64);
83
84impl Timestamp {
85 pub fn now() -> Self {
87 DEFAULT_FACTORY.lock().unwrap().now()
88 }
89
90 #[cfg(feature = "httpdate")]
91 pub fn parse_http_date(date: &str) -> Result<Self, httpdate::Error> {
92 httpdate::parse_http_date(date).map(Timestamp::from)
93 }
94
95 pub fn to_bytes(&self) -> [u8; 8] {
97 self.0.to_be_bytes()
98 }
99
100 pub fn as_u64(&self) -> u64 {
102 self.0
103 }
104
105 #[cfg(feature = "httpdate")]
106 pub fn format_http_date(&self) -> String {
107 httpdate::fmt_http_date(self.to_owned().into())
108 }
109}
110
111impl Default for Timestamp {
112 fn default() -> Self {
113 Timestamp::now()
114 }
115}
116
117impl TryFrom<&[u8]> for Timestamp {
118 type Error = TryFromSliceError;
119
120 fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
121 let bytes: [u8; 8] = bytes.try_into()?;
122
123 Ok(bytes.into())
124 }
125}
126
127impl From<Timestamp> for [u8; 8] {
128 fn from(timestamp: Timestamp) -> Self {
129 timestamp.0.to_be_bytes()
130 }
131}
132
133impl From<[u8; 8]> for Timestamp {
134 fn from(bytes: [u8; 8]) -> Self {
135 Self(u64::from_be_bytes(bytes))
136 }
137}
138
139impl From<u64> for Timestamp {
140 fn from(inner: u64) -> Self {
141 Self(inner)
142 }
143}
144
145impl From<&Timestamp> for Timestamp {
146 fn from(timestamp: &Timestamp) -> Self {
147 *timestamp
148 }
149}
150
151impl From<Timestamp> for u64 {
152 fn from(value: Timestamp) -> Self {
153 value.as_u64()
154 }
155}
156
157impl From<Timestamp> for SystemTime {
158 fn from(timestamp: Timestamp) -> Self {
159 let secs = timestamp.0 / 1_000_000; let subsec_nanos = (timestamp.0 % 1_000_000) * 1_000; SystemTime::UNIX_EPOCH + Duration::new(secs, subsec_nanos as u32)
163 }
164}
165
166impl From<SystemTime> for Timestamp {
167 fn from(system_time: SystemTime) -> Self {
168 (system_time
169 .duration_since(SystemTime::UNIX_EPOCH)
170 .expect("time drift")
171 .as_micros() as u64)
172 .into()
173 }
174}
175
176#[cfg(feature = "httpdate")]
177impl From<Timestamp> for httpdate::HttpDate {
178 fn from(value: Timestamp) -> Self {
179 SystemTime::from(value).into()
180 }
181}
182
183#[cfg(feature = "httpdate")]
184impl From<httpdate::HttpDate> for Timestamp {
185 fn from(value: httpdate::HttpDate) -> Self {
186 SystemTime::from(value).into()
187 }
188}
189
190impl Add<u64> for Timestamp {
193 type Output = Timestamp;
194
195 fn add(self, rhs: u64) -> Self::Output {
196 Timestamp(self.0.checked_add(rhs).unwrap_or(u64::MAX))
197 }
198}
199
200impl Sub<u64> for Timestamp {
201 type Output = Timestamp;
202
203 fn sub(self, rhs: u64) -> Self::Output {
204 self.0.saturating_sub(rhs).into()
205 }
206}
207
208impl AddAssign<u64> for Timestamp {
209 fn add_assign(&mut self, other: u64) {
210 self.0 = self.0.checked_add(other).unwrap_or(u64::MAX);
211 }
212}
213
214impl SubAssign<u64> for Timestamp {
215 fn sub_assign(&mut self, other: u64) {
216 self.0 = self.0.saturating_sub(other);
217 }
218}
219
220impl Add<Timestamp> for Timestamp {
221 type Output = Timestamp;
222
223 fn add(self, rhs: Timestamp) -> Self::Output {
224 self + rhs.0
225 }
226}
227
228impl Sub<Timestamp> for Timestamp {
229 type Output = Timestamp;
230
231 fn sub(self, rhs: Timestamp) -> Self::Output {
232 self - rhs.0
233 }
234}
235
236impl AddAssign<Timestamp> for Timestamp {
237 fn add_assign(&mut self, other: Timestamp) {
238 self.0 = self.0.checked_add(other.0).unwrap_or(u64::MAX);
239 }
240}
241
242impl SubAssign<Timestamp> for Timestamp {
243 fn sub_assign(&mut self, other: Timestamp) {
244 self.0 = self.0.saturating_sub(other.0)
245 }
246}
247
248#[cfg(feature = "serde")]
251impl Serialize for Timestamp {
252 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
253 where
254 S: serde::Serializer,
255 {
256 let bytes = self.to_bytes();
257 bytes.serialize(serializer)
258 }
259}
260
261#[cfg(feature = "serde")]
262impl<'de> Deserialize<'de> for Timestamp {
263 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
264 where
265 D: serde::Deserializer<'de>,
266 {
267 let bytes: [u8; 8] = Deserialize::deserialize(deserializer)?;
268 Ok(Timestamp(u64::from_be_bytes(bytes)))
269 }
270}
271
272impl Display for Timestamp {
275 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
276 #[cfg(feature = "base32")]
277 {
278 let bytes: [u8; 8] = self.to_owned().into();
279 f.write_str(&base32::encode(base32::Alphabet::Crockford, &bytes))
280 }
281 #[cfg(not(feature = "base32"))]
282 f.write_str(&format!("Timestamp ({})", self.0))
283 }
284}
285
286impl TryFrom<String> for Timestamp {
287 type Error = InvalidEncoding;
288
289 fn try_from(value: String) -> Result<Self, Self::Error> {
290 #[cfg(feature = "base32")]
291 return match base32::decode(base32::Alphabet::Crockford, &value) {
292 Some(vec) => {
293 let bytes: [u8; 8] = vec.try_into().map_err(|_| InvalidEncoding)?;
294
295 Ok(bytes.into())
296 }
297 None => Err(InvalidEncoding),
298 };
299
300 #[cfg(not(feature = "base32"))]
301 Ok(Self(
302 value[11..value.len() - 1]
303 .parse()
304 .map_err(|_| InvalidEncoding)?,
305 ))
306 }
307}
308
309fn system_time() -> u64 {
311 #[cfg(not(target_arch = "wasm32"))]
312 {
313 SystemTime::now()
314 .duration_since(SystemTime::UNIX_EPOCH)
315 .expect("time drift")
316 .as_micros() as u64
317 }
318 #[cfg(target_arch = "wasm32")]
319 {
320 (js_sys::Date::now() as u64 )
322 * 1000
324 }
325}
326
327#[derive(Debug)]
328pub struct InvalidEncoding;
329
330impl Error for InvalidEncoding {}
331
332impl Display for InvalidEncoding {
333 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
334 f.write_str("Invalid timestamp string encoding")
335 }
336}
337
338#[cfg(test)]
339mod tests {
340 use std::collections::HashSet;
341
342 use super::*;
343
344 #[test]
345 fn strictly_monotonic() {
346 const COUNT: usize = 100;
347
348 let mut set = HashSet::with_capacity(COUNT);
349 let mut vec = Vec::with_capacity(COUNT);
350
351 for _ in 0..COUNT {
352 let timestamp = Timestamp::now();
353
354 set.insert(timestamp.clone());
355 vec.push(timestamp);
356 }
357
358 let mut ordered = vec.clone();
359 ordered.sort();
360
361 assert_eq!(set.len(), COUNT, "unique");
362 assert_eq!(ordered, vec, "ordered");
363 }
364
365 #[test]
366 fn strings() {
367 const COUNT: usize = 100;
368
369 let mut set = HashSet::with_capacity(COUNT);
370 let mut vec = Vec::with_capacity(COUNT);
371
372 for _ in 0..COUNT {
373 let string = Timestamp::now().to_string();
374
375 set.insert(string.clone());
376 vec.push(string)
377 }
378
379 let mut ordered = vec.clone();
380 ordered.sort();
381
382 assert_eq!(set.len(), COUNT, "unique");
383 assert_eq!(ordered, vec, "ordered");
384 }
385
386 #[test]
387 fn to_from_string() {
388 let timestamp = Timestamp::now();
389 let string = timestamp.to_string();
390 let decoded: Timestamp = string.try_into().unwrap();
391
392 assert_eq!(decoded, timestamp)
393 }
394
395 #[cfg(feature = "serde")]
396 #[test]
397 fn serde() {
398 let timestamp = Timestamp::now();
399
400 let serialized = postcard::to_allocvec(×tamp).unwrap();
401
402 assert_eq!(serialized, timestamp.to_bytes());
403
404 let deserialized: Timestamp = postcard::from_bytes(&serialized).unwrap();
405
406 assert_eq!(deserialized, timestamp);
407 }
408
409 #[cfg(feature = "httpdate")]
410 #[test]
411 fn httpdate() {
412 let timestamp = Timestamp::now();
413
414 let httpdate = timestamp.format_http_date();
415
416 assert_eq!(
417 Timestamp::parse_http_date(&httpdate).unwrap().0,
418 timestamp.0 - (timestamp.0 % 1000_000) )
420 }
421}