use crate::{Result, Tag};
use alloc::format;
#[cfg(not(feature = "std"))]
use alloc::string::ToString;
use core::fmt;
#[cfg(feature = "datetime")]
use time::OffsetDateTime;
#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord)]
pub enum ASN1TimeZone {
Undefined,
Z,
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,
)
}
}
pub(crate) fn decode_decimal(tag: Tag, hi: u8, lo: u8) -> Result<u8> {
if hi.is_ascii_digit() && lo.is_ascii_digit() {
Ok((hi - b'0') * 10 + (lo - b'0'))
} else {
Err(tag.invalid_value("expected digit"))
}
}