smb_dtyp/binrw_util/
file_time.rs

1//! FileTime is a wrapper around a u64 that represents a file time,
2//! According to the FILETIME structure [MS-DTYP] 2.3.3.
3
4use std::fmt::Display;
5use std::ops::Deref;
6use std::time::{Duration, SystemTime};
7
8use binrw::prelude::*;
9use time::PrimitiveDateTime;
10use time::macros::datetime;
11
12#[derive(BinRead, BinWrite, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)]
13pub struct FileTime {
14    /// 100-nanosecond intervals since January 1, 1601 (UTC)
15    value: u64,
16}
17
18impl FileTime {
19    const EPOCH: PrimitiveDateTime = datetime!(1601-01-01 00:00:00);
20    const SCALE_VALUE_TO_NANOS: u64 = 100;
21    const SCALE_VALUE_TO_SECS: u64 = 1_000_000_000 / Self::SCALE_VALUE_TO_NANOS;
22
23    pub fn date_time(&self) -> PrimitiveDateTime {
24        let duration = core::time::Duration::from_nanos(self.value * Self::SCALE_VALUE_TO_NANOS);
25        Self::EPOCH + duration
26    }
27}
28
29impl Display for FileTime {
30    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
31        self.date_time().fmt(f)
32    }
33}
34
35impl std::fmt::Debug for FileTime {
36    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
37        f.debug_tuple("FileTime").field(&self.date_time()).finish()
38    }
39}
40
41impl From<u64> for FileTime {
42    fn from(value: u64) -> Self {
43        Self { value }
44    }
45}
46
47impl From<PrimitiveDateTime> for FileTime {
48    fn from(dt: PrimitiveDateTime) -> Self {
49        let duration = dt - Self::EPOCH;
50        Self {
51            value: duration.whole_nanoseconds() as u64 / Self::SCALE_VALUE_TO_NANOS,
52        }
53    }
54}
55
56impl Deref for FileTime {
57    type Target = u64;
58
59    fn deref(&self) -> &Self::Target {
60        &self.value
61    }
62}
63
64impl From<FileTime> for SystemTime {
65    fn from(src: FileTime) -> SystemTime {
66        let epoch = SystemTime::from(FileTime::EPOCH.as_utc());
67        let secs = src.value / FileTime::SCALE_VALUE_TO_SECS;
68        let nanos = src.value % FileTime::SCALE_VALUE_TO_SECS * FileTime::SCALE_VALUE_TO_NANOS;
69        epoch + Duration::new(secs, nanos as u32)
70    }
71}
72
73#[cfg(test)]
74mod tests {
75    use super::*;
76    use time::macros::datetime;
77
78    const TEST_VAL1_U64: u64 = 133818609802776324;
79    const TEST_VAL1_DT: PrimitiveDateTime = datetime!(2025-01-20 15:36:20.277632400);
80
81    #[test]
82    pub fn test_file_time_from_u64_correct() {
83        assert_eq!(FileTime::from(TEST_VAL1_U64).date_time(), TEST_VAL1_DT);
84        let result: SystemTime = FileTime::from(TEST_VAL1_U64).into();
85        assert_eq!(time::UtcDateTime::from(result), TEST_VAL1_DT.as_utc());
86    }
87
88    #[test]
89    pub fn test_file_time_from_datetime_correct() {
90        assert_eq!(*FileTime::from(TEST_VAL1_DT), TEST_VAL1_U64)
91    }
92}