diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 12:41:35 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 12:41:35 +0000 |
commit | 7e5d7eea9c580ef4b41a765bde624af431942b96 (patch) | |
tree | 2c0d9ca12878fc4525650aa4e54d77a81a07cc09 /vendor/der/src/datetime.rs | |
parent | Adding debian version 1.70.0+dfsg1-9. (diff) | |
download | rustc-7e5d7eea9c580ef4b41a765bde624af431942b96.tar.xz rustc-7e5d7eea9c580ef4b41a765bde624af431942b96.zip |
Merging upstream version 1.70.0+dfsg2.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'vendor/der/src/datetime.rs')
-rw-r--r-- | vendor/der/src/datetime.rs | 423 |
1 files changed, 423 insertions, 0 deletions
diff --git a/vendor/der/src/datetime.rs b/vendor/der/src/datetime.rs new file mode 100644 index 000000000..2b4c504bf --- /dev/null +++ b/vendor/der/src/datetime.rs @@ -0,0 +1,423 @@ +//! Date and time functionality shared between various ASN.1 types +//! (e.g. `GeneralizedTime`, `UTCTime`) + +// Adapted from the `humantime` crate. +// Copyright (c) 2016 The humantime Developers +// Released under the MIT OR Apache 2.0 licenses + +use crate::{Error, ErrorKind, Result, Tag, Writer}; +use core::{fmt, str::FromStr, time::Duration}; + +#[cfg(feature = "std")] +use std::time::{SystemTime, UNIX_EPOCH}; + +#[cfg(feature = "time")] +use time::PrimitiveDateTime; + +/// Minimum year allowed in [`DateTime`] values. +const MIN_YEAR: u16 = 1970; + +/// Maximum duration since `UNIX_EPOCH` which can be represented as a +/// [`DateTime`] (non-inclusive). +/// +/// This corresponds to: 9999-12-31T23:59:59Z +const MAX_UNIX_DURATION: Duration = Duration::from_secs(253_402_300_799); + +/// Date-and-time type shared by multiple ASN.1 types +/// (e.g. `GeneralizedTime`, `UTCTime`). +/// +/// Following conventions from RFC 5280, this type is always Z-normalized +/// (i.e. represents a UTC time). However, it isn't named "UTC time" in order +/// to prevent confusion with ASN.1 `UTCTime`. +#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] +pub struct DateTime { + /// Full year (e.g. 2000). + /// + /// Must be >=1970 to permit positive conversions to Unix time. + year: u16, + + /// Month (1-12) + month: u8, + + /// Day of the month (1-31) + day: u8, + + /// Hour (0-23) + hour: u8, + + /// Minutes (0-59) + minutes: u8, + + /// Seconds (0-59) + seconds: u8, + + /// [`Duration`] since the Unix epoch. + unix_duration: Duration, +} + +impl DateTime { + /// Create a new [`DateTime`] from the given UTC time components. + // TODO(tarcieri): checked arithmetic + #[allow(clippy::integer_arithmetic)] + pub fn new(year: u16, month: u8, day: u8, hour: u8, minutes: u8, seconds: u8) -> Result<Self> { + // Basic validation of the components. + if year < MIN_YEAR + || !(1..=12).contains(&month) + || !(1..=31).contains(&day) + || !(0..=23).contains(&hour) + || !(0..=59).contains(&minutes) + || !(0..=59).contains(&seconds) + { + return Err(ErrorKind::DateTime.into()); + } + + let leap_years = + ((year - 1) - 1968) / 4 - ((year - 1) - 1900) / 100 + ((year - 1) - 1600) / 400; + + let is_leap_year = year % 4 == 0 && (year % 100 != 0 || year % 400 == 0); + + let (mut ydays, mdays): (u16, u8) = match month { + 1 => (0, 31), + 2 if is_leap_year => (31, 29), + 2 => (31, 28), + 3 => (59, 31), + 4 => (90, 30), + 5 => (120, 31), + 6 => (151, 30), + 7 => (181, 31), + 8 => (212, 31), + 9 => (243, 30), + 10 => (273, 31), + 11 => (304, 30), + 12 => (334, 31), + _ => return Err(ErrorKind::DateTime.into()), + }; + + if day > mdays || day == 0 { + return Err(ErrorKind::DateTime.into()); + } + + ydays += u16::from(day) - 1; + + if is_leap_year && month > 2 { + ydays += 1; + } + + let days = u64::from(year - 1970) * 365 + u64::from(leap_years) + u64::from(ydays); + let time = u64::from(seconds) + (u64::from(minutes) * 60) + (u64::from(hour) * 3600); + let unix_duration = Duration::from_secs(time + days * 86400); + + if unix_duration > MAX_UNIX_DURATION { + return Err(ErrorKind::DateTime.into()); + } + + Ok(Self { + year, + month, + day, + hour, + minutes, + seconds, + unix_duration, + }) + } + + /// Compute a [`DateTime`] from the given [`Duration`] since the `UNIX_EPOCH`. + /// + /// Returns `None` if the value is outside the supported date range. + // TODO(tarcieri): checked arithmetic + #[allow(clippy::integer_arithmetic)] + pub fn from_unix_duration(unix_duration: Duration) -> Result<Self> { + if unix_duration > MAX_UNIX_DURATION { + return Err(ErrorKind::DateTime.into()); + } + + let secs_since_epoch = unix_duration.as_secs(); + + /// 2000-03-01 (mod 400 year, immediately after Feb 29) + const LEAPOCH: i64 = 11017; + const DAYS_PER_400Y: i64 = 365 * 400 + 97; + const DAYS_PER_100Y: i64 = 365 * 100 + 24; + const DAYS_PER_4Y: i64 = 365 * 4 + 1; + + let days = i64::try_from(secs_since_epoch / 86400)? - LEAPOCH; + let secs_of_day = secs_since_epoch % 86400; + + let mut qc_cycles = days / DAYS_PER_400Y; + let mut remdays = days % DAYS_PER_400Y; + + if remdays < 0 { + remdays += DAYS_PER_400Y; + qc_cycles -= 1; + } + + let mut c_cycles = remdays / DAYS_PER_100Y; + if c_cycles == 4 { + c_cycles -= 1; + } + remdays -= c_cycles * DAYS_PER_100Y; + + let mut q_cycles = remdays / DAYS_PER_4Y; + if q_cycles == 25 { + q_cycles -= 1; + } + remdays -= q_cycles * DAYS_PER_4Y; + + let mut remyears = remdays / 365; + if remyears == 4 { + remyears -= 1; + } + remdays -= remyears * 365; + + let mut year = 2000 + remyears + 4 * q_cycles + 100 * c_cycles + 400 * qc_cycles; + + let months = [31, 30, 31, 30, 31, 31, 30, 31, 30, 31, 31, 29]; + let mut mon = 0; + for mon_len in months.iter() { + mon += 1; + if remdays < *mon_len { + break; + } + remdays -= *mon_len; + } + let mday = remdays + 1; + let mon = if mon + 2 > 12 { + year += 1; + mon - 10 + } else { + mon + 2 + }; + + let second = secs_of_day % 60; + let mins_of_day = secs_of_day / 60; + let minute = mins_of_day % 60; + let hour = mins_of_day / 60; + + Self::new( + year.try_into()?, + mon, + mday.try_into()?, + hour.try_into()?, + minute.try_into()?, + second.try_into()?, + ) + } + + /// Get the year. + pub fn year(&self) -> u16 { + self.year + } + + /// Get the month. + pub fn month(&self) -> u8 { + self.month + } + + /// Get the day. + pub fn day(&self) -> u8 { + self.day + } + + /// Get the hour. + pub fn hour(&self) -> u8 { + self.hour + } + + /// Get the minutes. + pub fn minutes(&self) -> u8 { + self.minutes + } + + /// Get the seconds. + pub fn seconds(&self) -> u8 { + self.seconds + } + + /// Compute [`Duration`] since `UNIX_EPOCH` from the given calendar date. + pub fn unix_duration(&self) -> Duration { + self.unix_duration + } + + /// Instantiate from [`SystemTime`]. + #[cfg(feature = "std")] + #[cfg_attr(docsrs, doc(cfg(feature = "std")))] + pub fn from_system_time(time: SystemTime) -> Result<Self> { + time.duration_since(UNIX_EPOCH) + .map_err(|_| ErrorKind::DateTime.into()) + .and_then(Self::from_unix_duration) + } + + /// Convert to [`SystemTime`]. + #[cfg(feature = "std")] + #[cfg_attr(docsrs, doc(cfg(feature = "std")))] + pub fn to_system_time(&self) -> SystemTime { + UNIX_EPOCH + self.unix_duration() + } +} + +impl FromStr for DateTime { + type Err = Error; + + // TODO(tarcieri): checked arithmetic + #[allow(clippy::integer_arithmetic)] + fn from_str(s: &str) -> Result<Self> { + match *s.as_bytes() { + [year1, year2, year3, year4, b'-', month1, month2, b'-', day1, day2, b'T', hour1, hour2, b':', min1, min2, b':', sec1, sec2, b'Z'] => + { + let tag = Tag::GeneralizedTime; + let year = + u16::from(decode_decimal(tag, year1, year2).map_err(|_| ErrorKind::DateTime)?) + * 100 + + u16::from( + decode_decimal(tag, year3, year4).map_err(|_| ErrorKind::DateTime)?, + ); + let month = decode_decimal(tag, month1, month2).map_err(|_| ErrorKind::DateTime)?; + let day = decode_decimal(tag, day1, day2).map_err(|_| ErrorKind::DateTime)?; + let hour = decode_decimal(tag, hour1, hour2).map_err(|_| ErrorKind::DateTime)?; + let minutes = decode_decimal(tag, min1, min2).map_err(|_| ErrorKind::DateTime)?; + let seconds = decode_decimal(tag, sec1, sec2).map_err(|_| ErrorKind::DateTime)?; + Self::new(year, month, day, hour, minutes, seconds) + } + _ => Err(ErrorKind::DateTime.into()), + } + } +} + +impl fmt::Display for DateTime { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "{:02}-{:02}-{:02}T{:02}:{:02}:{:02}Z", + self.year, self.month, self.day, self.hour, self.minutes, self.seconds + ) + } +} + +#[cfg(feature = "std")] +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] +impl From<DateTime> for SystemTime { + fn from(time: DateTime) -> SystemTime { + time.to_system_time() + } +} + +#[cfg(feature = "std")] +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] +impl From<&DateTime> for SystemTime { + fn from(time: &DateTime) -> SystemTime { + time.to_system_time() + } +} + +#[cfg(feature = "std")] +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] +impl TryFrom<SystemTime> for DateTime { + type Error = Error; + + fn try_from(time: SystemTime) -> Result<DateTime> { + DateTime::from_system_time(time) + } +} + +#[cfg(feature = "std")] +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] +impl TryFrom<&SystemTime> for DateTime { + type Error = Error; + + fn try_from(time: &SystemTime) -> Result<DateTime> { + DateTime::from_system_time(*time) + } +} + +#[cfg(feature = "time")] +#[cfg_attr(docsrs, doc(cfg(feature = "time")))] +impl TryFrom<DateTime> for PrimitiveDateTime { + type Error = Error; + + fn try_from(time: DateTime) -> Result<PrimitiveDateTime> { + let month = (time.month() as u8).try_into()?; + let date = time::Date::from_calendar_date(i32::from(time.year()), month, time.day())?; + let time = time::Time::from_hms(time.hour(), time.minutes(), time.seconds())?; + + Ok(PrimitiveDateTime::new(date, time)) + } +} + +#[cfg(feature = "time")] +#[cfg_attr(docsrs, doc(cfg(feature = "time")))] +impl TryFrom<PrimitiveDateTime> for DateTime { + type Error = Error; + + fn try_from(time: PrimitiveDateTime) -> Result<DateTime> { + DateTime::new( + time.year().try_into().map_err(|_| ErrorKind::DateTime)?, + time.month().into(), + time.day(), + time.hour(), + time.minute(), + time.second(), + ) + } +} + +/// Decode 2-digit decimal value +// TODO(tarcieri): checked arithmetic +#[allow(clippy::integer_arithmetic)] +pub(crate) fn decode_decimal(tag: Tag, hi: u8, lo: u8) -> Result<u8> { + if (b'0'..=b'9').contains(&hi) && (b'0'..=b'9').contains(&lo) { + Ok((hi - b'0') * 10 + (lo - b'0')) + } else { + Err(tag.value_error()) + } +} + +/// Encode 2-digit decimal value +pub(crate) fn encode_decimal<W>(writer: &mut W, tag: Tag, value: u8) -> Result<()> +where + W: Writer + ?Sized, +{ + let hi_val = value / 10; + + if hi_val >= 10 { + return Err(tag.value_error()); + } + + writer.write_byte(b'0'.checked_add(hi_val).ok_or(ErrorKind::Overflow)?)?; + writer.write_byte(b'0'.checked_add(value % 10).ok_or(ErrorKind::Overflow)?) +} + +#[cfg(test)] +mod tests { + use super::DateTime; + + /// Ensure a day is OK + fn is_date_valid(year: u16, month: u8, day: u8, hour: u8, minute: u8, second: u8) -> bool { + DateTime::new(year, month, day, hour, minute, second).is_ok() + } + + #[test] + fn feb_leap_year_handling() { + assert!(is_date_valid(2000, 2, 29, 0, 0, 0)); + assert!(!is_date_valid(2001, 2, 29, 0, 0, 0)); + assert!(!is_date_valid(2100, 2, 29, 0, 0, 0)); + } + + #[test] + fn from_str() { + let datetime = "2001-01-02T12:13:14Z".parse::<DateTime>().unwrap(); + assert_eq!(datetime.year(), 2001); + assert_eq!(datetime.month(), 1); + assert_eq!(datetime.day(), 2); + assert_eq!(datetime.hour(), 12); + assert_eq!(datetime.minutes(), 13); + assert_eq!(datetime.seconds(), 14); + } + + #[cfg(feature = "alloc")] + #[test] + fn display() { + use alloc::string::ToString; + let datetime = DateTime::new(2001, 01, 02, 12, 13, 14).unwrap(); + assert_eq!(&datetime.to_string(), "2001-01-02T12:13:14Z"); + } +} |