#[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>,
}
pub fn parse2(b: &[u8]) -> Option<u8> {
if b.len() != 2 || !b[0].is_ascii_digit() || !b[1].is_ascii_digit() {
return None;
}
Some((b[0] - b'0') * 10 + (b[1] - b'0'))
}
pub fn parse4(b: &[u8]) -> Option<u16> {
if b.len() != 4 || b.iter().any(|c| !c.is_ascii_digit()) {
return None;
}
Some(
(b[0] - b'0') as u16 * 1_000
+ (b[1] - b'0') as u16 * 100
+ (b[2] - b'0') as u16 * 10
+ (b[3] - b'0') as u16,
)
}
pub fn expand_yy(yy: u8) -> u16 {
if yy >= 50 {
1900u16 + yy as u16
} else {
2000u16 + yy as u16
}
}
impl GeneralizedTime {
#[cfg(feature = "std")]
pub fn parse(s: &str) -> Result<Self, String> {
let b = s.as_bytes();
let (year, rest) = if s.len() == 15 && s.ends_with('Z') {
let year = parse4(&b[0..4]).ok_or_else(|| format!("invalid year in time '{s}'"))?;
(year, &b[4..])
} else if s.len() == 13 && s.ends_with('Z') {
let yy = parse2(&b[0..2]).ok_or_else(|| format!("invalid year in time '{s}'"))?;
(expand_yy(yy), &b[2..])
} else {
return Err(format!(
"unsupported time format '{s}': expected YYYYMMDDHHmmssZ or YYMMDDHHmmssZ"
));
};
let month = parse2(&rest[0..2]).ok_or_else(|| format!("invalid month in time '{s}'"))?;
let day = parse2(&rest[2..4]).ok_or_else(|| format!("invalid day in time '{s}'"))?;
let hour = parse2(&rest[4..6]).ok_or_else(|| format!("invalid hour in time '{s}'"))?;
let minute = parse2(&rest[6..8]).ok_or_else(|| format!("invalid minute in time '{s}'"))?;
let second = parse2(&rest[8..10]).ok_or_else(|| format!("invalid second in time '{s}'"))?;
Self::new(year, month, day, hour, minute, second, None)
.map_err(|e| format!("invalid GeneralizedTime '{s}': {e}"))
}
pub fn to_unix(&self) -> i64 {
let y = self.year as i64;
let m = self.month as i64;
let d = self.day as i64;
let y_adj = y - if m <= 2 { 1 } else { 0 };
let era = y_adj.div_euclid(400);
let yoe = y_adj - era * 400;
let doy = (153 * (m + if m > 2 { -3 } else { 9 }) + 2) / 5 + d - 1;
let doe = yoe * 365 + yoe / 4 - yoe / 100 + doy;
let days = era * 146_097 + doe - 719_468;
let sod = self.hour as i64 * 3_600 + self.minute as i64 * 60 + self.second as i64;
days * 86_400 + sod
}
pub fn from_unix(secs: i64) -> Option<Self> {
let days = secs.div_euclid(86_400);
let sod = secs.rem_euclid(86_400);
let z = days + 719_468;
let era = z.div_euclid(146_097);
let doe = z - era * 146_097;
let yoe = (doe - doe / 1_460 + doe / 36_524 - doe / 146_096) / 365;
let y = yoe + era * 400;
let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
let mp = (5 * doy + 2) / 153;
let d = doy - (153 * mp + 2) / 5 + 1;
let m = if mp < 10 { mp + 3 } else { mp - 9 };
let year = if m <= 2 { y + 1 } else { y };
let month = m;
let day = d;
if !(1..=9999).contains(&year) {
return None;
}
let hour = (sod / 3_600) as u8;
let minute = ((sod % 3_600) / 60) as u8;
let second = (sod % 60) as u8;
Self::new(
year as u16,
month as u8,
day as u8,
hour,
minute,
second,
None,
)
.ok()
}
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)
}
}