1use crate::{
4 DecodeValue, EncodeValue, Error, ErrorKind, FixedTag, Header, Length, Reader, Result, Tag,
5 ValueOrd, Writer,
6 datetime::{self, DateTime},
7};
8use core::{cmp::Ordering, time::Duration};
9
10#[cfg(feature = "std")]
11use std::time::SystemTime;
12
13#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
32pub struct UtcTime(DateTime);
33
34impl UtcTime {
35 pub const LENGTH: usize = 13;
37
38 pub const MAX_YEAR: u16 = 2049;
40
41 pub fn from_date_time(datetime: DateTime) -> Result<Self> {
46 if datetime.year() <= UtcTime::MAX_YEAR {
47 Ok(Self(datetime))
48 } else {
49 Err(Self::TAG.value_error().into())
50 }
51 }
52
53 #[must_use]
55 pub fn to_date_time(&self) -> DateTime {
56 self.0
57 }
58
59 pub fn from_unix_duration(unix_duration: Duration) -> Result<Self> {
65 DateTime::from_unix_duration(unix_duration)?.try_into()
66 }
67
68 #[must_use]
70 pub fn to_unix_duration(&self) -> Duration {
71 self.0.unix_duration()
72 }
73
74 #[cfg(feature = "std")]
79 pub fn from_system_time(time: SystemTime) -> Result<Self> {
80 DateTime::try_from(time)
81 .map_err(|_| Self::TAG.value_error())?
82 .try_into()
83 }
84
85 #[cfg(feature = "std")]
87 #[must_use]
88 pub fn to_system_time(&self) -> SystemTime {
89 self.0.to_system_time()
90 }
91}
92
93impl_any_conversions!(UtcTime);
94
95impl<'a> DecodeValue<'a> for UtcTime {
96 type Error = Error;
97
98 fn decode_value<R: Reader<'a>>(reader: &mut R, header: Header) -> Result<Self> {
99 if Self::LENGTH != usize::try_from(header.length())? {
100 return Err(reader.error(Self::TAG.value_error()));
101 }
102
103 let mut bytes = [0u8; Self::LENGTH];
104 reader.read_into(&mut bytes)?;
105
106 match bytes {
107 [
109 year1,
110 year2,
111 mon1,
112 mon2,
113 day1,
114 day2,
115 hour1,
116 hour2,
117 min1,
118 min2,
119 sec1,
120 sec2,
121 b'Z',
122 ] => {
123 let year = u16::from(datetime::decode_decimal(Self::TAG, year1, year2)?);
124 let month = datetime::decode_decimal(Self::TAG, mon1, mon2)?;
125 let day = datetime::decode_decimal(Self::TAG, day1, day2)?;
126 let hour = datetime::decode_decimal(Self::TAG, hour1, hour2)?;
127 let minute = datetime::decode_decimal(Self::TAG, min1, min2)?;
128 let second = datetime::decode_decimal(Self::TAG, sec1, sec2)?;
129
130 let year = if year >= 50 {
132 year.checked_add(1900)
133 } else {
134 year.checked_add(2000)
135 }
136 .ok_or(ErrorKind::DateTime)?;
137
138 DateTime::new(year, month, day, hour, minute, second)
139 .map_err(|_| reader.error(Self::TAG.value_error()))
140 .and_then(|dt| Self::from_unix_duration(dt.unix_duration()))
141 }
142 _ => Err(reader.error(Self::TAG.value_error())),
143 }
144 }
145}
146
147impl EncodeValue for UtcTime {
148 fn value_len(&self) -> Result<Length> {
149 Self::LENGTH.try_into()
150 }
151
152 fn encode_value(&self, writer: &mut impl Writer) -> Result<()> {
153 let year = match self.0.year() {
154 y @ 1950..=1999 => y.checked_sub(1900),
155 y @ 2000..=2049 => y.checked_sub(2000),
156 _ => return Err(Self::TAG.value_error().into()),
157 }
158 .and_then(|y| u8::try_from(y).ok())
159 .ok_or(ErrorKind::DateTime)?;
160
161 datetime::encode_decimal(writer, Self::TAG, year)?;
162 datetime::encode_decimal(writer, Self::TAG, self.0.month())?;
163 datetime::encode_decimal(writer, Self::TAG, self.0.day())?;
164 datetime::encode_decimal(writer, Self::TAG, self.0.hour())?;
165 datetime::encode_decimal(writer, Self::TAG, self.0.minutes())?;
166 datetime::encode_decimal(writer, Self::TAG, self.0.seconds())?;
167 writer.write_byte(b'Z')
168 }
169}
170
171impl FixedTag for UtcTime {
172 const TAG: Tag = Tag::UtcTime;
173}
174
175impl ValueOrd for UtcTime {
179 fn value_cmp(&self, other: &Self) -> Result<Ordering> {
180 let fields = |t: &Self| {
183 let dt = &t.0;
184 (
185 dt.year() % 100,
186 dt.month(),
187 dt.day(),
188 dt.hour(),
189 dt.minutes(),
190 dt.seconds(),
191 )
192 };
193 Ok(fields(self).cmp(&fields(other)))
194 }
195}
196
197impl From<&UtcTime> for UtcTime {
198 fn from(value: &UtcTime) -> UtcTime {
199 *value
200 }
201}
202
203impl From<UtcTime> for DateTime {
204 fn from(utc_time: UtcTime) -> DateTime {
205 utc_time.0
206 }
207}
208
209impl From<&UtcTime> for DateTime {
210 fn from(utc_time: &UtcTime) -> DateTime {
211 utc_time.0
212 }
213}
214
215impl TryFrom<DateTime> for UtcTime {
216 type Error = Error;
217
218 fn try_from(datetime: DateTime) -> Result<Self> {
219 Self::from_date_time(datetime)
220 }
221}
222
223impl TryFrom<&DateTime> for UtcTime {
224 type Error = Error;
225
226 fn try_from(datetime: &DateTime) -> Result<Self> {
227 Self::from_date_time(*datetime)
228 }
229}
230
231#[cfg(feature = "std")]
232impl From<UtcTime> for SystemTime {
233 fn from(utc_time: UtcTime) -> SystemTime {
234 utc_time.to_system_time()
235 }
236}
237
238#[cfg(feature = "arbitrary")]
243#[allow(clippy::unwrap_in_result)]
244impl<'a> arbitrary::Arbitrary<'a> for UtcTime {
245 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
246 const MIN_YEAR: u16 = 1970;
247 const VALID_YEAR_COUNT: u16 = UtcTime::MAX_YEAR - MIN_YEAR + 1;
248 const AVERAGE_SECONDS_IN_YEAR: u64 = 31_556_952;
249
250 let datetime = DateTime::arbitrary(u)?;
251 let year = datetime.year();
252 let duration = datetime.unix_duration();
253
254 let valid_year = (year.saturating_sub(MIN_YEAR))
256 .rem_euclid(VALID_YEAR_COUNT)
257 .saturating_add(MIN_YEAR);
258 let year_to_remove = year.saturating_sub(valid_year);
259 let valid_duration = duration
260 - Duration::from_secs(
261 u64::from(year_to_remove).saturating_mul(AVERAGE_SECONDS_IN_YEAR),
262 );
263
264 Self::from_date_time(DateTime::from_unix_duration(valid_duration).expect("supported range"))
265 .map_err(|_| arbitrary::Error::IncorrectFormat)
266 }
267
268 fn size_hint(depth: usize) -> (usize, Option<usize>) {
269 DateTime::size_hint(depth)
270 }
271}
272
273#[cfg(test)]
274#[allow(clippy::unwrap_used)]
275mod tests {
276 use super::UtcTime;
277 use crate::{Decode, DerOrd, Encode, SliceWriter, ValueOrd};
278 use core::cmp::Ordering;
279 use hex_literal::hex;
280
281 #[test]
282 fn value_ord_is_encoding_order() {
283 let y1999 = UtcTime::from_der(&hex!("170d 3939313233313233353935395a")).unwrap();
285 let y2000 = UtcTime::from_der(&hex!("170d 3030303130313030303030305a")).unwrap();
286 assert!(y1999 < y2000); assert_eq!(y2000.value_cmp(&y1999), Ok(Ordering::Less));
288 assert_eq!(y2000.der_cmp(&y1999), Ok(Ordering::Less));
289 assert_eq!(y1999.value_cmp(&y1999), Ok(Ordering::Equal));
290 }
291
292 #[test]
293 fn round_trip_vector() {
294 let example_bytes = hex!("17 0d 39 31 30 35 30 36 32 33 34 35 34 30 5a");
295 let utc_time = UtcTime::from_der(&example_bytes).unwrap();
296 assert_eq!(utc_time.to_unix_duration().as_secs(), 673573540);
297
298 let mut buf = [0u8; 128];
299 let mut writer = SliceWriter::new(&mut buf);
300 utc_time.encode(&mut writer).unwrap();
301 assert_eq!(example_bytes, writer.finish().unwrap());
302 }
303}