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 Undefined,
12 Z,
14 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
101pub(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}