// Copyright 2021 Colin Finck // SPDX-License-Identifier: MIT OR Apache-2.0 use binread::BinRead; use derive_more::From; #[cfg(any(feature = "chrono", feature = "std"))] use core::convert::TryFrom; #[cfg(feature = "chrono")] use { crate::error::NtfsError, chrono::{DateTime, Datelike, NaiveDate, Timelike, Utc}, }; #[cfg(feature = "std")] use std::time::{SystemTime, SystemTimeError}; /// Number of days between 0001-01-01 and 1601-01-01. #[cfg(feature = "chrono")] const DAYS_FROM_0001_TO_1601: i32 = 584389; /// Difference in 100-nanosecond intervals between the Windows/NTFS epoch (1601-01-01) and the Unix epoch (1970-01-01). #[cfg(feature = "std")] const EPOCH_DIFFERENCE_IN_INTERVALS: i64 = 116_444_736_000_000_000; /// Number of 100-nanosecond intervals in a second. #[cfg(any(feature = "chrono", feature = "std"))] const INTERVALS_PER_SECOND: u64 = 10_000_000; /// Number of 100-nanosecond intervals in a day. #[cfg(feature = "chrono")] const INTERVALS_PER_DAY: u64 = 24 * 60 * 60 * INTERVALS_PER_SECOND; /// An NTFS timestamp, used for expressing file times. /// /// NTFS (and the Windows NT line of operating systems) represent time as an unsigned 64-bit integer /// counting the number of 100-nanosecond intervals since January 1, 1601. #[derive(BinRead, Clone, Copy, Debug, Eq, From, Ord, PartialEq, PartialOrd)] pub struct NtfsTime(u64); impl NtfsTime { /// Returns the stored NT timestamp (number of 100-nanosecond intervals since January 1, 1601). pub fn nt_timestamp(&self) -> u64 { self.0 } } #[cfg(feature = "chrono")] #[cfg_attr(docsrs, doc(cfg(feature = "chrono")))] impl TryFrom> for NtfsTime { type Error = NtfsError; fn try_from(dt: DateTime) -> Result { // First do the time calculations, which safely fit into a u64. let mut intervals = dt.hour() as u64; intervals *= 60; intervals += dt.minute() as u64; intervals *= 60; intervals += dt.second() as u64; intervals *= INTERVALS_PER_SECOND; intervals += dt.nanosecond() as u64 / 100; // Now do checked arithmetics for the day calculations, which may // exceed the lower bounds (years before 1601) or upper bounds // (dates after approximately 28 May 60056). let num_days_from_ce = dt.num_days_from_ce(); let num_days_from_1601 = num_days_from_ce .checked_sub(DAYS_FROM_0001_TO_1601) .ok_or(NtfsError::InvalidTime)?; let intervals_days = INTERVALS_PER_DAY .checked_mul(num_days_from_1601 as u64) .ok_or(NtfsError::InvalidTime)?; intervals = intervals .checked_add(intervals_days) .ok_or(NtfsError::InvalidTime)?; Ok(Self(intervals)) } } #[cfg(feature = "chrono")] #[cfg_attr(docsrs, doc(cfg(feature = "chrono")))] impl From for DateTime { fn from(nt: NtfsTime) -> DateTime { let mut remainder = nt.nt_timestamp(); let nano = (remainder % INTERVALS_PER_SECOND) as u32 * 100; remainder /= INTERVALS_PER_SECOND; let sec = (remainder % 60) as u32; remainder /= 60; let min = (remainder % 60) as u32; remainder /= 60; let hour = (remainder % 24) as u32; remainder /= 24; let num_days_from_1601 = remainder as i32; let num_days_from_ce = num_days_from_1601 + DAYS_FROM_0001_TO_1601; let ndt = NaiveDate::from_num_days_from_ce(num_days_from_ce).and_hms_nano(hour, min, sec, nano); DateTime::::from_utc(ndt, Utc) } } #[cfg(feature = "std")] #[cfg_attr(docsrs, doc(cfg(feature = "std")))] impl TryFrom for NtfsTime { type Error = SystemTimeError; fn try_from(st: SystemTime) -> Result { let duration_since_unix_epoch = st.duration_since(SystemTime::UNIX_EPOCH)?; let intervals_since_unix_epoch = duration_since_unix_epoch.as_secs() as u64 * INTERVALS_PER_SECOND + duration_since_unix_epoch.subsec_nanos() as u64 / 100; let intervals_since_windows_epoch = intervals_since_unix_epoch + EPOCH_DIFFERENCE_IN_INTERVALS as u64; Ok(Self(intervals_since_windows_epoch)) } } #[cfg(test)] pub(crate) mod tests { use super::*; #[cfg(feature = "chrono")] use chrono::TimeZone; pub(crate) const NT_TIMESTAMP_2021_01_01: u64 = 132539328000000000u64; #[cfg(feature = "chrono")] #[test] fn test_chrono() { let dt = Utc.ymd(2013, 1, 5).and_hms(18, 15, 00); let nt = NtfsTime::try_from(dt).unwrap(); assert_eq!(*nt, 130018833000000000u64); let dt2 = DateTime::::from(nt); assert_eq!(dt, dt2); let dt = Utc.ymd(1601, 1, 1).and_hms(0, 0, 0); let nt = NtfsTime::try_from(dt).unwrap(); assert_eq!(*nt, 0u64); let dt = Utc.ymd(1600, 12, 31).and_hms(23, 59, 59); assert!(NtfsTime::try_from(dt).is_err()); let dt = Utc.ymd(60056, 5, 28).and_hms(0, 0, 0); assert!(NtfsTime::try_from(dt).is_ok()); let dt = Utc.ymd(60056, 5, 29).and_hms(0, 0, 0); assert!(NtfsTime::try_from(dt).is_err()); } #[cfg(feature = "std")] #[test] fn test_systemtime() { let st = SystemTime::now(); let nt = NtfsTime::try_from(st).unwrap(); assert!(nt.nt_timestamp() > NT_TIMESTAMP_2021_01_01); } }