Skip to main content

ntfs_core/
time.rs

1//! Windows `FILETIME` — the timestamp format used throughout NTFS.
2//!
3//! A `FILETIME` is a 64-bit count of 100-nanosecond intervals since
4//! 1601-01-01 00:00:00 UTC. Timestamps are trivially forgeable (timestomping),
5//! so this type stays a thin, lossless wrapper over the raw value and offers
6//! conversions for display — it never normalises or discards the original.
7
8/// 100-ns intervals between the FILETIME epoch (1601) and the Unix epoch (1970).
9const FILETIME_TO_UNIX_100NS: i128 = 116_444_736_000_000_000;
10
11/// A raw Windows `FILETIME` (100-ns ticks since 1601-01-01 UTC).
12#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
13pub struct Filetime(pub u64);
14
15impl Filetime {
16    /// Read a little-endian `FILETIME` from the start of `bytes` (needs 8 bytes).
17    #[must_use]
18    pub fn from_le(bytes: &[u8; 8]) -> Self {
19        Filetime(u64::from_le_bytes(*bytes))
20    }
21
22    /// `true` for the all-zero value (an unset timestamp).
23    #[must_use]
24    pub fn is_zero(&self) -> bool {
25        self.0 == 0
26    }
27
28    /// Whole seconds since the Unix epoch (may be negative for pre-1970 times).
29    #[must_use]
30    pub fn to_unix_seconds(&self) -> i64 {
31        let ticks_since_unix = i128::from(self.0) - FILETIME_TO_UNIX_100NS;
32        (ticks_since_unix / 10_000_000) as i64
33    }
34
35    /// Nanoseconds since the Unix epoch (`i128` to span the full FILETIME range).
36    #[must_use]
37    pub fn to_unix_nanos(&self) -> i128 {
38        (i128::from(self.0) - FILETIME_TO_UNIX_100NS) * 100
39    }
40}
41
42#[cfg(test)]
43mod tests {
44    use super::*;
45
46    #[test]
47    fn zero_is_unset() {
48        assert!(Filetime(0).is_zero());
49        assert!(!Filetime(1).is_zero());
50    }
51
52    #[test]
53    fn filetime_epoch_maps_to_unix_zero() {
54        // 116444736000000000 ticks = exactly the Unix epoch.
55        assert_eq!(Filetime(116_444_736_000_000_000).to_unix_seconds(), 0);
56    }
57
58    #[test]
59    fn known_date_converts() {
60        // 2010-01-01 00:00:00 UTC → unix 1262304000.
61        let ft = Filetime(129_067_776_000_000_000);
62        assert_eq!(ft.to_unix_seconds(), 1_262_304_000);
63    }
64
65    #[test]
66    fn pre_unix_epoch_is_negative() {
67        // One second before the Unix epoch.
68        let ft = Filetime(116_444_736_000_000_000 - 10_000_000);
69        assert_eq!(ft.to_unix_seconds(), -1);
70    }
71
72    #[test]
73    fn nanos_granularity() {
74        // One 100-ns tick past the epoch = 100 ns.
75        let ft = Filetime(116_444_736_000_000_000 + 1);
76        assert_eq!(ft.to_unix_nanos(), 100);
77    }
78
79    #[test]
80    fn from_le_reads_little_endian() {
81        let ft = Filetime::from_le(&1u64.to_le_bytes());
82        assert_eq!(ft.0, 1);
83    }
84}