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