use std::time::{Duration, SystemTime, UNIX_EPOCH};
use super::{Pack, ReadCursor, Unpack, WriteCursor};
use crate::error::Result;
const EPOCH_DIFF_100NS: u64 = 116_444_736_000_000_000;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct FileTime(
pub u64,
);
impl FileTime {
pub const ZERO: Self = Self(0);
pub fn from_system_time(t: SystemTime) -> Self {
match t.duration_since(UNIX_EPOCH) {
Ok(dur) => {
let intervals = dur.as_nanos() / 100;
Self(intervals as u64 + EPOCH_DIFF_100NS)
}
Err(e) => {
let before = e.duration();
let intervals = before.as_nanos() / 100;
Self(EPOCH_DIFF_100NS.saturating_sub(intervals as u64))
}
}
}
pub fn to_system_time(self) -> Option<SystemTime> {
if self.0 < EPOCH_DIFF_100NS {
return None;
}
let intervals_since_unix = self.0 - EPOCH_DIFF_100NS;
let nanos = (intervals_since_unix as u128) * 100;
let dur = Duration::new(
(nanos / 1_000_000_000) as u64,
(nanos % 1_000_000_000) as u32,
);
Some(UNIX_EPOCH + dur)
}
}
impl Pack for FileTime {
fn pack(&self, cursor: &mut WriteCursor) {
cursor.write_u64_le(self.0);
}
}
impl Unpack for FileTime {
fn unpack(cursor: &mut ReadCursor<'_>) -> Result<Self> {
let val = cursor.read_u64_le()?;
Ok(Self(val))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn zero_filetime() {
assert_eq!(FileTime::ZERO, FileTime(0));
}
#[test]
fn pack_zero() {
let mut w = WriteCursor::new();
FileTime::ZERO.pack(&mut w);
assert_eq!(w.as_bytes(), &[0u8; 8]);
}
#[test]
fn unpack_zero() {
let bytes = [0u8; 8];
let mut r = ReadCursor::new(&bytes);
let ft = FileTime::unpack(&mut r).unwrap();
assert_eq!(ft, FileTime::ZERO);
}
#[test]
fn known_value_2024_01_01() {
let expected_raw: u64 = 133_485_408_000_000_000;
let ft = FileTime(expected_raw);
let mut w = WriteCursor::new();
ft.pack(&mut w);
let mut r = ReadCursor::new(w.as_bytes());
let unpacked = FileTime::unpack(&mut r).unwrap();
assert_eq!(unpacked, ft);
let st = ft.to_system_time().unwrap();
let unix_dur = st.duration_since(UNIX_EPOCH).unwrap();
assert_eq!(unix_dur.as_secs(), 1_704_067_200);
assert_eq!(unix_dur.subsec_nanos(), 0);
}
#[test]
fn from_system_time_roundtrip() {
let unix_secs = 1_704_067_200u64;
let st = UNIX_EPOCH + Duration::from_secs(unix_secs);
let ft = FileTime::from_system_time(st);
assert_eq!(ft.0, 133_485_408_000_000_000);
let st2 = ft.to_system_time().unwrap();
let dur = st2.duration_since(UNIX_EPOCH).unwrap();
assert_eq!(dur.as_secs(), unix_secs);
}
#[test]
fn pre_unix_epoch_returns_none() {
let ft = FileTime(EPOCH_DIFF_100NS - 1);
assert!(ft.to_system_time().is_none());
assert!(FileTime::ZERO.to_system_time().is_none());
}
#[test]
fn unix_epoch_exactly() {
let ft = FileTime(EPOCH_DIFF_100NS);
let st = ft.to_system_time().unwrap();
assert_eq!(st, UNIX_EPOCH);
}
#[test]
fn from_system_time_unix_epoch() {
let ft = FileTime::from_system_time(UNIX_EPOCH);
assert_eq!(ft.0, EPOCH_DIFF_100NS);
}
#[test]
fn pack_unpack_roundtrip() {
let ft = FileTime(133_476_576_000_000_000);
let mut w = WriteCursor::new();
ft.pack(&mut w);
let mut r = ReadCursor::new(w.as_bytes());
let unpacked = FileTime::unpack(&mut r).unwrap();
assert_eq!(unpacked, ft);
}
#[test]
fn unpack_insufficient_bytes() {
let bytes = [0u8; 4]; let mut r = ReadCursor::new(&bytes);
assert!(FileTime::unpack(&mut r).is_err());
}
}