use binrw::BinRead;
use derive_more::From;
#[cfg(feature = "time")]
use {crate::error::NtfsError, time::OffsetDateTime};
#[cfg(feature = "std")]
use std::time::{SystemTime, SystemTimeError};
#[cfg(any(feature = "time", feature = "std"))]
const EPOCH_DIFFERENCE_IN_INTERVALS: u64 = 116_444_736_000_000_000;
#[cfg(any(feature = "time", feature = "std"))]
const INTERVALS_PER_SECOND: u64 = 10_000_000;
#[derive(BinRead, Clone, Copy, Debug, Eq, From, Ord, PartialEq, PartialOrd)]
pub struct NtfsTime(u64);
impl NtfsTime {
pub fn nt_timestamp(&self) -> u64 {
self.0
}
}
#[cfg(feature = "time")]
#[cfg_attr(docsrs, doc(cfg(feature = "time")))]
impl TryFrom<OffsetDateTime> for NtfsTime {
type Error = NtfsError;
fn try_from(dt: OffsetDateTime) -> Result<Self, Self::Error> {
let nanos_since_unix_epoch = dt.unix_timestamp_nanos();
let intervals_since_unix_epoch = nanos_since_unix_epoch / 100;
let intervals_since_windows_epoch =
intervals_since_unix_epoch + EPOCH_DIFFERENCE_IN_INTERVALS as i128;
let nt_timestamp =
u64::try_from(intervals_since_windows_epoch).map_err(|_| NtfsError::InvalidTime)?;
Ok(Self(nt_timestamp))
}
}
#[cfg(feature = "time")]
#[cfg_attr(docsrs, doc(cfg(feature = "time")))]
impl From<NtfsTime> for OffsetDateTime {
fn from(nt: NtfsTime) -> OffsetDateTime {
let intervals_since_windows_epoch = nt.nt_timestamp() as i128;
let intervals_since_unix_epoch =
intervals_since_windows_epoch - EPOCH_DIFFERENCE_IN_INTERVALS as i128;
let nanos_since_unix_epoch = intervals_since_unix_epoch * 100;
OffsetDateTime::from_unix_timestamp_nanos(nanos_since_unix_epoch).unwrap()
}
}
#[cfg(feature = "std")]
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
impl TryFrom<SystemTime> for NtfsTime {
type Error = SystemTimeError;
fn try_from(st: SystemTime) -> Result<Self, Self::Error> {
let duration_since_unix_epoch = st.duration_since(SystemTime::UNIX_EPOCH)?;
let intervals_since_unix_epoch = duration_since_unix_epoch.as_secs() * 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;
Ok(Self(intervals_since_windows_epoch))
}
}
#[cfg(test)]
pub(crate) mod tests {
use super::*;
#[cfg(feature = "time")]
use time::macros::datetime;
pub(crate) const NT_TIMESTAMP_2021_01_01: u64 = 132539328000000000u64;
#[cfg(feature = "time")]
#[test]
fn test_offsetdatetime() {
let dt = datetime!(2013-01-05 18:15 UTC);
let nt = NtfsTime::try_from(dt).unwrap();
assert_eq!(nt.nt_timestamp(), 130018833000000000u64);
let dt2 = OffsetDateTime::from(nt);
assert_eq!(dt, dt2);
let dt = datetime!(1601-01-01 0:00 UTC);
let nt = NtfsTime::try_from(dt).unwrap();
assert_eq!(nt.nt_timestamp(), 0u64);
let dt = datetime!(1600-12-31 23:59:59 UTC);
assert!(NtfsTime::try_from(dt).is_err());
let dt = datetime!(+60056-05-28 0:00 UTC);
assert!(NtfsTime::try_from(dt).is_ok());
let dt = datetime!(+60056-05-29 0:00 UTC);
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);
}
}