use serde::{Deserialize, Serialize};
use std::time::{SystemTime, UNIX_EPOCH};
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
pub struct Timestamp {
micros: i64,
}
impl Timestamp {
pub fn from_micros(micros: i64) -> Self {
Self { micros }
}
pub fn from_millis(millis: i64) -> Self {
Self {
micros: millis.saturating_mul(1000),
}
}
pub fn from_secs(secs: i64) -> Self {
Self {
micros: secs.saturating_mul(1_000_000),
}
}
pub fn now() -> Self {
let duration = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default();
Self {
micros: duration.as_micros() as i64,
}
}
pub fn as_micros(&self) -> i64 {
self.micros
}
pub fn as_micros_u64(&self) -> u64 {
self.micros.max(0) as u64
}
pub fn as_millis(&self) -> i64 {
self.micros / 1000
}
pub fn as_secs(&self) -> i64 {
self.micros / 1_000_000
}
pub fn value(&self) -> i64 {
self.micros
}
pub fn in_range(&self, start: Timestamp, end: Timestamp) -> bool {
self.micros >= start.micros && self.micros <= end.micros
}
pub fn parse_iso(s: &str) -> Option<Timestamp> {
if let Ok(micros) = s.parse::<i64>() {
return Some(Timestamp::from_micros(micros));
}
let (date_part, time_part) = if let Some(idx) = s.find(['T', ' ']) {
(&s[..idx], Some(&s[idx + 1..]))
} else {
(s, None)
};
let dparts: Vec<&str> = date_part.split('-').collect();
if dparts.len() != 3 {
return None;
}
let year: i32 = dparts[0].parse().ok()?;
let month: u32 = dparts[1].parse().ok()?;
let day: u32 = dparts[2].parse().ok()?;
if month < 1 || month > 12 || day < 1 || day > 31 {
return None;
}
let (hour, min, sec) = if let Some(tp) = time_part {
let tparts: Vec<&str> = tp.split(':').collect();
let h: u32 = tparts.first().and_then(|s| s.parse().ok()).unwrap_or(0);
let m: u32 = tparts.get(1).and_then(|s| s.parse().ok()).unwrap_or(0);
let s: u32 = tparts
.get(2)
.and_then(|s| s.split('.').next().and_then(|n| n.parse().ok()))
.unwrap_or(0);
(h, m, s)
} else {
(0, 0, 0)
};
let days = days_from_civil(year, month, day)?;
let micros = days as i64 * 86_400_000_000
+ hour as i64 * 3_600_000_000
+ min as i64 * 60_000_000
+ sec as i64 * 1_000_000;
Some(Timestamp::from_micros(micros))
}
}
fn days_from_civil(y: i32, m: u32, d: u32) -> Option<i64> {
let y = if m <= 2 { y - 1 } else { y };
let era = if y >= 0 { y } else { y - 399 } / 400;
let yoe = (y - era * 400) as u32; let doy = (153 * (if m > 2 { m - 3 } else { m + 9 }) + 2) / 5 + d - 1; let doe = yoe * 365 + yoe / 4 - yoe / 100 + doy; let days = era as i64 * 146_097 + doe as i64 - 719_468;
Some(days)
}
impl Default for Timestamp {
fn default() -> Self {
Self::now()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_timestamp_creation() {
let ts = Timestamp::from_secs(1000);
assert_eq!(ts.as_secs(), 1000);
assert_eq!(ts.as_millis(), 1_000_000);
assert_eq!(ts.as_micros(), 1_000_000_000);
}
#[test]
fn test_timestamp_ordering() {
let ts1 = Timestamp::from_secs(100);
let ts2 = Timestamp::from_secs(200);
assert!(ts1 < ts2);
assert!(ts2 > ts1);
}
#[test]
fn test_timestamp_range() {
let start = Timestamp::from_secs(100);
let end = Timestamp::from_secs(200);
let middle = Timestamp::from_secs(150);
let before = Timestamp::from_secs(50);
assert!(middle.in_range(start, end));
assert!(!before.in_range(start, end));
}
#[test]
fn test_timestamp_now() {
let ts = Timestamp::now();
assert!(ts.as_secs() > 0);
}
}