use serde::{Deserialize, Serialize};
use std::time::Duration;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub struct Timestamp(i64);
impl Timestamp {
pub fn now() -> Self {
Self(chrono::Utc::now().timestamp_millis())
}
pub fn from_millis(ms: i64) -> Self {
Self(ms)
}
pub fn as_millis(&self) -> i64 {
self.0
}
pub fn elapsed(&self) -> Duration {
let now = chrono::Utc::now().timestamp_millis();
Duration::from_millis((now - self.0).max(0) as u64)
}
pub fn is_expired(&self, ttl: Duration) -> bool {
self.elapsed() > ttl
}
}
impl std::fmt::Display for Timestamp {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}ms", self.0)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn timestamp_now_returns_milliseconds() {
let ts = Timestamp::now();
assert!(ts.as_millis() > 1_577_836_800_000);
}
#[test]
fn timestamp_ordering() {
let t1 = Timestamp::now();
std::thread::sleep(Duration::from_millis(2));
let t2 = Timestamp::now();
assert!(t2 > t1);
}
#[test]
fn timestamp_elapsed_since() {
let t1 = Timestamp::now();
std::thread::sleep(Duration::from_millis(10));
let elapsed = t1.elapsed();
assert!(elapsed.as_millis() >= 10);
}
#[test]
fn timestamp_from_millis_roundtrip() {
let ms = 1706500000000_i64;
let ts = Timestamp::from_millis(ms);
assert_eq!(ts.as_millis(), ms);
}
#[test]
fn timestamp_is_expired_after_duration() {
let ts = Timestamp::from_millis(chrono::Utc::now().timestamp_millis() - 5000);
let ttl = Duration::from_secs(3);
assert!(ts.is_expired(ttl));
}
#[test]
fn timestamp_is_not_expired_within_duration() {
let ts = Timestamp::now();
let ttl = Duration::from_secs(3600);
assert!(!ts.is_expired(ttl));
}
}