Skip to main content

ser_file/
timestamp.rs

1use binrw::binrw;
2use chrono::{NaiveDate, NaiveDateTime, TimeDelta};
3use std::{ops::AddAssign, time::Duration};
4
5/// An integer representing the number of 100 nanosecond periods from the Common
6/// Era epoch Jan 1, 0001.
7///
8/// A value of `0` indicates an "invalid" or "unused" timestamp.
9#[binrw]
10#[br(map(u64::into))]
11#[bw(map(u64::from))]
12#[derive(Clone)]
13pub enum Timestamp {
14    Valid(u64),
15    Invalid(u64),
16}
17
18impl Timestamp {
19    pub const MASK: u64 = !(0b11 << 62);
20
21    pub fn is_valid(&self) -> bool {
22        matches!(self, Timestamp::Valid(_))
23    }
24}
25
26impl Default for Timestamp {
27    fn default() -> Self {
28        Timestamp::Invalid(0)
29    }
30}
31
32impl std::fmt::Display for &Timestamp {
33    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
34        match NaiveDateTime::try_from(*self) {
35            Ok(naive) => naive.fmt(f),
36            Err(_) => f.write_str("None"),
37        }
38    }
39}
40
41impl TryFrom<&Timestamp> for NaiveDateTime {
42    type Error = Box<dyn std::error::Error>;
43
44    fn try_from(value: &Timestamp) -> Result<Self, Self::Error> {
45        match value {
46            Timestamp::Valid(t) => {
47                let mut naive = NaiveDate::from_ymd_opt(1, 1, 1)
48                    .ok_or("Naive date failed")?
49                    .and_hms_opt(0, 0, 0)
50                    .ok_or("Naive time failed")?;
51
52                let micros = (t & Timestamp::MASK) / 10;
53                naive.add_assign(TimeDelta::from_std(Duration::from_micros(micros))?);
54                Ok(naive)
55            }
56            Timestamp::Invalid(_) => Err("Cannot convert invalid timestamp".into()),
57        }
58    }
59}
60
61impl TryFrom<&Timestamp> for std::time::SystemTime {
62    type Error = Box<dyn std::error::Error>;
63
64    fn try_from(value: &Timestamp) -> Result<Self, Self::Error> {
65        let naive: NaiveDateTime = value.try_into()?;
66        let utc = naive.and_utc();
67        let nanos_since_epoch = utc.timestamp_nanos_opt().ok_or("Timestamp out of range")?;
68        if nanos_since_epoch >= 0 {
69            let duration = std::time::Duration::from_nanos(nanos_since_epoch as u64);
70            Ok(std::time::UNIX_EPOCH + duration)
71        } else {
72            let duration = std::time::Duration::from_nanos(nanos_since_epoch.abs() as u64);
73            Ok(std::time::UNIX_EPOCH - duration)
74        }
75    }
76}
77
78impl From<u64> for Timestamp {
79    fn from(value: u64) -> Self {
80        match value {
81            v if (v & Timestamp::MASK) == 0 => Timestamp::Invalid(v),
82            v => Timestamp::Valid(v),
83        }
84    }
85}
86
87impl From<&Timestamp> for u64 {
88    fn from(value: &Timestamp) -> Self {
89        match value {
90            Timestamp::Valid(v) => *v,
91            Timestamp::Invalid(v) => *v,
92        }
93    }
94}