1use binrw::binrw;
2use chrono::{NaiveDate, NaiveDateTime, TimeDelta};
3use std::{ops::AddAssign, time::Duration};
4
5#[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}