fast_ntfs 1.0.2

Forked a low-level NTFS filesystem library
Documentation
// Copyright 2021-2026 Colin Finck <colin@reactos.org>
// SPDX-License-Identifier: MIT OR Apache-2.0

use zerocopy::{FromBytes, Immutable, KnownLayout, LittleEndian, U64, Unaligned};

/// Difference in 100-nanosecond intervals between the Windows/NTFS epoch (1601-01-01) and the Unix epoch (1970-01-01).
#[cfg(any(feature = "time", feature = "std"))]
const EPOCH_DIFFERENCE_IN_INTERVALS: u64 = 116_444_736_000_000_000;

/// Number of 100-nanosecond intervals in a second.
#[cfg(feature = "std")]
const INTERVALS_PER_SECOND: u64 = 10_000_000;

/// 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(
    Clone, Copy, Debug, Eq, FromBytes, Immutable, KnownLayout, Ord, PartialEq, PartialOrd, Unaligned,
)]
#[repr(transparent)]
pub struct NtfsTime(U64<LittleEndian>);

impl NtfsTime {
    /// Returns the stored NT timestamp (number of 100-nanosecond intervals since January 1, 1601).
    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))
    }
}