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    /// Converts the FileTime to a PrimitiveDateTime.
24    ///
25    /// > This is a legacy method. Use `FileTime::Into<PrimitiveDateTime>` instead.
26    pub fn date_time(&self) -> PrimitiveDateTime {
27        let duration = core::time::Duration::from_nanos(self.value * Self::SCALE_VALUE_TO_NANOS);
28        Self::EPOCH + duration
29    }
30
31    /// A constant representing a zero FileTime value.
32    pub const ZERO: FileTime = FileTime { value: 0 };
33
34    /// Returns true if the FileTime value is zero.
35    ///
36    /// This is usually an indicator of "no time" or "not set".
37    pub fn is_zero(&self) -> bool {
38        self.value == 0
39    }
40
41    /// Returns the duration since the FILETIME epoch (January 1, 1601).
42    ///
43    /// This is useful for cases where the file time represents a duration offset.
44    pub fn since_epoch(&self) -> Duration {
45        let secs = self.value / Self::SCALE_VALUE_TO_SECS;
46        let nanos = self.value % Self::SCALE_VALUE_TO_SECS * Self::SCALE_VALUE_TO_NANOS;
47        Duration::new(secs, nanos as u32)
48    }
49}
50
51impl Display for FileTime {
52    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
53        self.date_time().fmt(f)
54    }
55}
56
57impl std::fmt::Debug for FileTime {
58    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
59        f.debug_tuple("FileTime").field(&self.date_time()).finish()
60    }
61}
62
63impl From<u64> for FileTime {
64    fn from(value: u64) -> Self {
65        Self { value }
66    }
67}
68
69impl From<PrimitiveDateTime> for FileTime {
70    fn from(dt: PrimitiveDateTime) -> Self {
71        let duration = dt - Self::EPOCH;
72        Self {
73            value: duration.whole_nanoseconds() as u64 / Self::SCALE_VALUE_TO_NANOS,
74        }
75    }
76}
77
78impl From<FileTime> for PrimitiveDateTime {
79    fn from(val: FileTime) -> Self {
80        val.date_time()
81    }
82}
83
84impl Deref for FileTime {
85    type Target = u64;
86
87    fn deref(&self) -> &Self::Target {
88        &self.value
89    }
90}
91
92impl From<FileTime> for SystemTime {
93    fn from(src: FileTime) -> SystemTime {
94        let epoch = SystemTime::from(FileTime::EPOCH.as_utc());
95        epoch + src.since_epoch()
96    }
97}
98
99#[cfg(test)]
100mod tests {
101    use super::*;
102    use time::macros::datetime;
103
104    const TEST_VAL1_U64: u64 = 133818609802776324;
105    const TEST_VAL1_DT: PrimitiveDateTime = datetime!(2025-01-20 15:36:20.277632400);
106
107    #[test]
108    pub fn test_file_time_from_u64_correct() {
109        assert_eq!(FileTime::from(TEST_VAL1_U64).date_time(), TEST_VAL1_DT);
110        let result: SystemTime = FileTime::from(TEST_VAL1_U64).into();
111        assert_eq!(time::UtcDateTime::from(result), TEST_VAL1_DT.as_utc());
112    }
113
114    #[test]
115    pub fn test_file_time_from_datetime_correct() {
116        assert_eq!(*FileTime::from(TEST_VAL1_DT), TEST_VAL1_U64)
117    }
118
119    #[test]
120    pub fn test_zero_file_time() {
121        let ft = FileTime::ZERO;
122        assert!(ft.is_zero());
123        assert_eq!(ft.date_time(), FileTime::EPOCH);
124    }
125}