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