use std::fmt;
use std::time::{Duration, SystemTime, UNIX_EPOCH};
use buffa_types::google::protobuf::Timestamp as Wire;
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub struct Timestamp {
pub seconds: i64,
pub nanos: u32,
}
impl Timestamp {
pub fn to_system_time(self) -> Option<SystemTime> {
let whole = Duration::from_secs(self.seconds.unsigned_abs());
let start = if self.seconds >= 0 {
UNIX_EPOCH.checked_add(whole)?
} else {
UNIX_EPOCH.checked_sub(whole)?
};
start.checked_add(Duration::from_nanos(u64::from(self.nanos)))
}
}
impl From<SystemTime> for Timestamp {
fn from(time: SystemTime) -> Self {
match time.duration_since(UNIX_EPOCH) {
Ok(after) => Self {
seconds: after.as_secs() as i64,
nanos: after.subsec_nanos(),
},
Err(before) => {
let before = before.duration();
let partial = before.subsec_nanos();
Self {
seconds: -(before.as_secs() as i64) - i64::from(partial > 0),
nanos: (1_000_000_000 - partial) % 1_000_000_000,
}
}
}
}
}
impl fmt::Display for Timestamp {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let (year, month, day) = civil_date(self.seconds.div_euclid(86_400));
let second_of_day = self.seconds.rem_euclid(86_400);
let (hour, minute, second) = (second_of_day / 3600, second_of_day / 60 % 60, second_of_day % 60);
write!(f, "{year:04}-{month:02}-{day:02}T{hour:02}:{minute:02}:{second:02}")?;
if self.nanos > 0 {
let fraction = format!("{:09}", self.nanos);
write!(f, ".{}", fraction.trim_end_matches('0'))?;
}
f.write_str("Z")
}
}
impl fmt::Debug for Timestamp {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(self, f)
}
}
fn civil_date(days: i64) -> (i64, i64, i64) {
let shifted = days + 719_468;
let era = shifted.div_euclid(146_097);
let day_of_era = shifted.rem_euclid(146_097);
let year_of_era = (day_of_era - day_of_era / 1_460 + day_of_era / 36_524 - day_of_era / 146_096) / 365;
let day_of_year = day_of_era - (365 * year_of_era + year_of_era / 4 - year_of_era / 100);
let month_from_march = (5 * day_of_year + 2) / 153;
let day = day_of_year - (153 * month_from_march + 2) / 5 + 1;
let month = if month_from_march < 10 {
month_from_march + 3
} else {
month_from_march - 9
};
let year = year_of_era + era * 400 + i64::from(month <= 2);
(year, month, day)
}
pub(crate) fn from_wire(wire: Wire) -> Timestamp {
Timestamp {
seconds: wire.seconds,
nanos: u32::try_from(wire.nanos).unwrap_or(0),
}
}
pub(crate) fn to_wire(time: Timestamp) -> Wire {
Wire {
seconds: time.seconds,
nanos: i32::try_from(time.nanos).unwrap_or(i32::MAX),
..Default::default()
}
}