1use crate::*;
2use core::convert::TryFrom;
3use core::fmt;
4#[cfg(feature = "datetime")]
5use time::OffsetDateTime;
6
7#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
8pub struct UtcTime(pub ASN1DateTime);
9
10impl UtcTime {
11 pub const fn new(datetime: ASN1DateTime) -> Self {
12 UtcTime(datetime)
13 }
14
15 pub fn from_bytes(bytes: &[u8]) -> Result<Self> {
16 let (year, month, day, hour, minute, rem) = match bytes {
29 [year1, year2, mon1, mon2, day1, day2, hour1, hour2, min1, min2, rem @ ..] => {
30 let year = decode_decimal(Self::TAG, *year1, *year2)?;
31 let month = decode_decimal(Self::TAG, *mon1, *mon2)?;
32 let day = decode_decimal(Self::TAG, *day1, *day2)?;
33 let hour = decode_decimal(Self::TAG, *hour1, *hour2)?;
34 let minute = decode_decimal(Self::TAG, *min1, *min2)?;
35 (year, month, day, hour, minute, rem)
36 }
37 _ => return Err(Self::TAG.invalid_value("malformed time string (not yymmddhhmm)")),
38 };
39 if rem.is_empty() {
40 return Err(Self::TAG.invalid_value("malformed time string"));
41 }
42 let (second, rem) = match rem {
44 [sec1, sec2, rem @ ..] => {
45 let second = decode_decimal(Self::TAG, *sec1, *sec2)?;
46 (second, rem)
47 }
48 _ => (0, rem),
49 };
50 if month > 12 || day > 31 || hour > 23 || minute > 59 || second > 59 {
51 return Err(Self::TAG.invalid_value("time components with invalid values"));
52 }
53 if rem.is_empty() {
54 return Err(Self::TAG.invalid_value("malformed time string"));
55 }
56 let tz = match rem {
57 [b'Z'] => ASN1TimeZone::Z,
58 [b'+', h1, h2, m1, m2] => {
59 let hh = decode_decimal(Self::TAG, *h1, *h2)?;
60 let mm = decode_decimal(Self::TAG, *m1, *m2)?;
61 ASN1TimeZone::Offset(hh as i8, mm as i8)
62 }
63 [b'-', h1, h2, m1, m2] => {
64 let hh = decode_decimal(Self::TAG, *h1, *h2)?;
65 let mm = decode_decimal(Self::TAG, *m1, *m2)?;
66 ASN1TimeZone::Offset(-(hh as i8), mm as i8)
67 }
68 _ => return Err(Self::TAG.invalid_value("malformed time string: no time zone")),
69 };
70 Ok(UtcTime(ASN1DateTime::new(
71 year as u32,
72 month,
73 day,
74 hour,
75 minute,
76 second,
77 None,
78 tz,
79 )))
80 }
97
98 #[cfg(feature = "datetime")]
100 #[cfg_attr(docsrs, doc(cfg(feature = "datetime")))]
101 #[inline]
102 pub fn utc_datetime(&self) -> Result<OffsetDateTime> {
103 self.0.to_datetime()
104 }
105
106 #[cfg(feature = "datetime")]
110 #[cfg_attr(docsrs, doc(cfg(feature = "datetime")))]
111 #[inline]
112 pub fn utc_adjusted_datetime(&self) -> Result<OffsetDateTime> {
113 self.0.to_datetime().and_then(|dt| {
114 let year = dt.year();
115 let year = if year >= 50 { year + 1900 } else { year + 2000 };
118 time::Date::from_calendar_date(year, dt.month(), dt.day())
119 .map(|d| dt.replace_date(d))
120 .map_err(|_e| Self::TAG.invalid_value("Invalid adjusted date"))
121 })
122 }
123
124 #[cfg(feature = "datetime")]
126 #[cfg_attr(docsrs, doc(cfg(feature = "datetime")))]
127 pub fn timestamp(&self) -> Result<i64> {
128 let dt = self.0.to_datetime()?;
129 Ok(dt.unix_timestamp())
130 }
131}
132
133impl<'a> TryFrom<Any<'a>> for UtcTime {
134 type Error = Error;
135
136 fn try_from(any: Any<'a>) -> Result<UtcTime> {
137 TryFrom::try_from(&any)
138 }
139}
140
141impl<'a, 'b> TryFrom<&'b Any<'a>> for UtcTime {
142 type Error = Error;
143
144 fn try_from(any: &'b Any<'a>) -> Result<UtcTime> {
145 any.tag().assert_eq(Self::TAG)?;
146 #[allow(clippy::trivially_copy_pass_by_ref)]
147 fn is_visible(b: &u8) -> bool {
148 0x20 <= *b && *b <= 0x7f
149 }
150 if !any.data.iter().all(is_visible) {
151 return Err(Error::StringInvalidCharset);
152 }
153
154 UtcTime::from_bytes(any.data)
155 }
156}
157
158impl fmt::Display for UtcTime {
159 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
160 let dt = &self.0;
161 match dt.tz {
162 ASN1TimeZone::Z | ASN1TimeZone::Undefined => write!(
163 f,
164 "{:04}-{:02}-{:02} {:02}:{:02}:{:02}Z",
165 dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second
166 ),
167 ASN1TimeZone::Offset(hh, mm) => {
168 let (s, hh) = if hh > 0 { ('+', hh) } else { ('-', -hh) };
169 write!(
170 f,
171 "{:04}-{:02}-{:02} {:02}:{:02}:{:02}{}{:02}{:02}",
172 dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second, s, hh, mm
173 )
174 }
175 }
176 }
177}
178
179impl CheckDerConstraints for UtcTime {
180 fn check_constraints(_any: &Any) -> Result<()> {
181 Ok(())
182 }
183}
184
185impl DerAutoDerive for UtcTime {}
186
187impl Tagged for UtcTime {
188 const TAG: Tag = Tag::UtcTime;
189}
190
191#[cfg(feature = "std")]
192impl ToDer for UtcTime {
193 fn to_der_len(&self) -> Result<usize> {
194 Ok(15)
205 }
206
207 fn write_der_header(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
208 writer.write(&[Self::TAG.0 as u8, 13]).map_err(Into::into)
210 }
211
212 fn write_der_content(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
213 write!(
214 writer,
215 "{:02}{:02}{:02}{:02}{:02}{:02}Z",
216 self.0.year, self.0.month, self.0.day, self.0.hour, self.0.minute, self.0.second,
217 )?;
218 Ok(13)
220 }
221}