asn1_rs/
datetime.rs

1use crate::{Result, Tag};
2use alloc::format;
3use alloc::string::ToString;
4use core::fmt;
5#[cfg(feature = "datetime")]
6use time::OffsetDateTime;
7
8#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord)]
9pub enum ASN1TimeZone {
10    /// No timezone provided
11    Undefined,
12    /// Coordinated universal time
13    Z,
14    /// Local zone, with offset to coordinated universal time
15    ///
16    /// `(offset_hour, offset_minute)`
17    Offset(i8, i8),
18}
19
20#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
21pub struct ASN1DateTime {
22    pub year: u32,
23    pub month: u8,
24    pub day: u8,
25    pub hour: u8,
26    pub minute: u8,
27    pub second: u8,
28    pub millisecond: Option<u16>,
29    pub tz: ASN1TimeZone,
30}
31
32impl ASN1DateTime {
33    #[allow(clippy::too_many_arguments)]
34    pub const fn new(
35        year: u32,
36        month: u8,
37        day: u8,
38        hour: u8,
39        minute: u8,
40        second: u8,
41        millisecond: Option<u16>,
42        tz: ASN1TimeZone,
43    ) -> Self {
44        ASN1DateTime {
45            year,
46            month,
47            day,
48            hour,
49            minute,
50            second,
51            millisecond,
52            tz,
53        }
54    }
55
56    #[cfg(feature = "datetime")]
57    fn to_time_datetime(
58        &self,
59    ) -> core::result::Result<OffsetDateTime, time::error::ComponentRange> {
60        use std::convert::TryFrom;
61        use time::{Date, Month, PrimitiveDateTime, Time, UtcOffset};
62
63        let month = Month::try_from(self.month)?;
64        let date = Date::from_calendar_date(self.year as i32, month, self.day)?;
65        let time = Time::from_hms_milli(
66            self.hour,
67            self.minute,
68            self.second,
69            self.millisecond.unwrap_or(0),
70        )?;
71        let primitive_date = PrimitiveDateTime::new(date, time);
72        let offset = match self.tz {
73            ASN1TimeZone::Offset(h, m) => UtcOffset::from_hms(h, m, 0)?,
74            ASN1TimeZone::Undefined | ASN1TimeZone::Z => UtcOffset::UTC,
75        };
76        Ok(primitive_date.assume_offset(offset))
77    }
78
79    #[cfg(feature = "datetime")]
80    pub fn to_datetime(&self) -> Result<OffsetDateTime> {
81        use crate::Error;
82
83        self.to_time_datetime().map_err(|_| Error::InvalidDateTime)
84    }
85}
86
87impl fmt::Display for ASN1DateTime {
88    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
89        let fractional = match self.millisecond {
90            None => "".to_string(),
91            Some(v) => format!(".{}", v),
92        };
93        write!(
94            f,
95            "{:04}{:02}{:02}{:02}{:02}{:02}{}Z",
96            self.year, self.month, self.day, self.hour, self.minute, self.second, fractional,
97        )
98    }
99}
100
101/// Decode 2-digit decimal value
102pub(crate) fn decode_decimal(tag: Tag, hi: u8, lo: u8) -> Result<u8> {
103    if (b'0'..=b'9').contains(&hi) && (b'0'..=b'9').contains(&lo) {
104        Ok((hi - b'0') * 10 + (lo - b'0'))
105    } else {
106        Err(tag.invalid_value("expected digit"))
107    }
108}