use std::time::{Duration, SystemTime, UNIX_EPOCH};
const FILETIME_UNIX_DIFF: u64 = 116444736000000000;
const INTERVALS_PER_SECOND: u64 = 10_000_000;
const INTERVALS_PER_MILLI: u64 = 10_000;
const INTERVALS_PER_MICRO: u64 = 10;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Timestamp {
filetime: u64,
}
impl Timestamp {
#[inline]
pub const fn from_filetime(filetime: u64) -> Self {
Self { filetime }
}
pub fn from_unix_secs(secs: i64) -> Option<Self> {
if secs < 0 {
let neg_secs = (-secs) as u64;
let neg_intervals = neg_secs.checked_mul(INTERVALS_PER_SECOND)?;
FILETIME_UNIX_DIFF
.checked_sub(neg_intervals)
.map(Self::from_filetime)
} else {
let intervals = (secs as u64).checked_mul(INTERVALS_PER_SECOND)?;
FILETIME_UNIX_DIFF
.checked_add(intervals)
.map(Self::from_filetime)
}
}
pub fn from_unix_secs_nanos(secs: i64, nanos: u32) -> Option<Self> {
let nano_intervals = (nanos as u64) / 100;
if secs < 0 {
let neg_secs = (-secs) as u64;
let neg_intervals = neg_secs.checked_mul(INTERVALS_PER_SECOND)?;
let base = FILETIME_UNIX_DIFF.checked_sub(neg_intervals)?;
if nano_intervals > 0 {
base.checked_add(nano_intervals).map(Self::from_filetime)
} else {
Some(Self::from_filetime(base))
}
} else {
let sec_intervals = (secs as u64).checked_mul(INTERVALS_PER_SECOND)?;
let base = FILETIME_UNIX_DIFF.checked_add(sec_intervals)?;
base.checked_add(nano_intervals).map(Self::from_filetime)
}
}
pub fn from_system_time(time: SystemTime) -> Option<Self> {
match time.duration_since(UNIX_EPOCH) {
Ok(duration) => {
Self::from_unix_secs_nanos(duration.as_secs() as i64, duration.subsec_nanos())
}
Err(e) => {
let duration = e.duration();
Self::from_unix_secs_nanos(-(duration.as_secs() as i64), duration.subsec_nanos())
}
}
}
#[inline]
pub const fn as_filetime(&self) -> u64 {
self.filetime
}
pub fn as_unix_secs(&self) -> i64 {
if self.filetime >= FILETIME_UNIX_DIFF {
let intervals = self.filetime - FILETIME_UNIX_DIFF;
(intervals / INTERVALS_PER_SECOND) as i64
} else {
let intervals = FILETIME_UNIX_DIFF - self.filetime;
let secs = intervals / INTERVALS_PER_SECOND;
let extra = if intervals % INTERVALS_PER_SECOND > 0 {
1
} else {
0
};
-((secs + extra) as i64)
}
}
pub fn as_unix_millis(&self) -> i64 {
if self.filetime >= FILETIME_UNIX_DIFF {
let intervals = self.filetime - FILETIME_UNIX_DIFF;
(intervals / INTERVALS_PER_MILLI) as i64
} else {
let intervals = FILETIME_UNIX_DIFF - self.filetime;
-((intervals / INTERVALS_PER_MILLI) as i64)
}
}
pub fn as_unix_micros(&self) -> i64 {
if self.filetime >= FILETIME_UNIX_DIFF {
let intervals = self.filetime - FILETIME_UNIX_DIFF;
(intervals / INTERVALS_PER_MICRO) as i64
} else {
let intervals = FILETIME_UNIX_DIFF - self.filetime;
-((intervals / INTERVALS_PER_MICRO) as i64)
}
}
pub fn as_unix_nanos(&self) -> i128 {
if self.filetime >= FILETIME_UNIX_DIFF {
let intervals = self.filetime - FILETIME_UNIX_DIFF;
(intervals as i128) * 100
} else {
let intervals = FILETIME_UNIX_DIFF - self.filetime;
-((intervals as i128) * 100)
}
}
pub fn as_system_time(&self) -> SystemTime {
if self.filetime >= FILETIME_UNIX_DIFF {
let intervals = self.filetime - FILETIME_UNIX_DIFF;
let secs = intervals / INTERVALS_PER_SECOND;
let sub_intervals = intervals % INTERVALS_PER_SECOND;
let nanos = (sub_intervals * 100) as u32;
UNIX_EPOCH + Duration::new(secs, nanos)
} else {
let intervals = FILETIME_UNIX_DIFF - self.filetime;
let secs = intervals / INTERVALS_PER_SECOND;
let sub_intervals = intervals % INTERVALS_PER_SECOND;
let nanos = (sub_intervals * 100) as u32;
UNIX_EPOCH - Duration::new(secs, nanos)
}
}
#[inline]
pub fn sub_second_100ns(&self) -> u32 {
(self.filetime % INTERVALS_PER_SECOND) as u32
}
#[inline]
pub fn sub_second_nanos(&self) -> u32 {
((self.filetime % INTERVALS_PER_SECOND) * 100) as u32
}
#[inline]
pub fn sub_second_micros(&self) -> u32 {
((self.filetime % INTERVALS_PER_SECOND) / INTERVALS_PER_MICRO) as u32
}
#[inline]
pub fn sub_second_millis(&self) -> u32 {
((self.filetime % INTERVALS_PER_SECOND) / INTERVALS_PER_MILLI) as u32
}
#[inline]
pub fn is_before_unix_epoch(&self) -> bool {
self.filetime < FILETIME_UNIX_DIFF
}
#[inline]
pub fn is_at_or_after_unix_epoch(&self) -> bool {
self.filetime >= FILETIME_UNIX_DIFF
}
}
impl Default for Timestamp {
fn default() -> Self {
Self::from_filetime(FILETIME_UNIX_DIFF)
}
}
impl From<u64> for Timestamp {
fn from(filetime: u64) -> Self {
Self::from_filetime(filetime)
}
}
impl From<Timestamp> for u64 {
fn from(ts: Timestamp) -> u64 {
ts.filetime
}
}
impl From<Timestamp> for SystemTime {
fn from(ts: Timestamp) -> SystemTime {
ts.as_system_time()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_unix_epoch() {
let ts = Timestamp::from_filetime(FILETIME_UNIX_DIFF);
assert_eq!(ts.as_unix_secs(), 0);
assert_eq!(ts.as_unix_millis(), 0);
assert_eq!(ts.as_unix_micros(), 0);
assert_eq!(ts.as_unix_nanos(), 0);
assert_eq!(ts.as_system_time(), UNIX_EPOCH);
assert!(!ts.is_before_unix_epoch());
assert!(ts.is_at_or_after_unix_epoch());
}
#[test]
fn test_sub_second_precision() {
let ts = Timestamp::from_filetime(FILETIME_UNIX_DIFF + 15_000_000);
assert_eq!(ts.as_unix_secs(), 1);
assert_eq!(ts.sub_second_100ns(), 5_000_000);
assert_eq!(ts.sub_second_nanos(), 500_000_000);
assert_eq!(ts.sub_second_micros(), 500_000);
assert_eq!(ts.sub_second_millis(), 500);
}
#[test]
fn test_from_unix_secs() {
let ts = Timestamp::from_unix_secs(0).unwrap();
assert_eq!(ts.as_filetime(), FILETIME_UNIX_DIFF);
let ts = Timestamp::from_unix_secs(1).unwrap();
assert_eq!(ts.as_filetime(), FILETIME_UNIX_DIFF + INTERVALS_PER_SECOND);
let ts = Timestamp::from_unix_secs(-1).unwrap();
assert_eq!(ts.as_filetime(), FILETIME_UNIX_DIFF - INTERVALS_PER_SECOND);
}
#[test]
fn test_from_unix_secs_nanos() {
let ts = Timestamp::from_unix_secs_nanos(1, 500_000_000).unwrap();
assert_eq!(ts.as_unix_secs(), 1);
assert_eq!(ts.sub_second_millis(), 500);
}
#[test]
fn test_roundtrip_system_time() {
let original = UNIX_EPOCH + Duration::new(1234567890, 123_456_700);
let ts = Timestamp::from_system_time(original).unwrap();
let recovered = ts.as_system_time();
assert_eq!(original, recovered);
}
#[test]
fn test_100ns_precision() {
let ts = Timestamp::from_filetime(FILETIME_UNIX_DIFF + 123);
assert_eq!(ts.sub_second_100ns(), 123);
assert_eq!(ts.sub_second_nanos(), 12300);
}
#[test]
fn test_before_unix_epoch() {
let day_in_intervals = 24 * 60 * 60 * INTERVALS_PER_SECOND;
let ts = Timestamp::from_filetime(FILETIME_UNIX_DIFF - day_in_intervals);
assert!(ts.is_before_unix_epoch());
assert_eq!(ts.as_unix_secs(), -86400);
}
#[test]
fn test_conversions() {
let ts: Timestamp = 132456789012345678u64.into();
let back: u64 = ts.into();
assert_eq!(back, 132456789012345678);
}
#[test]
fn test_default() {
let ts = Timestamp::default();
assert_eq!(ts.as_unix_secs(), 0);
}
}