mod cmp;
mod consts;
mod convert;
mod dos_date_time;
mod fmt;
mod ops;
#[cfg(feature = "serde")]
mod serde;
mod unix_time;
use core::{mem, str::FromStr};
use crate::error::ParseFileTimeError;
const FILE_TIMES_PER_SEC: u64 = 10_000_000;
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[cfg_attr(test, derive(proptest_derive::Arbitrary))]
pub struct FileTime(u64);
impl FileTime {
#[cfg(feature = "std")]
#[must_use]
pub fn now() -> Self {
use std::time::SystemTime;
SystemTime::now()
.try_into()
.expect("the current date and time should be in the range of the file time")
}
#[must_use]
#[inline]
pub const fn new(ft: u64) -> Self {
Self(ft)
}
#[must_use]
#[inline]
pub const fn to_raw(self) -> u64 {
self.0
}
#[must_use]
#[inline]
pub const fn to_be_bytes(self) -> [u8; mem::size_of::<Self>()] {
self.to_raw().to_be_bytes()
}
#[must_use]
#[inline]
pub const fn to_le_bytes(self) -> [u8; mem::size_of::<Self>()] {
self.to_raw().to_le_bytes()
}
#[must_use]
#[inline]
pub const fn from_be_bytes(bytes: [u8; mem::size_of::<Self>()]) -> Self {
Self::new(u64::from_be_bytes(bytes))
}
#[must_use]
#[inline]
pub const fn from_le_bytes(bytes: [u8; mem::size_of::<Self>()]) -> Self {
Self::new(u64::from_le_bytes(bytes))
}
}
impl Default for FileTime {
#[inline]
fn default() -> Self {
Self::NT_TIME_EPOCH
}
}
impl FromStr for FileTime {
type Err = ParseFileTimeError;
#[inline]
fn from_str(s: &str) -> Result<Self, Self::Err> {
s.parse().map_err(ParseFileTimeError::new).map(Self::new)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn clone() {
assert_eq!(FileTime::NT_TIME_EPOCH.clone(), FileTime::NT_TIME_EPOCH);
}
#[test]
fn copy() {
let a = FileTime::NT_TIME_EPOCH;
let b = a;
assert_eq!(a, b);
}
#[cfg(feature = "std")]
#[test]
fn hash() {
use std::{
collections::hash_map::DefaultHasher,
hash::{Hash, Hasher},
};
assert_ne!(
{
let mut hasher = DefaultHasher::new();
FileTime::NT_TIME_EPOCH.hash(&mut hasher);
hasher.finish()
},
{
let mut hasher = DefaultHasher::new();
FileTime::MAX.hash(&mut hasher);
hasher.finish()
}
);
}
#[cfg(feature = "std")]
#[test]
fn now() {
let now = FileTime::now();
assert!(now >= FileTime::new(133_170_048_000_000_000));
}
#[test]
fn new() {
assert_eq!(FileTime::new(u64::MIN), FileTime::NT_TIME_EPOCH);
assert_eq!(FileTime::new(116_444_736_000_000_000), FileTime::UNIX_EPOCH);
assert_eq!(FileTime::new(u64::MAX), FileTime::MAX);
}
#[test]
fn to_raw() {
assert_eq!(FileTime::NT_TIME_EPOCH.to_raw(), u64::MIN);
assert_eq!(FileTime::UNIX_EPOCH.to_raw(), 116_444_736_000_000_000);
assert_eq!(FileTime::MAX.to_raw(), u64::MAX);
}
#[test]
fn to_be_bytes() {
assert_eq!(FileTime::NT_TIME_EPOCH.to_be_bytes(), [u8::MIN; 8]);
assert_eq!(
FileTime::UNIX_EPOCH.to_be_bytes(),
[0x01, 0x9d, 0xb1, 0xde, 0xd5, 0x3e, 0x80, 0x00]
);
assert_eq!(FileTime::MAX.to_be_bytes(), [u8::MAX; 8]);
}
#[cfg(feature = "std")]
#[test_strategy::proptest]
fn to_be_bytes_roundtrip(ft: FileTime) {
use proptest::prop_assert_eq;
prop_assert_eq!(ft.to_be_bytes(), ft.to_raw().to_be_bytes());
}
#[test]
fn to_le_bytes() {
assert_eq!(FileTime::NT_TIME_EPOCH.to_le_bytes(), [u8::MIN; 8]);
assert_eq!(
FileTime::UNIX_EPOCH.to_le_bytes(),
[0x00, 0x80, 0x3e, 0xd5, 0xde, 0xb1, 0x9d, 0x01]
);
assert_eq!(FileTime::MAX.to_le_bytes(), [u8::MAX; 8]);
}
#[cfg(feature = "std")]
#[test_strategy::proptest]
fn to_le_bytes_roundtrip(ft: FileTime) {
use proptest::prop_assert_eq;
prop_assert_eq!(ft.to_le_bytes(), ft.to_raw().to_le_bytes());
}
#[test]
fn from_be_bytes() {
assert_eq!(
FileTime::from_be_bytes([u8::MIN; 8]),
FileTime::NT_TIME_EPOCH
);
assert_eq!(
FileTime::from_be_bytes([0x01, 0x9d, 0xb1, 0xde, 0xd5, 0x3e, 0x80, 0x00]),
FileTime::UNIX_EPOCH
);
assert_eq!(FileTime::from_be_bytes([u8::MAX; 8]), FileTime::MAX);
}
#[cfg(feature = "std")]
#[test_strategy::proptest]
fn from_be_bytes_roundtrip(bytes: [u8; mem::size_of::<FileTime>()]) {
use proptest::prop_assert_eq;
prop_assert_eq!(
FileTime::from_be_bytes(bytes),
FileTime::new(u64::from_be_bytes(bytes))
);
}
#[test]
fn from_le_bytes() {
assert_eq!(
FileTime::from_le_bytes([u8::MIN; 8]),
FileTime::NT_TIME_EPOCH
);
assert_eq!(
FileTime::from_le_bytes([0x00, 0x80, 0x3e, 0xd5, 0xde, 0xb1, 0x9d, 0x01]),
FileTime::UNIX_EPOCH
);
assert_eq!(FileTime::from_le_bytes([u8::MAX; 8]), FileTime::MAX);
}
#[cfg(feature = "std")]
#[test_strategy::proptest]
fn from_le_bytes_roundtrip(bytes: [u8; mem::size_of::<FileTime>()]) {
use proptest::prop_assert_eq;
prop_assert_eq!(
FileTime::from_le_bytes(bytes),
FileTime::new(u64::from_le_bytes(bytes))
);
}
#[test]
fn default() {
assert_eq!(FileTime::default(), FileTime::NT_TIME_EPOCH);
}
#[test]
fn from_str() {
assert_eq!(FileTime::from_str("0").unwrap(), FileTime::NT_TIME_EPOCH);
assert_eq!(FileTime::from_str("+0").unwrap(), FileTime::NT_TIME_EPOCH);
assert_eq!(
FileTime::from_str("116444736000000000").unwrap(),
FileTime::UNIX_EPOCH
);
assert_eq!(
FileTime::from_str("+116444736000000000").unwrap(),
FileTime::UNIX_EPOCH
);
assert_eq!(
FileTime::from_str("18446744073709551615").unwrap(),
FileTime::MAX
);
assert_eq!(
FileTime::from_str("+18446744073709551615").unwrap(),
FileTime::MAX
);
}
#[cfg(feature = "std")]
#[test_strategy::proptest]
fn from_str_roundtrip(#[strategy(r"\+?[0-9]{1,19}")] s: std::string::String) {
use proptest::prop_assert_eq;
let ft = s.parse().unwrap();
prop_assert_eq!(FileTime::from_str(&s).unwrap(), FileTime::new(ft));
}
#[cfg(feature = "std")]
#[test]
fn from_str_when_empty() {
use std::{
error::Error,
num::{IntErrorKind, ParseIntError},
};
assert_eq!(
FileTime::from_str("")
.unwrap_err()
.source()
.unwrap()
.downcast_ref::<ParseIntError>()
.unwrap()
.kind(),
&IntErrorKind::Empty
);
}
#[cfg(feature = "std")]
#[test]
fn from_str_with_invalid_digit() {
use std::{
error::Error,
num::{IntErrorKind, ParseIntError},
};
assert_eq!(
FileTime::from_str("a")
.unwrap_err()
.source()
.unwrap()
.downcast_ref::<ParseIntError>()
.unwrap()
.kind(),
&IntErrorKind::InvalidDigit
);
assert_eq!(
FileTime::from_str("-1")
.unwrap_err()
.source()
.unwrap()
.downcast_ref::<ParseIntError>()
.unwrap()
.kind(),
&IntErrorKind::InvalidDigit
);
assert_eq!(
FileTime::from_str("+")
.unwrap_err()
.source()
.unwrap()
.downcast_ref::<ParseIntError>()
.unwrap()
.kind(),
&IntErrorKind::InvalidDigit
);
assert_eq!(
FileTime::from_str("-")
.unwrap_err()
.source()
.unwrap()
.downcast_ref::<ParseIntError>()
.unwrap()
.kind(),
&IntErrorKind::InvalidDigit
);
assert_eq!(
FileTime::from_str(" 0")
.unwrap_err()
.source()
.unwrap()
.downcast_ref::<ParseIntError>()
.unwrap()
.kind(),
&IntErrorKind::InvalidDigit
);
assert_eq!(
FileTime::from_str("0 ")
.unwrap_err()
.source()
.unwrap()
.downcast_ref::<ParseIntError>()
.unwrap()
.kind(),
&IntErrorKind::InvalidDigit
);
}
#[cfg(feature = "std")]
#[test_strategy::proptest]
fn from_str_with_invalid_digit_roundtrip(
#[strategy(r"-[0-9]+|[^0-9]+")] s: std::string::String,
) {
use proptest::prop_assert;
prop_assert!(FileTime::from_str(&s).is_err());
}
#[cfg(feature = "std")]
#[test]
fn from_str_when_positive_overflow() {
use std::{
error::Error,
num::{IntErrorKind, ParseIntError},
};
assert_eq!(
FileTime::from_str("18446744073709551616")
.unwrap_err()
.source()
.unwrap()
.downcast_ref::<ParseIntError>()
.unwrap()
.kind(),
&IntErrorKind::PosOverflow
);
}
}