1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
//! The one wall-clock reading the console's records are timed from.
//!
//! The wall clock is read once, when the hub is built, and every message's
//! time is that reading plus the monotonic time elapsed since. A per-message
//! clock read could fail (a clock set before the epoch) or step backwards;
//! this cannot, and its one failure happens at start, where it is named.
use std::time::{Instant, SystemTime, UNIX_EPOCH};
use super::error::ConsoleError;
/// A wall-clock reading in milliseconds since the epoch, paired with the
/// monotonic instant it was taken at.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct WallBase {
unix_ms: u64,
at: Instant,
}
impl WallBase {
/// Reads the wall clock now.
///
/// # Errors
/// Returns [`ConsoleError::Clock`] when the wall clock reads before the
/// epoch or past what milliseconds in a `u64` can hold.
pub fn now() -> Result<Self, ConsoleError> {
let at = Instant::now();
let since_epoch = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map_err(|error| ConsoleError::Clock {
reason: format!("the wall clock reads before the epoch: {error}"),
})?;
let unix_ms = u64::try_from(since_epoch.as_millis()).map_err(|_| ConsoleError::Clock {
reason: "the wall clock is past u64 milliseconds".to_owned(),
})?;
Ok(Self { unix_ms, at })
}
/// A base fixed at `unix_ms` for `at`, for records whose times must be
/// reproducible.
#[must_use]
pub const fn fixed(unix_ms: u64, at: Instant) -> Self {
Self { unix_ms, at }
}
/// Wall time, in milliseconds since the epoch, of the monotonic instant
/// `seen_at`. An instant before the base reads as the base.
///
/// # Errors
/// Returns [`ConsoleError::Clock`] when the sum passes u64 milliseconds.
pub fn unix_ms_at(&self, seen_at: Instant) -> Result<u64, ConsoleError> {
self.unix_ms_of(seen_at.max(self.at))
}
/// Wall time, in milliseconds since the epoch, of the monotonic instant
/// `at`, before or after the base.
///
/// # Errors
/// Returns [`ConsoleError::Clock`] when it falls outside u64
/// milliseconds.
pub fn unix_ms_of(&self, at: Instant) -> Result<u64, ConsoleError> {
let wall = if at >= self.at {
u64::try_from(at.duration_since(self.at).as_millis())
.ok()
.and_then(|after| self.unix_ms.checked_add(after))
} else {
u64::try_from(self.at.duration_since(at).as_millis())
.ok()
.and_then(|before| self.unix_ms.checked_sub(before))
};
wall.ok_or_else(|| ConsoleError::Clock {
reason: "a time is outside u64 milliseconds since the epoch".to_owned(),
})
}
}