summaryrefslogtreecommitdiff
path: root/src/datetime.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/datetime.rs')
-rw-r--r--src/datetime.rs108
1 files changed, 108 insertions, 0 deletions
diff --git a/src/datetime.rs b/src/datetime.rs
new file mode 100644
index 0000000..613b44b
--- /dev/null
+++ b/src/datetime.rs
@@ -0,0 +1,108 @@
+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"))
+ }
+}