#[cfg(not(feature = "std"))]
use alloc::string::String;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct UtcTime {
pub year: u16,
pub month: u8,
pub day: u8,
pub hour: u8,
pub minute: u8,
pub second: u8,
}
impl UtcTime {
pub fn new(
year: u16,
month: u8,
day: u8,
hour: u8,
minute: u8,
second: u8,
) -> crate::Result<Self> {
if !(1950..=2049).contains(&year) {
return Err(crate::Error::InvalidTime { position: 0 });
}
if !(1..=12).contains(&month) {
return Err(crate::Error::InvalidTime { position: 0 });
}
if !(1..=31).contains(&day) {
return Err(crate::Error::InvalidTime { position: 0 });
}
if hour > 23 {
return Err(crate::Error::InvalidTime { position: 0 });
}
if minute > 59 {
return Err(crate::Error::InvalidTime { position: 0 });
}
if second > 59 {
return Err(crate::Error::InvalidTime { position: 0 });
}
Ok(Self {
year,
month,
day,
hour,
minute,
second,
})
}
pub(crate) fn from_str(s: &str) -> crate::Result<Self> {
if s.len() != 13 {
return Err(crate::Error::InvalidTime { position: 0 });
}
if !s.ends_with('Z') {
return Err(crate::Error::InvalidTime { position: 0 });
}
let yy = parse_two_digits(&s[0..2])?;
let mm = parse_two_digits(&s[2..4])?;
let dd = parse_two_digits(&s[4..6])?;
let hh = parse_two_digits(&s[6..8])?;
let min = parse_two_digits(&s[8..10])?;
let ss = parse_two_digits(&s[10..12])?;
let year = if yy >= 50 {
1900 + yy as u16
} else {
2000 + yy as u16
};
Self::new(year, mm, dd, hh, min, ss)
}
}
#[cfg(feature = "std")]
impl core::fmt::Display for UtcTime {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
let yy = (self.year % 100) as u8;
write!(
f,
"{:02}{:02}{:02}{:02}{:02}{:02}Z",
yy, self.month, self.day, self.hour, self.minute, self.second
)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct GeneralizedTime {
pub year: u16,
pub month: u8,
pub day: u8,
pub hour: u8,
pub minute: u8,
pub second: u8,
pub milliseconds: Option<u16>,
}
impl GeneralizedTime {
pub fn new(
year: u16,
month: u8,
day: u8,
hour: u8,
minute: u8,
second: u8,
milliseconds: Option<u16>,
) -> crate::Result<Self> {
if !(1..=12).contains(&month) {
return Err(crate::Error::InvalidTime { position: 0 });
}
if !(1..=31).contains(&day) {
return Err(crate::Error::InvalidTime { position: 0 });
}
if hour > 23 {
return Err(crate::Error::InvalidTime { position: 0 });
}
if minute > 59 {
return Err(crate::Error::InvalidTime { position: 0 });
}
if second > 59 {
return Err(crate::Error::InvalidTime { position: 0 });
}
if let Some(ms) = milliseconds {
if ms > 999 {
return Err(crate::Error::InvalidTime { position: 0 });
}
}
Ok(Self {
year,
month,
day,
hour,
minute,
second,
milliseconds,
})
}
pub(crate) fn from_str(s: &str) -> crate::Result<Self> {
if !s.ends_with('Z') {
return Err(crate::Error::InvalidTime { position: 0 });
}
let has_fraction = s.contains('.');
if has_fraction {
if s.len() < 16 {
return Err(crate::Error::InvalidTime { position: 0 });
}
let yyyy = parse_four_digits(&s[0..4])?;
let mm = parse_two_digits(&s[4..6])?;
let dd = parse_two_digits(&s[6..8])?;
let hh = parse_two_digits(&s[8..10])?;
let min = parse_two_digits(&s[10..12])?;
let ss = parse_two_digits(&s[12..14])?;
let dot_pos = 14;
if s.as_bytes()[dot_pos] != b'.' {
return Err(crate::Error::InvalidTime { position: 0 });
}
let fraction_str = &s[15..s.len() - 1]; let milliseconds = if fraction_str.is_empty() {
None
} else {
let ms_str = &fraction_str[..fraction_str.len().min(3)];
let ms = ms_str
.parse::<u16>()
.map_err(|_| crate::Error::InvalidTime { position: 0 })?;
let ms = match ms_str.len() {
1 => ms * 100,
2 => ms * 10,
_ => ms,
};
Some(ms)
};
Self::new(yyyy, mm, dd, hh, min, ss, milliseconds)
} else {
if s.len() != 15 {
return Err(crate::Error::InvalidTime { position: 0 });
}
let yyyy = parse_four_digits(&s[0..4])?;
let mm = parse_two_digits(&s[4..6])?;
let dd = parse_two_digits(&s[6..8])?;
let hh = parse_two_digits(&s[8..10])?;
let min = parse_two_digits(&s[10..12])?;
let ss = parse_two_digits(&s[12..14])?;
Self::new(yyyy, mm, dd, hh, min, ss, None)
}
}
}
#[cfg(feature = "std")]
impl core::fmt::Display for GeneralizedTime {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
if let Some(ms) = self.milliseconds {
write!(
f,
"{:04}{:02}{:02}{:02}{:02}{:02}.{:03}Z",
self.year, self.month, self.day, self.hour, self.minute, self.second, ms
)
} else {
write!(
f,
"{:04}{:02}{:02}{:02}{:02}{:02}Z",
self.year, self.month, self.day, self.hour, self.minute, self.second
)
}
}
}
fn parse_two_digits(s: &str) -> crate::Result<u8> {
if s.len() != 2 {
return Err(crate::Error::InvalidTime { position: 0 });
}
s.parse::<u8>()
.map_err(|_| crate::Error::InvalidTime { position: 0 })
}
fn parse_four_digits(s: &str) -> crate::Result<u16> {
if s.len() != 4 {
return Err(crate::Error::InvalidTime { position: 0 });
}
s.parse::<u16>()
.map_err(|_| crate::Error::InvalidTime { position: 0 })
}
impl crate::traits::Decode<'_> for UtcTime {
fn decode(decoder: &mut crate::der::decoder::Decoder) -> crate::Result<Self> {
use crate::tag::TAG_UTC_TIME;
let tag = decoder.read_tag()?;
let expected_tag = crate::Tag::universal(TAG_UTC_TIME);
if tag != expected_tag {
return Err(crate::Error::UnexpectedTag {
position: decoder.position(),
expected: expected_tag,
actual: tag,
});
}
let length = decoder.read_length()?;
let len = length.definite()?;
let bytes = decoder.read_bytes(len)?;
let s = core::str::from_utf8(bytes).map_err(|_| crate::Error::InvalidTime {
position: decoder.position(),
})?;
UtcTime::from_str(s)
}
}
impl crate::traits::Encode for UtcTime {
fn encode(&self, encoder: &mut crate::der::encoder::Encoder) -> crate::Result<()> {
use crate::tag::TAG_UTC_TIME;
let tag = crate::Tag::universal(TAG_UTC_TIME);
encoder.write_tag(tag)?;
#[cfg(feature = "std")]
let time_str = self.to_string();
#[cfg(not(feature = "std"))]
let time_str = {
use core::fmt::Write;
let mut s = String::new();
let yy = (self.year % 100) as u8;
write!(
&mut s,
"{:02}{:02}{:02}{:02}{:02}{:02}Z",
yy, self.month, self.day, self.hour, self.minute, self.second
)
.map_err(|_| crate::Error::InvalidTime { position: 0 })?;
s
};
encoder.write_length(time_str.len())?;
encoder.write_bytes(time_str.as_bytes());
Ok(())
}
fn encoded_len(&self) -> crate::Result<usize> {
let tag_len = 1;
let content_len = 13; let length_len = crate::Length::Definite(content_len).encoded_len()?;
Ok(tag_len + length_len + content_len)
}
}
impl crate::traits::Tagged for UtcTime {
fn tag() -> crate::Tag {
crate::Tag::universal(crate::tag::TAG_UTC_TIME)
}
}
impl crate::traits::Decode<'_> for GeneralizedTime {
fn decode(decoder: &mut crate::der::decoder::Decoder) -> crate::Result<Self> {
use crate::tag::TAG_GENERALIZED_TIME;
let tag = decoder.read_tag()?;
let expected_tag = crate::Tag::universal(TAG_GENERALIZED_TIME);
if tag != expected_tag {
return Err(crate::Error::UnexpectedTag {
position: decoder.position(),
expected: expected_tag,
actual: tag,
});
}
let length = decoder.read_length()?;
let len = length.definite()?;
let bytes = decoder.read_bytes(len)?;
let s = core::str::from_utf8(bytes).map_err(|_| crate::Error::InvalidTime {
position: decoder.position(),
})?;
GeneralizedTime::from_str(s)
}
}
impl crate::traits::Encode for GeneralizedTime {
fn encode(&self, encoder: &mut crate::der::encoder::Encoder) -> crate::Result<()> {
use crate::tag::TAG_GENERALIZED_TIME;
let tag = crate::Tag::universal(TAG_GENERALIZED_TIME);
encoder.write_tag(tag)?;
#[cfg(feature = "std")]
let time_str = self.to_string();
#[cfg(not(feature = "std"))]
let time_str = {
use core::fmt::Write;
let mut s = String::new();
if let Some(ms) = self.milliseconds {
write!(
&mut s,
"{:04}{:02}{:02}{:02}{:02}{:02}.{:03}Z",
self.year, self.month, self.day, self.hour, self.minute, self.second, ms
)
.map_err(|_| crate::Error::InvalidTime { position: 0 })?;
} else {
write!(
&mut s,
"{:04}{:02}{:02}{:02}{:02}{:02}Z",
self.year, self.month, self.day, self.hour, self.minute, self.second
)
.map_err(|_| crate::Error::InvalidTime { position: 0 })?;
}
s
};
encoder.write_length(time_str.len())?;
encoder.write_bytes(time_str.as_bytes());
Ok(())
}
fn encoded_len(&self) -> crate::Result<usize> {
let tag_len = 1;
let content_len = if self.milliseconds.is_some() {
19 } else {
15 };
let length_len = crate::Length::Definite(content_len).encoded_len()?;
Ok(tag_len + length_len + content_len)
}
}
impl crate::traits::Tagged for GeneralizedTime {
fn tag() -> crate::Tag {
crate::Tag::universal(crate::tag::TAG_GENERALIZED_TIME)
}
}
#[cfg(feature = "serde")]
impl serde::Serialize for UtcTime {
fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
use core::fmt::Write;
let mut buf = String::new();
let yy = (self.year % 100) as u8;
let _ = write!(
buf,
"{:02}{:02}{:02}{:02}{:02}{:02}Z",
yy, self.month, self.day, self.hour, self.minute, self.second
);
s.serialize_str(&buf)
}
}
#[cfg(feature = "serde")]
impl<'de> serde::Deserialize<'de> for UtcTime {
fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
struct V;
impl<'de> serde::de::Visitor<'de> for V {
type Value = UtcTime;
fn expecting(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
write!(f, "a UTCTime string in YYMMDDHHMMSSZ format")
}
fn visit_str<E: serde::de::Error>(self, v: &str) -> Result<UtcTime, E> {
UtcTime::from_str(v).map_err(|_| E::custom("invalid UTCTime string"))
}
}
d.deserialize_str(V)
}
}
#[cfg(feature = "serde")]
impl serde::Serialize for GeneralizedTime {
fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
use core::fmt::Write;
let mut buf = String::new();
if let Some(ms) = self.milliseconds {
let _ = write!(
buf,
"{:04}{:02}{:02}{:02}{:02}{:02}.{:03}Z",
self.year, self.month, self.day, self.hour, self.minute, self.second, ms
);
} else {
let _ = write!(
buf,
"{:04}{:02}{:02}{:02}{:02}{:02}Z",
self.year, self.month, self.day, self.hour, self.minute, self.second
);
}
s.serialize_str(&buf)
}
}
#[cfg(feature = "serde")]
impl<'de> serde::Deserialize<'de> for GeneralizedTime {
fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
struct V;
impl<'de> serde::de::Visitor<'de> for V {
type Value = GeneralizedTime;
fn expecting(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
write!(
f,
"a GeneralizedTime string in YYYYMMDDHHMMSSZ or YYYYMMDDHHMMSS.mmmZ format"
)
}
fn visit_str<E: serde::de::Error>(self, v: &str) -> Result<GeneralizedTime, E> {
GeneralizedTime::from_str(v)
.map_err(|_| E::custom("invalid GeneralizedTime string"))
}
}
d.deserialize_str(V)
}
}