use std::fmt;
use std::time::{Duration, SystemTime, UNIX_EPOCH};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Default)]
pub struct Timestamp(i64);
impl Timestamp {
pub const fn from_nanos(nanos: i64) -> Self {
Self(nanos)
}
pub const fn from_micros(micros: i64) -> Self {
Self(micros.saturating_mul(1_000))
}
pub const fn from_millis(millis: i64) -> Self {
Self(millis.saturating_mul(1_000_000))
}
pub const fn from_secs(secs: i64) -> Self {
Self(secs.saturating_mul(1_000_000_000))
}
pub const fn as_nanos(self) -> i64 {
self.0
}
pub const fn as_millis(self) -> i64 {
self.0 / 1_000_000
}
pub const fn as_secs(self) -> i64 {
self.0 / 1_000_000_000
}
pub fn now() -> Self {
Self::since_epoch(
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or(Duration::ZERO),
)
}
fn since_epoch(elapsed: Duration) -> Self {
Self(i64::try_from(elapsed.as_nanos()).unwrap_or(i64::MAX))
}
pub fn into_system_time(self) -> Option<SystemTime> {
u64::try_from(self.0)
.ok()
.map(|nanos| UNIX_EPOCH + Duration::from_nanos(nanos))
}
}
impl fmt::Display for Timestamp {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let instant = chrono::DateTime::from_timestamp_nanos(self.0);
write!(f, "{}", instant.format("%Y-%m-%dT%H:%M:%S%.3fZ"))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn every_resolution_lands_on_the_same_instant() {
let secs = Timestamp::from_secs(1_700_000_000);
assert_eq!(secs, Timestamp::from_millis(1_700_000_000_000));
assert_eq!(secs, Timestamp::from_micros(1_700_000_000_000_000));
assert_eq!(secs, Timestamp::from_nanos(1_700_000_000_000_000_000));
}
#[test]
fn downscaling_truncates_instead_of_rounding() {
let ts = Timestamp::from_nanos(1_999_999_999);
assert_eq!(ts.as_secs(), 1);
assert_eq!(ts.as_millis(), 1_999);
}
#[test]
fn ordering_compares_instants_not_source_exchanges() {
let earlier = Timestamp::from_millis(1_700_000_000_000);
let later = Timestamp::from_nanos(1_700_000_000_000_000_001);
assert!(earlier < later);
}
#[test]
fn a_clock_past_the_representable_range_saturates_rather_than_wrapping() {
let ordinary = Duration::from_secs(1_700_000_000);
assert_eq!(
Timestamp::since_epoch(ordinary),
Timestamp::from_secs(1_700_000_000)
);
for beyond in [
Duration::from_nanos(u64::MAX),
Duration::from_secs(u64::MAX),
Duration::MAX,
] {
assert_eq!(
Timestamp::since_epoch(beyond),
Timestamp::from_nanos(i64::MAX),
"{beyond:?}"
);
}
}
#[test]
fn pre_epoch_timestamps_do_not_panic_on_conversion() {
assert!(Timestamp::from_secs(-1).into_system_time().is_none());
assert!(Timestamp::from_secs(0).into_system_time().is_some());
}
}