use serde::{Deserialize, Serialize};
use std::fmt;
use std::time::{SystemTime, UNIX_EPOCH};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[serde(transparent)]
pub struct Timestamp(u64);
impl Timestamp {
#[must_use]
pub const fn from_millis(millis: u64) -> Self {
Self(millis)
}
#[must_use]
pub fn now() -> Self {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.map_or(Self(0), |d| {
Self(u64::try_from(d.as_millis()).unwrap_or(u64::MAX))
})
}
#[must_use]
pub const fn as_millis(&self) -> u64 {
self.0
}
#[must_use]
pub const fn as_secs(&self) -> u64 {
self.0 / 1000
}
#[must_use]
pub const fn duration_since(&self, other: Self) -> Option<u64> {
if self.0 >= other.0 {
Some(self.0 - other.0)
} else {
None
}
}
#[must_use]
pub const fn add_millis(&self, millis: u64) -> Self {
Self(self.0.saturating_add(millis))
}
#[must_use]
pub const fn sub_millis(&self, millis: u64) -> Self {
Self(self.0.saturating_sub(millis))
}
}
impl Default for Timestamp {
fn default() -> Self {
Self::now()
}
}
impl fmt::Display for Timestamp {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl From<u64> for Timestamp {
fn from(millis: u64) -> Self {
Self::from_millis(millis)
}
}
impl From<Timestamp> for u64 {
fn from(ts: Timestamp) -> Self {
ts.0
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn from_millis_creates_timestamp() {
let ts = Timestamp::from_millis(1_704_067_200_000);
assert_eq!(ts.as_millis(), 1_704_067_200_000);
}
#[test]
fn now_creates_current_timestamp() {
let ts = Timestamp::now();
assert!(ts.as_millis() > 0);
}
#[test]
fn as_secs_truncates() {
let ts = Timestamp::from_millis(5500);
assert_eq!(ts.as_secs(), 5);
}
#[test]
fn timestamps_are_ordered() {
let ts1 = Timestamp::from_millis(1000);
let ts2 = Timestamp::from_millis(2000);
assert!(ts1 < ts2);
}
#[test]
fn conversion_to_u64() {
let ts = Timestamp::from_millis(12345);
let millis: u64 = ts.into();
assert_eq!(millis, 12345);
}
#[test]
fn conversion_from_u64() {
let ts: Timestamp = 12345_u64.into();
assert_eq!(ts.as_millis(), 12345);
}
#[test]
fn duration_since() {
let ts1 = Timestamp::from_millis(1000);
let ts2 = Timestamp::from_millis(2000);
assert_eq!(ts2.duration_since(ts1), Some(1000));
assert_eq!(ts1.duration_since(ts2), None);
}
#[test]
fn add_millis() {
let ts = Timestamp::from_millis(1000);
assert_eq!(ts.add_millis(500).as_millis(), 1500);
}
#[test]
fn sub_millis() {
let ts = Timestamp::from_millis(1000);
assert_eq!(ts.sub_millis(500).as_millis(), 500);
}
#[test]
fn sub_millis_saturates() {
let ts = Timestamp::from_millis(100);
assert_eq!(ts.sub_millis(500).as_millis(), 0);
}
#[test]
fn serde_roundtrip() -> Result<(), serde_json::Error> {
let ts = Timestamp::from_millis(1_704_067_200_000);
let json = serde_json::to_string(&ts)?;
let restored: Timestamp = serde_json::from_str(&json)?;
assert_eq!(ts, restored);
Ok(())
}
#[test]
fn serde_is_transparent() -> Result<(), serde_json::Error> {
let ts = Timestamp::from_millis(12345);
let json = serde_json::to_string(&ts)?;
assert_eq!(json, "12345");
Ok(())
}
}