Skip to main content

provenance_mark/
date.rs

1use chrono::{Datelike, Duration, TimeZone, Utc};
2use dcbor::prelude::*;
3
4use crate::{Error, Result};
5
6pub trait SerializableDate: Sized {
7    fn serialize_2_bytes(&self) -> Result<[u8; 2]>;
8    fn deserialize_2_bytes(bytes: &[u8; 2]) -> Result<Self>;
9
10    fn serialize_4_bytes(&self) -> Result<[u8; 4]>;
11    fn deserialize_4_bytes(bytes: &[u8; 4]) -> Result<Self>;
12
13    fn serialize_6_bytes(&self) -> Result<[u8; 6]>;
14    fn deserialize_6_bytes(bytes: &[u8; 6]) -> Result<Self>;
15}
16
17impl SerializableDate for Date {
18    fn serialize_2_bytes(&self) -> Result<[u8; 2]> {
19        let components = self.datetime();
20        let year = components.year();
21        let month = components.month();
22        let day = components.day();
23
24        let yy = year - 2023;
25        if !(0..128).contains(&yy) {
26            return Err(Error::YearOutOfRange { year });
27        }
28        if !(1..=12).contains(&month) || !(1..=31).contains(&day) {
29            return Err(Error::InvalidMonthOrDay { year, month, day });
30        }
31
32        let value = ((yy as u16) << 9) | ((month as u16) << 5) | (day as u16);
33        Ok(value.to_be_bytes())
34    }
35
36    fn deserialize_2_bytes(bytes: &[u8; 2]) -> Result<Self> {
37        let value = u16::from_be_bytes(*bytes);
38        let day = (value & 0b11111) as u32;
39        let month = ((value >> 5) & 0b1111) as u32;
40        let yy = ((value >> 9) & 0b1111111) as i32;
41        let year = yy + 2023;
42
43        if !(1..=12).contains(&month)
44            || !range_of_days_in_month(year, month).contains(&day)
45        {
46            return Err(Error::InvalidMonthOrDay { year, month, day });
47        }
48
49        let date = Utc
50            .with_ymd_and_hms(year, month, day, 0, 0, 0)
51            .single()
52            .ok_or_else(|| Error::InvalidDate {
53                details: format!(
54                    "Cannot construct date {year}-{month:02}-{day:02}"
55                ),
56            })?;
57        Ok(Date::from_datetime(date))
58    }
59
60    fn serialize_4_bytes(&self) -> Result<[u8; 4]> {
61        let reference_date =
62            Utc.with_ymd_and_hms(2001, 1, 1, 0, 0, 0).single().unwrap();
63        let duration = self.datetime() - reference_date;
64        let seconds = duration.num_seconds();
65        let n = u32::try_from(seconds).map_err(|_| Error::DateOutOfRange {
66            details: "seconds value too large for u32".to_string(),
67        })?;
68        Ok(n.to_be_bytes())
69    }
70
71    fn deserialize_4_bytes(bytes: &[u8; 4]) -> Result<Self> {
72        let n = u32::from_be_bytes(*bytes);
73        let reference_date =
74            Utc.with_ymd_and_hms(2001, 1, 1, 0, 0, 0).single().unwrap();
75        let date = reference_date + chrono::Duration::seconds(n as i64);
76        Ok(Date::from_datetime(date))
77    }
78
79    fn serialize_6_bytes(&self) -> Result<[u8; 6]> {
80        let reference_date =
81            Utc.with_ymd_and_hms(2001, 1, 1, 0, 0, 0).single().unwrap();
82        let duration = self.datetime() - reference_date;
83        let milliseconds = duration.num_milliseconds();
84        let n =
85            u64::try_from(milliseconds).map_err(|_| Error::DateOutOfRange {
86                details: "milliseconds value too large for u64".to_string(),
87            })?;
88
89        if n > 0xe5940a78a7ff {
90            return Err(Error::DateOutOfRange {
91                details: "date exceeds maximum representable value".to_string(),
92            });
93        }
94
95        let bytes = n.to_be_bytes();
96        Ok(bytes[2..8].try_into().unwrap())
97    }
98
99    fn deserialize_6_bytes(bytes: &[u8; 6]) -> Result<Self> {
100        let mut full_bytes = [0u8; 8];
101        full_bytes[2..].copy_from_slice(bytes);
102        let n = u64::from_be_bytes(full_bytes);
103
104        if n > 0xe5940a78a7ff {
105            return Err(Error::DateOutOfRange {
106                details: "date exceeds maximum representable value".to_string(),
107            });
108        }
109
110        let reference_date =
111            Utc.with_ymd_and_hms(2001, 1, 1, 0, 0, 0).single().unwrap();
112        let date = reference_date + chrono::Duration::milliseconds(n as i64);
113        Ok(Date::from_datetime(date))
114    }
115}
116
117pub fn range_of_days_in_month(year: i32, month: u32) -> std::ops::Range<u32> {
118    let next_month = if month == 12 {
119        Utc.with_ymd_and_hms(year + 1, 1, 1, 0, 0, 0).unwrap()
120    } else {
121        Utc.with_ymd_and_hms(year, month + 1, 1, 0, 0, 0).unwrap()
122    };
123    let last_day = (next_month - Duration::days(1)).day();
124    1..last_day + 1
125}