use crate::{
asn1::Any,
datetime::{self, DateTime},
Encodable, Encoder, Error, Header, Length, Result, Tag, Tagged,
};
use core::{convert::TryFrom, time::Duration};
#[cfg(feature = "std")]
use std::time::{SystemTime, UNIX_EPOCH};
const MAX_UNIX_DURATION: Duration = Duration::from_secs(2_524_608_000);
#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
pub struct UtcTime(Duration);
impl UtcTime {
pub const LENGTH: Length = Length::new(13);
pub fn new(unix_duration: Duration) -> Result<Self> {
if unix_duration < MAX_UNIX_DURATION {
Ok(Self(unix_duration))
} else {
Err(Self::TAG.value_error())
}
}
pub fn unix_duration(&self) -> Duration {
self.0
}
#[cfg(feature = "std")]
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
pub fn from_system_time(time: SystemTime) -> Result<Self> {
time.duration_since(UNIX_EPOCH)
.map_err(|_| Self::TAG.value_error())
.and_then(Self::new)
}
#[cfg(feature = "std")]
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
pub fn to_system_time(&self) -> SystemTime {
UNIX_EPOCH + self.unix_duration()
}
}
impl From<&UtcTime> for UtcTime {
fn from(value: &UtcTime) -> UtcTime {
*value
}
}
#[cfg(feature = "std")]
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
impl From<UtcTime> for SystemTime {
fn from(utc_time: UtcTime) -> SystemTime {
utc_time.to_system_time()
}
}
impl TryFrom<Any<'_>> for UtcTime {
type Error = Error;
fn try_from(any: Any<'_>) -> Result<UtcTime> {
any.tag().assert_eq(Self::TAG)?;
match *any.as_bytes() {
[year1, year2, mon1, mon2, day1, day2, hour1, hour2, min1, min2, sec1, sec2, b'Z'] => {
let year = datetime::decode_decimal(Self::TAG, year1, year2)?;
let month = datetime::decode_decimal(Self::TAG, mon1, mon2)?;
let day = datetime::decode_decimal(Self::TAG, day1, day2)?;
let hour = datetime::decode_decimal(Self::TAG, hour1, hour2)?;
let minute = datetime::decode_decimal(Self::TAG, min1, min2)?;
let second = datetime::decode_decimal(Self::TAG, sec1, sec2)?;
let year = if year >= 50 { year + 1900 } else { year + 2000 };
DateTime::new(year, month, day, hour, minute, second)
.and_then(|dt| dt.unix_duration())
.ok_or_else(|| Self::TAG.value_error())
.and_then(Self::new)
}
_ => Err(Self::TAG.value_error()),
}
}
}
impl Encodable for UtcTime {
fn encoded_len(&self) -> Result<Length> {
Self::LENGTH.for_tlv()
}
fn encode(&self, encoder: &mut Encoder<'_>) -> Result<()> {
Header::new(Self::TAG, Self::LENGTH)?.encode(encoder)?;
let datetime =
DateTime::from_unix_duration(self.0).ok_or_else(|| Self::TAG.value_error())?;
debug_assert!((1950..2050).contains(&datetime.year()));
let year = match datetime.year() {
y @ 1950..=1999 => y - 1900,
y @ 2000..=2049 => y - 2000,
_ => return Err(Self::TAG.value_error()),
};
datetime::encode_decimal(encoder, Self::TAG, year)?;
datetime::encode_decimal(encoder, Self::TAG, datetime.month())?;
datetime::encode_decimal(encoder, Self::TAG, datetime.day())?;
datetime::encode_decimal(encoder, Self::TAG, datetime.hour())?;
datetime::encode_decimal(encoder, Self::TAG, datetime.minute())?;
datetime::encode_decimal(encoder, Self::TAG, datetime.second())?;
encoder.byte(b'Z')
}
}
impl Tagged for UtcTime {
const TAG: Tag = Tag::UtcTime;
}
#[cfg(test)]
mod tests {
use super::{DateTime, UtcTime};
use crate::{Decodable, Encodable, Encoder};
use hex_literal::hex;
#[test]
fn round_trip_vector() {
let example_bytes = hex!("17 0d 39 31 30 35 30 36 32 33 34 35 34 30 5a");
let utc_time = UtcTime::from_der(&example_bytes).unwrap();
assert_eq!(utc_time.unix_duration().as_secs(), 673573540);
let mut buf = [0u8; 128];
let mut encoder = Encoder::new(&mut buf);
utc_time.encode(&mut encoder).unwrap();
assert_eq!(example_bytes, encoder.finish().unwrap());
}
#[test]
fn round_trip_examples() {
for year in 1970..=2049 {
for month in 1..=12 {
let max_day = if month == 2 { 28 } else { 30 };
for day in 1..=max_day {
for hour in 0..=23 {
let datetime1 = DateTime::new(year, month, day, hour, 0, 0).unwrap();
let utc_time1 = UtcTime::new(datetime1.unix_duration().unwrap()).unwrap();
let mut buf = [0u8; 128];
let mut encoder = Encoder::new(&mut buf);
utc_time1.encode(&mut encoder).unwrap();
let der_bytes = encoder.finish().unwrap();
let utc_time2 = UtcTime::from_der(der_bytes).unwrap();
assert_eq!(utc_time1, utc_time2);
}
}
}
}
}
}