use crate::{color::Color, error::*, io::BoundedFile, make_display_color};
use std::{fmt::Display, io::Read, time::SystemTime};
#[derive(Debug, Copy, Clone, Eq, Ord, PartialEq, PartialOrd)]
pub struct Timestamp(u64);
fn epoch_to_parts(epoch_secs: u64) -> (u64, u8, u8, u8, u8, u8) {
let secs_per_day: u64 = 86400;
let days_since_epoch = epoch_secs / secs_per_day;
let time_of_day = epoch_secs % secs_per_day;
let hour = (time_of_day / 3600) as u8;
let min = ((time_of_day % 3600) / 60) as u8;
let sec = (time_of_day % 60) as u8;
let z = days_since_epoch as i64 + 719_468;
let era = if z >= 0 { z } else { z - 146_096 } / 146_097;
let doe = z - era * 146_097; let yoe = (doe - doe / 1_460 + doe / 36_524 - doe / 146_096) / 365; let y = yoe + era * 400;
let doy = doe - (365 * yoe + yoe / 4 - yoe / 100); let mp = (5 * doy + 2) / 153; let day = (doy - (153 * mp + 2) / 5 + 1) as u8; let month = (mp + if mp < 10 { 3 } else { -9 }) as u8; let year = (y + if month <= 2 { 1 } else { 0 }) as u64;
(year, month, day, hour, min, sec)
}
impl Display for Timestamp {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
let (year, month, day, hour, min, sec) = epoch_to_parts(self.0);
write!(
f,
"{:04}-{:02}-{:02}T{:02}:{:02}:{:02}Z",
year, month, day, hour, min, sec
)
}
}
make_display_color!(Timestamp, |s, f| {
write!(f, "{}{}{}", Color::Timestamp, s, Color::Default)
});
impl Timestamp {
pub fn from_system_time(time: SystemTime) -> Result<Self, std::time::SystemTimeError> {
time.duration_since(SystemTime::UNIX_EPOCH)
.map(|d| d.as_secs())
.map(Timestamp)
}
pub fn serialize<W: std::io::Write>(&self, w: &mut W) -> Result<(), AnonLocErr> {
w.write_all(&self.0.to_le_bytes())
.map_err(AnonLocErr::Write)?;
Ok(())
}
pub fn deserialize(bytes: &[u8; std::mem::size_of::<u64>()]) -> Self {
Timestamp(u64::from_le_bytes(*bytes))
}
pub fn now() -> Result<Self, std::time::SystemTimeError> {
Self::from_system_time(SystemTime::now())
}
}
pub trait HandleTimestamp {
fn strip_timestamp(self) -> Result<(Self, Timestamp), AnonLocErr>
where
Self: std::marker::Sized;
}
impl HandleTimestamp for BoundedFile {
fn strip_timestamp(mut self: BoundedFile) -> Result<(Self, Timestamp), AnonLocErr>
where
Self: std::marker::Sized,
{
let mut buf = [b'\0'; std::mem::size_of::<u64>()];
self.read_exact(&mut buf).map_err(AnonLocErr::Read)?;
let timestamp = Timestamp::deserialize(&buf);
self.increase_lower_bound_by(buf.len() as u64)?;
Ok((self, timestamp))
}
}
#[cfg(test)]
mod tests {
use super::*;
fn ts(secs: u64) -> String {
Timestamp(secs).to_string()
}
#[test]
fn epoch_zero() {
assert_eq!(ts(0), "1970-01-01T00:00:00Z");
}
#[test]
fn known_date() {
assert_eq!(ts(1740234745), "2025-02-22T14:32:25Z");
}
#[test]
fn leap_year_feb_29_2000() {
assert_eq!(ts(951782400), "2000-02-29T00:00:00Z");
}
#[test]
fn leap_year_feb_29_2024() {
assert_eq!(ts(1709208000), "2024-02-29T12:00:00Z");
}
#[test]
fn year_boundary() {
assert_eq!(ts(1735689599), "2024-12-31T23:59:59Z");
assert_eq!(ts(1735689600), "2025-01-01T00:00:00Z");
}
#[test]
fn end_of_day() {
assert_eq!(ts(86399), "1970-01-01T23:59:59Z");
}
#[test]
fn year_2100_plus() {
assert_eq!(ts(4102444800), "2100-01-01T00:00:00Z");
}
#[test]
fn non_leap_century_2100() {
assert_eq!(ts(4107542400), "2100-03-01T00:00:00Z");
assert_eq!(ts(4107542399), "2100-02-28T23:59:59Z");
}
#[test]
fn leap_day_boundary_2024() {
assert_eq!(ts(1709164799), "2024-02-28T23:59:59Z");
assert_eq!(ts(1709164800), "2024-02-29T00:00:00Z");
assert_eq!(ts(1709251199), "2024-02-29T23:59:59Z");
assert_eq!(ts(1709251200), "2024-03-01T00:00:00Z");
}
#[test]
fn non_leap_day_boundary_2023() {
assert_eq!(ts(1677628799), "2023-02-28T23:59:59Z");
assert_eq!(ts(1677628800), "2023-03-01T00:00:00Z");
}
#[test]
fn four_hundred_year_rule_2400() {
assert_eq!(ts(13574563200), "2400-02-29T00:00:00Z");
assert_eq!(ts(13574649600), "2400-03-01T00:00:00Z");
}
#[test]
fn month_boundary_april_to_may_2025() {
assert_eq!(ts(1746057599), "2025-04-30T23:59:59Z");
assert_eq!(ts(1746057600), "2025-05-01T00:00:00Z");
}
#[test]
fn serialize_deserialize_roundtrip() {
let original = Timestamp(1735689600); let mut buf = Vec::new();
original.serialize(&mut buf).unwrap();
assert_eq!(buf.len(), std::mem::size_of::<u64>());
let bytes: [u8; 8] = buf.as_slice().try_into().unwrap();
let parsed = Timestamp::deserialize(&bytes);
assert_eq!(parsed, original);
}
}