1 //! Date and time functionality shared between various ASN.1 types
2 //! (e.g. `GeneralizedTime`, `UTCTime`)
3 
4 // Adapted from the `humantime` crate.
5 // Copyright (c) 2016 The humantime Developers
6 // Released under the MIT OR Apache 2.0 licenses
7 
8 use crate::{Error, ErrorKind, Result, Tag, Writer};
9 use core::{fmt, str::FromStr, time::Duration};
10 
11 #[cfg(feature = "std")]
12 use std::time::{SystemTime, UNIX_EPOCH};
13 
14 #[cfg(feature = "time")]
15 use time::PrimitiveDateTime;
16 
17 /// Minimum year allowed in [`DateTime`] values.
18 const MIN_YEAR: u16 = 1970;
19 
20 /// Maximum duration since `UNIX_EPOCH` which can be represented as a
21 /// [`DateTime`] (non-inclusive).
22 ///
23 /// This corresponds to: 9999-12-31T23:59:59Z
24 const MAX_UNIX_DURATION: Duration = Duration::from_secs(253_402_300_799);
25 
26 /// Date-and-time type shared by multiple ASN.1 types
27 /// (e.g. `GeneralizedTime`, `UTCTime`).
28 ///
29 /// Following conventions from RFC 5280, this type is always Z-normalized
30 /// (i.e. represents a UTC time). However, it isn't named "UTC time" in order
31 /// to prevent confusion with ASN.1 `UTCTime`.
32 #[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
33 pub struct DateTime {
34     /// Full year (e.g. 2000).
35     ///
36     /// Must be >=1970 to permit positive conversions to Unix time.
37     year: u16,
38 
39     /// Month (1-12)
40     month: u8,
41 
42     /// Day of the month (1-31)
43     day: u8,
44 
45     /// Hour (0-23)
46     hour: u8,
47 
48     /// Minutes (0-59)
49     minutes: u8,
50 
51     /// Seconds (0-59)
52     seconds: u8,
53 
54     /// [`Duration`] since the Unix epoch.
55     unix_duration: Duration,
56 }
57 
58 impl DateTime {
59     /// This is the maximum date represented by the [`DateTime`]
60     /// This corresponds to: 9999-12-31T23:59:59Z
61     pub const INFINITY: DateTime = DateTime {
62         year: 9999,
63         month: 12,
64         day: 31,
65         hour: 23,
66         minutes: 59,
67         seconds: 59,
68         unix_duration: MAX_UNIX_DURATION,
69     };
70 
71     /// Create a new [`DateTime`] from the given UTC time components.
72     // TODO(tarcieri): checked arithmetic
73     #[allow(clippy::integer_arithmetic)]
new(year: u16, month: u8, day: u8, hour: u8, minutes: u8, seconds: u8) -> Result<Self>74     pub fn new(year: u16, month: u8, day: u8, hour: u8, minutes: u8, seconds: u8) -> Result<Self> {
75         // Basic validation of the components.
76         if year < MIN_YEAR
77             || !(1..=12).contains(&month)
78             || !(1..=31).contains(&day)
79             || !(0..=23).contains(&hour)
80             || !(0..=59).contains(&minutes)
81             || !(0..=59).contains(&seconds)
82         {
83             return Err(ErrorKind::DateTime.into());
84         }
85 
86         let leap_years =
87             ((year - 1) - 1968) / 4 - ((year - 1) - 1900) / 100 + ((year - 1) - 1600) / 400;
88 
89         let is_leap_year = year % 4 == 0 && (year % 100 != 0 || year % 400 == 0);
90 
91         let (mut ydays, mdays): (u16, u8) = match month {
92             1 => (0, 31),
93             2 if is_leap_year => (31, 29),
94             2 => (31, 28),
95             3 => (59, 31),
96             4 => (90, 30),
97             5 => (120, 31),
98             6 => (151, 30),
99             7 => (181, 31),
100             8 => (212, 31),
101             9 => (243, 30),
102             10 => (273, 31),
103             11 => (304, 30),
104             12 => (334, 31),
105             _ => return Err(ErrorKind::DateTime.into()),
106         };
107 
108         if day > mdays || day == 0 {
109             return Err(ErrorKind::DateTime.into());
110         }
111 
112         ydays += u16::from(day) - 1;
113 
114         if is_leap_year && month > 2 {
115             ydays += 1;
116         }
117 
118         let days = u64::from(year - 1970) * 365 + u64::from(leap_years) + u64::from(ydays);
119         let time = u64::from(seconds) + (u64::from(minutes) * 60) + (u64::from(hour) * 3600);
120         let unix_duration = Duration::from_secs(time + days * 86400);
121 
122         if unix_duration > MAX_UNIX_DURATION {
123             return Err(ErrorKind::DateTime.into());
124         }
125 
126         Ok(Self {
127             year,
128             month,
129             day,
130             hour,
131             minutes,
132             seconds,
133             unix_duration,
134         })
135     }
136 
137     /// Compute a [`DateTime`] from the given [`Duration`] since the `UNIX_EPOCH`.
138     ///
139     /// Returns `None` if the value is outside the supported date range.
140     // TODO(tarcieri): checked arithmetic
141     #[allow(clippy::integer_arithmetic)]
from_unix_duration(unix_duration: Duration) -> Result<Self>142     pub fn from_unix_duration(unix_duration: Duration) -> Result<Self> {
143         if unix_duration > MAX_UNIX_DURATION {
144             return Err(ErrorKind::DateTime.into());
145         }
146 
147         let secs_since_epoch = unix_duration.as_secs();
148 
149         /// 2000-03-01 (mod 400 year, immediately after Feb 29)
150         const LEAPOCH: i64 = 11017;
151         const DAYS_PER_400Y: i64 = 365 * 400 + 97;
152         const DAYS_PER_100Y: i64 = 365 * 100 + 24;
153         const DAYS_PER_4Y: i64 = 365 * 4 + 1;
154 
155         let days = i64::try_from(secs_since_epoch / 86400)? - LEAPOCH;
156         let secs_of_day = secs_since_epoch % 86400;
157 
158         let mut qc_cycles = days / DAYS_PER_400Y;
159         let mut remdays = days % DAYS_PER_400Y;
160 
161         if remdays < 0 {
162             remdays += DAYS_PER_400Y;
163             qc_cycles -= 1;
164         }
165 
166         let mut c_cycles = remdays / DAYS_PER_100Y;
167         if c_cycles == 4 {
168             c_cycles -= 1;
169         }
170         remdays -= c_cycles * DAYS_PER_100Y;
171 
172         let mut q_cycles = remdays / DAYS_PER_4Y;
173         if q_cycles == 25 {
174             q_cycles -= 1;
175         }
176         remdays -= q_cycles * DAYS_PER_4Y;
177 
178         let mut remyears = remdays / 365;
179         if remyears == 4 {
180             remyears -= 1;
181         }
182         remdays -= remyears * 365;
183 
184         let mut year = 2000 + remyears + 4 * q_cycles + 100 * c_cycles + 400 * qc_cycles;
185 
186         let months = [31, 30, 31, 30, 31, 31, 30, 31, 30, 31, 31, 29];
187         let mut mon = 0;
188         for mon_len in months.iter() {
189             mon += 1;
190             if remdays < *mon_len {
191                 break;
192             }
193             remdays -= *mon_len;
194         }
195         let mday = remdays + 1;
196         let mon = if mon + 2 > 12 {
197             year += 1;
198             mon - 10
199         } else {
200             mon + 2
201         };
202 
203         let second = secs_of_day % 60;
204         let mins_of_day = secs_of_day / 60;
205         let minute = mins_of_day % 60;
206         let hour = mins_of_day / 60;
207 
208         Self::new(
209             year.try_into()?,
210             mon,
211             mday.try_into()?,
212             hour.try_into()?,
213             minute.try_into()?,
214             second.try_into()?,
215         )
216     }
217 
218     /// Get the year.
year(&self) -> u16219     pub fn year(&self) -> u16 {
220         self.year
221     }
222 
223     /// Get the month.
month(&self) -> u8224     pub fn month(&self) -> u8 {
225         self.month
226     }
227 
228     /// Get the day.
day(&self) -> u8229     pub fn day(&self) -> u8 {
230         self.day
231     }
232 
233     /// Get the hour.
hour(&self) -> u8234     pub fn hour(&self) -> u8 {
235         self.hour
236     }
237 
238     /// Get the minutes.
minutes(&self) -> u8239     pub fn minutes(&self) -> u8 {
240         self.minutes
241     }
242 
243     /// Get the seconds.
seconds(&self) -> u8244     pub fn seconds(&self) -> u8 {
245         self.seconds
246     }
247 
248     /// Compute [`Duration`] since `UNIX_EPOCH` from the given calendar date.
unix_duration(&self) -> Duration249     pub fn unix_duration(&self) -> Duration {
250         self.unix_duration
251     }
252 
253     /// Instantiate from [`SystemTime`].
254     #[cfg(feature = "std")]
from_system_time(time: SystemTime) -> Result<Self>255     pub fn from_system_time(time: SystemTime) -> Result<Self> {
256         time.duration_since(UNIX_EPOCH)
257             .map_err(|_| ErrorKind::DateTime.into())
258             .and_then(Self::from_unix_duration)
259     }
260 
261     /// Convert to [`SystemTime`].
262     #[cfg(feature = "std")]
to_system_time(&self) -> SystemTime263     pub fn to_system_time(&self) -> SystemTime {
264         UNIX_EPOCH + self.unix_duration()
265     }
266 }
267 
268 impl FromStr for DateTime {
269     type Err = Error;
270 
from_str(s: &str) -> Result<Self>271     fn from_str(s: &str) -> Result<Self> {
272         match *s.as_bytes() {
273             [year1, year2, year3, year4, b'-', month1, month2, b'-', day1, day2, b'T', hour1, hour2, b':', min1, min2, b':', sec1, sec2, b'Z'] =>
274             {
275                 let tag = Tag::GeneralizedTime;
276                 let year = decode_year(&[year1, year2, year3, year4])?;
277                 let month = decode_decimal(tag, month1, month2).map_err(|_| ErrorKind::DateTime)?;
278                 let day = decode_decimal(tag, day1, day2).map_err(|_| ErrorKind::DateTime)?;
279                 let hour = decode_decimal(tag, hour1, hour2).map_err(|_| ErrorKind::DateTime)?;
280                 let minutes = decode_decimal(tag, min1, min2).map_err(|_| ErrorKind::DateTime)?;
281                 let seconds = decode_decimal(tag, sec1, sec2).map_err(|_| ErrorKind::DateTime)?;
282                 Self::new(year, month, day, hour, minutes, seconds)
283             }
284             _ => Err(ErrorKind::DateTime.into()),
285         }
286     }
287 }
288 
289 impl fmt::Display for DateTime {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result290     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
291         write!(
292             f,
293             "{:02}-{:02}-{:02}T{:02}:{:02}:{:02}Z",
294             self.year, self.month, self.day, self.hour, self.minutes, self.seconds
295         )
296     }
297 }
298 
299 #[cfg(feature = "std")]
300 impl From<DateTime> for SystemTime {
from(time: DateTime) -> SystemTime301     fn from(time: DateTime) -> SystemTime {
302         time.to_system_time()
303     }
304 }
305 
306 #[cfg(feature = "std")]
307 impl From<&DateTime> for SystemTime {
from(time: &DateTime) -> SystemTime308     fn from(time: &DateTime) -> SystemTime {
309         time.to_system_time()
310     }
311 }
312 
313 #[cfg(feature = "std")]
314 impl TryFrom<SystemTime> for DateTime {
315     type Error = Error;
316 
try_from(time: SystemTime) -> Result<DateTime>317     fn try_from(time: SystemTime) -> Result<DateTime> {
318         DateTime::from_system_time(time)
319     }
320 }
321 
322 #[cfg(feature = "std")]
323 impl TryFrom<&SystemTime> for DateTime {
324     type Error = Error;
325 
try_from(time: &SystemTime) -> Result<DateTime>326     fn try_from(time: &SystemTime) -> Result<DateTime> {
327         DateTime::from_system_time(*time)
328     }
329 }
330 
331 #[cfg(feature = "time")]
332 impl TryFrom<DateTime> for PrimitiveDateTime {
333     type Error = Error;
334 
try_from(time: DateTime) -> Result<PrimitiveDateTime>335     fn try_from(time: DateTime) -> Result<PrimitiveDateTime> {
336         let month = time.month().try_into()?;
337         let date = time::Date::from_calendar_date(i32::from(time.year()), month, time.day())?;
338         let time = time::Time::from_hms(time.hour(), time.minutes(), time.seconds())?;
339 
340         Ok(PrimitiveDateTime::new(date, time))
341     }
342 }
343 
344 #[cfg(feature = "time")]
345 impl TryFrom<PrimitiveDateTime> for DateTime {
346     type Error = Error;
347 
try_from(time: PrimitiveDateTime) -> Result<DateTime>348     fn try_from(time: PrimitiveDateTime) -> Result<DateTime> {
349         DateTime::new(
350             time.year().try_into().map_err(|_| ErrorKind::DateTime)?,
351             time.month().into(),
352             time.day(),
353             time.hour(),
354             time.minute(),
355             time.second(),
356         )
357     }
358 }
359 
360 // Implement by hand because the derive would create invalid values.
361 // Use the conversion from Duration to create a valid value.
362 #[cfg(feature = "arbitrary")]
363 impl<'a> arbitrary::Arbitrary<'a> for DateTime {
arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self>364     fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
365         Self::from_unix_duration(Duration::new(
366             u.int_in_range(0..=MAX_UNIX_DURATION.as_secs().saturating_sub(1))?,
367             u.int_in_range(0..=999_999_999)?,
368         ))
369         .map_err(|_| arbitrary::Error::IncorrectFormat)
370     }
371 
size_hint(depth: usize) -> (usize, Option<usize>)372     fn size_hint(depth: usize) -> (usize, Option<usize>) {
373         arbitrary::size_hint::and(u64::size_hint(depth), u32::size_hint(depth))
374     }
375 }
376 
377 /// Decode 2-digit decimal value
378 // TODO(tarcieri): checked arithmetic
379 #[allow(clippy::integer_arithmetic)]
decode_decimal(tag: Tag, hi: u8, lo: u8) -> Result<u8>380 pub(crate) fn decode_decimal(tag: Tag, hi: u8, lo: u8) -> Result<u8> {
381     if hi.is_ascii_digit() && lo.is_ascii_digit() {
382         Ok((hi - b'0') * 10 + (lo - b'0'))
383     } else {
384         Err(tag.value_error())
385     }
386 }
387 
388 /// Encode 2-digit decimal value
encode_decimal<W>(writer: &mut W, tag: Tag, value: u8) -> Result<()> where W: Writer + ?Sized,389 pub(crate) fn encode_decimal<W>(writer: &mut W, tag: Tag, value: u8) -> Result<()>
390 where
391     W: Writer + ?Sized,
392 {
393     let hi_val = value / 10;
394 
395     if hi_val >= 10 {
396         return Err(tag.value_error());
397     }
398 
399     writer.write_byte(b'0'.checked_add(hi_val).ok_or(ErrorKind::Overflow)?)?;
400     writer.write_byte(b'0'.checked_add(value % 10).ok_or(ErrorKind::Overflow)?)
401 }
402 
403 /// Decode 4-digit year.
404 // TODO(tarcieri): checked arithmetic
405 #[allow(clippy::integer_arithmetic)]
decode_year(year: &[u8; 4]) -> Result<u16>406 fn decode_year(year: &[u8; 4]) -> Result<u16> {
407     let tag = Tag::GeneralizedTime;
408     let hi = decode_decimal(tag, year[0], year[1]).map_err(|_| ErrorKind::DateTime)?;
409     let lo = decode_decimal(tag, year[2], year[3]).map_err(|_| ErrorKind::DateTime)?;
410     Ok(u16::from(hi) * 100 + u16::from(lo))
411 }
412 
413 #[cfg(test)]
414 mod tests {
415     use super::DateTime;
416 
417     /// Ensure a day is OK
is_date_valid(year: u16, month: u8, day: u8, hour: u8, minute: u8, second: u8) -> bool418     fn is_date_valid(year: u16, month: u8, day: u8, hour: u8, minute: u8, second: u8) -> bool {
419         DateTime::new(year, month, day, hour, minute, second).is_ok()
420     }
421 
422     #[test]
feb_leap_year_handling()423     fn feb_leap_year_handling() {
424         assert!(is_date_valid(2000, 2, 29, 0, 0, 0));
425         assert!(!is_date_valid(2001, 2, 29, 0, 0, 0));
426         assert!(!is_date_valid(2100, 2, 29, 0, 0, 0));
427     }
428 
429     #[test]
from_str()430     fn from_str() {
431         let datetime = "2001-01-02T12:13:14Z".parse::<DateTime>().unwrap();
432         assert_eq!(datetime.year(), 2001);
433         assert_eq!(datetime.month(), 1);
434         assert_eq!(datetime.day(), 2);
435         assert_eq!(datetime.hour(), 12);
436         assert_eq!(datetime.minutes(), 13);
437         assert_eq!(datetime.seconds(), 14);
438     }
439 
440     #[cfg(feature = "alloc")]
441     #[test]
display()442     fn display() {
443         use alloc::string::ToString;
444         let datetime = DateTime::new(2001, 01, 02, 12, 13, 14).unwrap();
445         assert_eq!(&datetime.to_string(), "2001-01-02T12:13:14Z");
446     }
447 }
448