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 Undefined,
13 Z,
15 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
102pub(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}