#![warn(clippy::arithmetic_side_effects)]
use std::time::Duration;
use std::time::SystemTime;
const NANOSECONDS_PER_SECOND: u64 = 1_000_000_000;
const EPOCH_DIFFERENCE_SECONDS: u64 = 11_644_473_600;
const EPOCH_DIFFERENCE_NANOSECONDS: u64 = EPOCH_DIFFERENCE_SECONDS * NANOSECONDS_PER_SECOND;
const EPOCH_DIFFERENCE_TICKS: u64 = EPOCH_DIFFERENCE_NANOSECONDS / NANOSECONDS_PER_TICK;
const NANOSECONDS_PER_TICK: u64 = 100;
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, PartialOrd, Ord)]
pub struct FileTime {
time: u64,
}
impl FileTime {
pub fn from_raw(time: u64) -> Self {
Self { time }
}
pub fn into_raw(self) -> u64 {
self.time
}
}
#[derive(Debug)]
pub enum TryFromFileTimeError {
Adjust,
Nanos,
SystemTime,
Unspecified,
}
impl std::fmt::Display for TryFromFileTimeError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Adjust => f.write_str(
"failed to adjust epochs while converting a file time into a system time",
),
Self::Nanos => f.write_str(
"failed to convert ticks into nanoseconds while converting a file time into a system time"
),
Self::SystemTime => f.write_str(
"failed to convert ticks into a system time while converting a file time into a system time"
),
Self::Unspecified => f.write_str(
"an unspecified error occured while converting a file time into a system time",
),
}
}
}
impl std::error::Error for TryFromFileTimeError {}
impl TryFrom<FileTime> for SystemTime {
type Error = TryFromFileTimeError;
fn try_from(file_time: FileTime) -> Result<Self, Self::Error> {
let ticks = file_time.into_raw();
let adjusted = ticks
.checked_sub(EPOCH_DIFFERENCE_TICKS)
.ok_or(TryFromFileTimeError::Adjust)?;
let nanos = u128::from(adjusted)
.checked_mul(NANOSECONDS_PER_TICK.into())
.ok_or(TryFromFileTimeError::Nanos)?;
let secs_part = u64::try_from(nanos.checked_div(NANOSECONDS_PER_SECOND.into()).unwrap())
.map_err(|_| TryFromFileTimeError::Unspecified)?;
let offset_seconds = Duration::from_secs(secs_part);
let nanos_part =
u64::try_from(nanos.rem_euclid(u128::from(NANOSECONDS_PER_SECOND))).unwrap();
let offset_nanos = Duration::from_nanos(nanos_part);
let offset = offset_seconds
.checked_add(offset_nanos)
.ok_or(TryFromFileTimeError::Unspecified)?;
Self::UNIX_EPOCH
.checked_add(offset)
.ok_or(TryFromFileTimeError::SystemTime)
}
}
#[derive(Debug)]
pub enum TryFromSystemTimeError {
Unspecified,
}
impl std::fmt::Display for TryFromSystemTimeError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Unspecified => f.write_str(
"an unspecified error occured whil converting a system time into a file time",
),
}
}
}
impl std::error::Error for TryFromSystemTimeError {}
impl TryFrom<SystemTime> for FileTime {
type Error = TryFromSystemTimeError;
fn try_from(time: SystemTime) -> Result<Self, Self::Error> {
let nanos = time
.duration_since(SystemTime::UNIX_EPOCH)
.map_err(|_e| TryFromSystemTimeError::Unspecified)?
.as_nanos();
let adjusted = nanos
.checked_add(EPOCH_DIFFERENCE_NANOSECONDS.into())
.ok_or(TryFromSystemTimeError::Unspecified)?;
let ticks_u128 = adjusted.checked_div(NANOSECONDS_PER_TICK.into()).unwrap();
let raw = u64::try_from(ticks_u128).map_err(|_| TryFromSystemTimeError::Unspecified)?;
Ok(Self::from_raw(raw))
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn system_time_round() {
let now = SystemTime::now();
let file_time: FileTime = now.try_into().unwrap();
let round: SystemTime = file_time.try_into().unwrap();
let diff = now.duration_since(round).unwrap() < Duration::from_nanos(NANOSECONDS_PER_TICK);
assert!(diff, "(original) {now:?} != (new) {round:?}");
}
}