use core::{
ops::{Add, Sub},
time::Duration,
};
use crate::time::{TimeError, TimePoint};
#[cfg(feature = "std")]
use std::time::{SystemTime, UNIX_EPOCH};
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Eq, Ord)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(transparent))]
pub struct Timestamp {
t: u64,
}
impl Timestamp {
#[cfg(feature = "std")]
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
#[must_use]
#[allow(
clippy::expect_used,
reason = "the out-of-range panic is documented above; no meaningful recovery exists"
)]
pub fn now() -> Self {
Self::try_now().expect("system clock outside the representable timestamp range")
}
#[cfg(feature = "std")]
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
pub fn try_now() -> Result<Self, TimeError> {
let since_epoch = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map_err(|_| TimeError::DurationUnderflow)?;
u64::try_from(since_epoch.as_nanos())
.map(|nanos| Timestamp { t: nanos })
.map_err(|_| TimeError::DurationOverflow)
}
#[must_use]
pub const fn zero() -> Self {
Timestamp { t: 0 }
}
#[must_use]
pub const fn from_nanos(nanos: u64) -> Self {
Timestamp { t: nanos }
}
#[must_use]
pub const fn as_nanos(&self) -> u64 {
self.t
}
pub fn as_seconds(&self) -> Result<f64, TimeError> {
const NANOSECONDS_PER_SECOND: f64 = 1_000_000_000.0;
const MAX_ACCURATE_NANOS: u64 = 1 << 53;
if self.t > MAX_ACCURATE_NANOS {
return Err(TimeError::AccuracyLoss);
}
#[allow(clippy::cast_precision_loss)]
Ok(self.t as f64 / NANOSECONDS_PER_SECOND)
}
#[must_use = "this returns the result of the operation, without modifying the original"]
#[allow(clippy::cast_precision_loss)]
pub fn as_seconds_lossy(&self) -> f64 {
const NANOSECONDS_PER_SECOND: f64 = 1_000_000_000.0;
self.t as f64 / NANOSECONDS_PER_SECOND
}
}
impl Sub<Timestamp> for Timestamp {
type Output = Result<Duration, TimeError>;
fn sub(
self,
other: Timestamp,
) -> Self::Output {
self.t
.checked_sub(other.t)
.map(Duration::from_nanos)
.ok_or(TimeError::DurationUnderflow)
}
}
impl Add<Duration> for Timestamp {
type Output = Result<Timestamp, TimeError>;
fn add(
self,
rhs: Duration,
) -> Self::Output {
u64::try_from(rhs.as_nanos())
.ok()
.and_then(|duration_nanos| self.t.checked_add(duration_nanos))
.map(|final_nanos| Timestamp { t: final_nanos })
.ok_or(TimeError::DurationOverflow)
}
}
impl Sub<Duration> for Timestamp {
type Output = Result<Timestamp, TimeError>;
fn sub(
self,
rhs: Duration,
) -> Self::Output {
u64::try_from(rhs.as_nanos())
.ok()
.and_then(|duration_nanos| self.t.checked_sub(duration_nanos))
.map(|final_nanos| Timestamp { t: final_nanos })
.ok_or(TimeError::DurationUnderflow)
}
}
impl TimePoint for Timestamp {
fn duration_since(
self,
earlier: Self,
) -> Result<Duration, TimeError> {
self - earlier
}
fn checked_sub(
self,
rhs: Duration,
) -> Result<Self, TimeError> {
self - rhs
}
fn as_seconds_lossy(self) -> f64 {
Timestamp::as_seconds_lossy(&self)
}
}
#[cfg(test)]
mod tests;