use zerocopy::{FromBytes, Immutable, KnownLayout, LittleEndian, U64, Unaligned};
#[cfg(any(feature = "time", feature = "std"))]
const EPOCH_DIFFERENCE_IN_INTERVALS: u64 = 116_444_736_000_000_000;
#[cfg(feature = "std")]
const INTERVALS_PER_SECOND: u64 = 10_000_000;
#[derive(
Clone, Copy, Debug, Eq, FromBytes, Immutable, KnownLayout, Ord, PartialEq, PartialOrd, Unaligned,
)]
#[repr(transparent)]
pub struct NtfsTime(U64<LittleEndian>);
impl NtfsTime {
pub fn nt_timestamp(&self) -> u64 {
self.0.get()
}
}
impl From<u64> for NtfsTime {
fn from(value: u64) -> Self {
Self(U64::new(value))
}
}
#[cfg(feature = "time")]
#[cfg_attr(docsrs, doc(cfg(feature = "time")))]
impl TryFrom<time::OffsetDateTime> for NtfsTime {
type Error = crate::error::NtfsError;
fn try_from(dt: time::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 + i128::from(EPOCH_DIFFERENCE_IN_INTERVALS);
let nt_timestamp = u64::try_from(intervals_since_windows_epoch)
.map_err(|_| crate::error::NtfsError::InvalidTime)?;
Ok(Self::from(nt_timestamp))
}
}
#[cfg(feature = "time")]
#[cfg_attr(docsrs, doc(cfg(feature = "time")))]
impl From<NtfsTime> for time::OffsetDateTime {
fn from(nt: NtfsTime) -> time::OffsetDateTime {
let intervals_since_windows_epoch = i128::from(nt.nt_timestamp());
let intervals_since_unix_epoch =
intervals_since_windows_epoch - i128::from(EPOCH_DIFFERENCE_IN_INTERVALS);
let nanos_since_unix_epoch = intervals_since_unix_epoch * 100;
time::OffsetDateTime::from_unix_timestamp_nanos(nanos_since_unix_epoch).unwrap()
}
}
#[cfg(feature = "std")]
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
impl TryFrom<std::time::SystemTime> for NtfsTime {
type Error = crate::error::NtfsError;
fn try_from(st: std::time::SystemTime) -> Result<Self, Self::Error> {
let intervals_since_windows_epoch = match st.duration_since(std::time::UNIX_EPOCH) {
Ok(duration) => {
let intervals_since_unix_epoch = duration
.as_secs()
.checked_mul(INTERVALS_PER_SECOND)
.and_then(|intervals| {
intervals.checked_add(u64::from(duration.subsec_nanos()) / 100)
})
.ok_or(crate::error::NtfsError::InvalidTime)?;
intervals_since_unix_epoch
.checked_add(EPOCH_DIFFERENCE_IN_INTERVALS)
.ok_or(crate::error::NtfsError::InvalidTime)?
}
Err(error) => {
let duration = error.duration();
let subsecond_intervals = u64::from(duration.subsec_nanos()).div_ceil(100);
let intervals_before_unix_epoch = duration
.as_secs()
.checked_mul(INTERVALS_PER_SECOND)
.and_then(|intervals| intervals.checked_add(subsecond_intervals))
.ok_or(crate::error::NtfsError::InvalidTime)?;
EPOCH_DIFFERENCE_IN_INTERVALS
.checked_sub(intervals_before_unix_epoch)
.ok_or(crate::error::NtfsError::InvalidTime)?
}
};
Ok(Self::from(intervals_since_windows_epoch))
}
}