Skip to main content

ttf_view/types/
longdatetime.rs

1use chrono::{DateTime, NaiveDate, NaiveDateTime, TimeDelta, Utc};
2use std::fmt;
3
4#[derive(Copy, Hash)]
5#[derive_const(Clone, PartialEq, Eq)]
6#[repr(transparent)]
7pub struct LongDateTime([u8; 8]);
8
9const EPOCH_NAIVE: NaiveDateTime =
10    NaiveDate::from_ymd_opt(1904, 1, 1).unwrap().and_hms_opt(0, 0, 0).unwrap();
11
12impl LongDateTime {
13    pub const EPOCH: DateTime<Utc> = EPOCH_NAIVE.and_utc();
14
15    pub const fn new(datetime: DateTime<Utc>) -> Self {
16        let delta = datetime.naive_utc().signed_duration_since(EPOCH_NAIVE);
17        Self::from_epoch_seconds(delta.num_seconds())
18    }
19    pub const fn datetime(&self) -> Option<DateTime<Utc>> {
20        let delta = TimeDelta::try_seconds(self.epoch_seconds())?;
21        Some(EPOCH_NAIVE.checked_add_signed(delta)?.and_utc())
22    }
23
24    pub const fn from_epoch_seconds(secs: i64) -> Self {
25        Self(i64::to_be_bytes(secs))
26    }
27    pub const fn epoch_seconds(&self) -> i64 {
28        i64::from_be_bytes(self.0)
29    }
30
31    pub const fn from_be_bytes(bytes: [u8; 8]) -> Self {
32        Self(bytes)
33    }
34    pub const fn to_be_bytes(self) -> [u8; 8] {
35        self.0
36    }
37}
38
39// TODO: When [u8; 8]'s Default is constified, replace this impl with #[derive_const]
40#[allow(clippy::derivable_impls)]
41const impl Default for LongDateTime {
42    fn default() -> Self {
43        Self([0; 8])
44    }
45}
46// Note: PartialEq + Eq impls need to be explicit, because [u8; 8] is compared unsignedly.
47const impl PartialOrd for LongDateTime {
48    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
49        Some(self.cmp(other))
50    }
51}
52const impl Ord for LongDateTime {
53    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
54        self.epoch_seconds().cmp(&other.epoch_seconds())
55    }
56}
57
58impl fmt::Debug for LongDateTime {
59    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
60        match self.datetime() {
61            Some(datetime) => datetime.fmt(f),
62            // TODO: maybe try to represent the out-of-range date?
63            None => write!(f, "{:#010X}", self.epoch_seconds()),
64        }
65    }
66}
67
68const impl From<DateTime<Utc>> for LongDateTime {
69    fn from(value: DateTime<Utc>) -> Self {
70        Self::new(value)
71    }
72}
73const impl TryFrom<LongDateTime> for DateTime<Utc> {
74    type Error = ();
75    fn try_from(value: LongDateTime) -> Result<Self, Self::Error> {
76        value.datetime().ok_or(())
77    }
78}