use std::fmt;
use std::time::{SystemTime, UNIX_EPOCH};
use thiserror::Error;
pub(crate) const NONE_SENTINEL: u64 = u64::MAX;
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ClockTime(u64);
#[derive(Debug, Error, PartialEq, Eq)]
pub enum ClockTimeError {
#[error("system clock before Unix epoch")]
BeforeEpoch,
#[error("reserved sentinel value {0} (u64::MAX)")]
ReservedSentinel(u64),
}
impl ClockTime {
pub const fn try_from_millis(millis: u64) -> Result<Self, ClockTimeError> {
if millis == NONE_SENTINEL {
Err(ClockTimeError::ReservedSentinel(millis))
} else {
Ok(Self(millis))
}
}
pub fn now() -> Result<Self, ClockTimeError> {
let millis = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map_err(|_| ClockTimeError::BeforeEpoch)?
.as_millis();
let truncated = u64::try_from(millis).unwrap_or(NONE_SENTINEL - 1);
Self::try_from_millis(truncated)
}
#[must_use]
pub const fn as_millis(self) -> u64 {
self.0
}
}
impl fmt::Display for ClockTime {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}ms", self.0)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn sentinel_is_rejected() {
assert!(matches!(
ClockTime::try_from_millis(u64::MAX),
Err(ClockTimeError::ReservedSentinel(_))
));
}
#[test]
fn ordering_is_numeric() {
let a = ClockTime::try_from_millis(100).expect("non-sentinel");
let b = ClockTime::try_from_millis(200).expect("non-sentinel");
assert!(a < b);
}
#[test]
fn now_is_close_to_epoch_plus_wallclock() {
let t = ClockTime::now().expect("wall clock sane");
assert!(t.as_millis() > 1_577_836_800_000);
}
}