| use crate::{Result, Tag}; |
| use alloc::format; |
| use alloc::string::ToString; |
| use core::fmt; |
| #[cfg(feature = "datetime")] |
| use time::OffsetDateTime; |
| |
| #[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord)] |
| pub enum ASN1TimeZone { |
| /// No timezone provided |
| Undefined, |
| /// Coordinated universal time |
| Z, |
| /// Local zone, with offset to coordinated universal time |
| /// |
| /// `(offset_hour, offset_minute)` |
| Offset(i8, i8), |
| } |
| |
| #[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] |
| pub struct ASN1DateTime { |
| pub year: u32, |
| pub month: u8, |
| pub day: u8, |
| pub hour: u8, |
| pub minute: u8, |
| pub second: u8, |
| pub millisecond: Option<u16>, |
| pub tz: ASN1TimeZone, |
| } |
| |
| impl ASN1DateTime { |
| #[allow(clippy::too_many_arguments)] |
| pub const fn new( |
| year: u32, |
| month: u8, |
| day: u8, |
| hour: u8, |
| minute: u8, |
| second: u8, |
| millisecond: Option<u16>, |
| tz: ASN1TimeZone, |
| ) -> Self { |
| ASN1DateTime { |
| year, |
| month, |
| day, |
| hour, |
| minute, |
| second, |
| millisecond, |
| tz, |
| } |
| } |
| |
| #[cfg(feature = "datetime")] |
| fn to_time_datetime( |
| &self, |
| ) -> core::result::Result<OffsetDateTime, time::error::ComponentRange> { |
| use std::convert::TryFrom; |
| use time::{Date, Month, PrimitiveDateTime, Time, UtcOffset}; |
| |
| let month = Month::try_from(self.month)?; |
| let date = Date::from_calendar_date(self.year as i32, month, self.day)?; |
| let time = Time::from_hms_milli( |
| self.hour, |
| self.minute, |
| self.second, |
| self.millisecond.unwrap_or(0), |
| )?; |
| let primitive_date = PrimitiveDateTime::new(date, time); |
| let offset = match self.tz { |
| ASN1TimeZone::Offset(h, m) => UtcOffset::from_hms(h, m, 0)?, |
| ASN1TimeZone::Undefined | ASN1TimeZone::Z => UtcOffset::UTC, |
| }; |
| Ok(primitive_date.assume_offset(offset)) |
| } |
| |
| #[cfg(feature = "datetime")] |
| pub fn to_datetime(&self) -> Result<OffsetDateTime> { |
| use crate::Error; |
| |
| self.to_time_datetime().map_err(|_| Error::InvalidDateTime) |
| } |
| } |
| |
| impl fmt::Display for ASN1DateTime { |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| let fractional = match self.millisecond { |
| None => "".to_string(), |
| Some(v) => format!(".{}", v), |
| }; |
| write!( |
| f, |
| "{:04}{:02}{:02}{:02}{:02}{:02}{}Z", |
| self.year, self.month, self.day, self.hour, self.minute, self.second, fractional, |
| ) |
| } |
| } |
| |
| /// Decode 2-digit decimal value |
| pub(crate) fn decode_decimal(tag: Tag, hi: u8, lo: u8) -> Result<u8> { |
| if (b'0'..=b'9').contains(&hi) && (b'0'..=b'9').contains(&lo) { |
| Ok((hi - b'0') * 10 + (lo - b'0')) |
| } else { |
| Err(tag.invalid_value("expected digit")) |
| } |
| } |