asn1_rs/
datetime.rs

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