1#![warn(clippy::arithmetic_side_effects)]
2
3use std::time::Duration;
4use std::time::SystemTime;
5
6const NANOSECONDS_PER_SECOND: u64 = 1_000_000_000;
8
9const EPOCH_DIFFERENCE_SECONDS: u64 = 11_644_473_600;
13
14const EPOCH_DIFFERENCE_NANOSECONDS: u64 = EPOCH_DIFFERENCE_SECONDS * NANOSECONDS_PER_SECOND;
16
17const EPOCH_DIFFERENCE_TICKS: u64 = EPOCH_DIFFERENCE_NANOSECONDS / NANOSECONDS_PER_TICK;
19
20const NANOSECONDS_PER_TICK: u64 = 100;
22
23#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, PartialOrd, Ord)]
29pub struct FileTime {
30 time: u64,
31}
32
33impl FileTime {
34 pub fn from_raw(time: u64) -> Self {
36 Self { time }
37 }
38
39 pub fn into_raw(self) -> u64 {
41 self.time
42 }
43}
44
45#[derive(Debug)]
47pub enum TryFromFileTimeError {
48 Adjust,
50
51 Nanos,
53
54 SystemTime,
56
57 Unspecified,
59}
60
61impl std::fmt::Display for TryFromFileTimeError {
62 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
63 match self {
64 Self::Adjust => f.write_str(
65 "failed to adjust epochs while converting a file time into a system time",
66 ),
67 Self::Nanos => f.write_str(
68 "failed to convert ticks into nanoseconds while converting a file time into a system time"
69 ),
70 Self::SystemTime => f.write_str(
71 "failed to convert ticks into a system time while converting a file time into a system time"
72 ),
73 Self::Unspecified => f.write_str(
74 "an unspecified error occured while converting a file time into a system time",
75 ),
76 }
77 }
78}
79
80impl std::error::Error for TryFromFileTimeError {}
81
82impl TryFrom<FileTime> for SystemTime {
83 type Error = TryFromFileTimeError;
84
85 fn try_from(file_time: FileTime) -> Result<Self, Self::Error> {
86 let ticks = file_time.into_raw();
89
90 let adjusted = ticks
95 .checked_sub(EPOCH_DIFFERENCE_TICKS)
96 .ok_or(TryFromFileTimeError::Adjust)?;
97
98 let nanos = u128::from(adjusted)
100 .checked_mul(NANOSECONDS_PER_TICK.into())
101 .ok_or(TryFromFileTimeError::Nanos)?;
102
103 let secs_part = u64::try_from(nanos.checked_div(NANOSECONDS_PER_SECOND.into()).unwrap())
109 .map_err(|_| TryFromFileTimeError::Unspecified)?;
110 let offset_seconds = Duration::from_secs(secs_part);
111 let nanos_part =
113 u64::try_from(nanos.rem_euclid(u128::from(NANOSECONDS_PER_SECOND))).unwrap();
114 let offset_nanos = Duration::from_nanos(nanos_part);
115 let offset = offset_seconds
116 .checked_add(offset_nanos)
117 .ok_or(TryFromFileTimeError::Unspecified)?;
118
119 Self::UNIX_EPOCH
121 .checked_add(offset)
122 .ok_or(TryFromFileTimeError::SystemTime)
123 }
124}
125
126#[derive(Debug)]
128pub enum TryFromSystemTimeError {
129 Unspecified,
131}
132
133impl std::fmt::Display for TryFromSystemTimeError {
134 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
135 match self {
136 Self::Unspecified => f.write_str(
137 "an unspecified error occured whil converting a system time into a file time",
138 ),
139 }
140 }
141}
142
143impl std::error::Error for TryFromSystemTimeError {}
144
145impl TryFrom<SystemTime> for FileTime {
146 type Error = TryFromSystemTimeError;
147
148 fn try_from(time: SystemTime) -> Result<Self, Self::Error> {
149 let nanos = time
154 .duration_since(SystemTime::UNIX_EPOCH)
155 .map_err(|_e| TryFromSystemTimeError::Unspecified)?
156 .as_nanos();
157
158 let adjusted = nanos
160 .checked_add(EPOCH_DIFFERENCE_NANOSECONDS.into())
161 .ok_or(TryFromSystemTimeError::Unspecified)?;
162
163 let ticks_u128 = adjusted.checked_div(NANOSECONDS_PER_TICK.into()).unwrap();
165
166 let raw = u64::try_from(ticks_u128).map_err(|_| TryFromSystemTimeError::Unspecified)?;
169
170 Ok(Self::from_raw(raw))
171 }
172}
173
174#[cfg(test)]
175mod test {
176 use super::*;
177
178 #[test]
179 fn system_time_round() {
180 let now = SystemTime::now();
181 let file_time: FileTime = now.try_into().unwrap();
182 let round: SystemTime = file_time.try_into().unwrap();
183
184 let diff = now.duration_since(round).unwrap() < Duration::from_nanos(NANOSECONDS_PER_TICK);
186 assert!(diff, "(original) {now:?} != (new) {round:?}");
187
188 }
190}