From dbbf96e06df92673baf78f99c9c9b5056fff9fea Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Sun, 22 Feb 2026 13:55:46 +0100 Subject: [PATCH] uefi-raw: add time integration into module This helps to better keep an overview. --- uefi-raw/src/time.rs | 542 +++++++++++++++++++++---------------------- 1 file changed, 271 insertions(+), 271 deletions(-) diff --git a/uefi-raw/src/time.rs b/uefi-raw/src/time.rs index fd08c673..88011e12 100644 --- a/uefi-raw/src/time.rs +++ b/uefi-raw/src/time.rs @@ -2,69 +2,11 @@ //! Date and time types. +#[cfg(feature = "time")] +pub use time_crate_integration::*; + use bitflags::bitflags; -use core::error; use core::fmt::{self, Display, Formatter}; -#[cfg(feature = "time")] -use time::{OffsetDateTime, UtcOffset}; - -/// The time is invalid as it doesn't fulfill the requirements of -/// [`Time::is_valid`]. -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -#[cfg(feature = "time")] -pub struct InvalidTimeError(Time); - -#[cfg(feature = "time")] -impl Display for InvalidTimeError { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - write!(f, "Invalid EFI time (not valid): {}", self.0) - } -} - -#[cfg(feature = "time")] -impl error::Error for InvalidTimeError {} - -/// Errors that can happen when converting a [`Time`] to a [`OffsetDateTime`]. -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -#[cfg(feature = "time")] -pub enum ToOffsetDateTimeError { - /// See [`InvalidTimeError`]. - Invalid(InvalidTimeError), - /// The timezone is a valid EFI value but [`Time::UNSPECIFIED_TIMEZONE`] - /// means `local` which cannot be determined in a `no_std` context. - UnspecifiedTimezone, - /// The corresponding component has an invalid range (e.g. February 30th). - ComponentRange(time::error::ComponentRange), -} - -#[cfg(feature = "time")] -impl Display for ToOffsetDateTimeError { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - match self { - Self::Invalid(e) => { - write!(f, "Cannot convert to OffsetDateTime: {}", e) - } - Self::UnspecifiedTimezone => write!( - f, - "Cannot convert to OffsetDateTime: the timezone (local) can't be determined in a no_std context" - ), - Self::ComponentRange(e) => { - write!(f, "Cannot convert to OffsetDateTime: {}", e) - } - } - } -} - -#[cfg(feature = "time")] -impl error::Error for ToOffsetDateTimeError { - fn source(&self) -> Option<&(dyn error::Error + 'static)> { - match self { - Self::Invalid(e) => Some(e), - Self::UnspecifiedTimezone => None, - Self::ComponentRange(e) => Some(e), - } - } -} /// Date and time representation. /// @@ -120,150 +62,6 @@ impl Time { /// Indicates the time should be interpreted as local time. pub const UNSPECIFIED_TIMEZONE: i16 = 0x07ff; - #[cfg(feature = "time")] - fn _to_offset_date_time( - &self, - local_timezone: Option, - ) -> Result { - if !self.is_valid() { - return Err(ToOffsetDateTimeError::Invalid(InvalidTimeError(*self))); - } - - // Special handling for `UNSPECIFIED_TIMEZONE` (local): - // - specific time zone => all good - // - `UNSPECIFIED_TIMEZONE` and no default time zone => return Err - // - else: use the default time zone - let offset_hms: (i8, i8, i8) = { - if self.time_zone == Self::UNSPECIFIED_TIMEZONE { - if let Some(local_timezone) = local_timezone { - local_timezone.as_hms() - } else { - return Err(ToOffsetDateTimeError::UnspecifiedTimezone); - } - } else { - let h = self.time_zone / 60; - let m = self.time_zone % 60; - - (h as i8, m as i8, 0) - } - }; - - // Emulated try {} block to keep the `?` error propagation scoped - // (we have a different error type here) - let datetime: Result = (|| { - let month = time::Month::try_from(self.month)?; - let date = time::Date::from_calendar_date(self.year as i32, month, self.day)?; - let time = - time::Time::from_hms_nano(self.hour, self.minute, self.second, self.nanosecond)?; - let offset = UtcOffset::from_hms(offset_hms.0, offset_hms.1, offset_hms.2)?; - Ok(OffsetDateTime::new_in_offset(date, time, offset)) - })(); - - datetime.map_err(ToOffsetDateTimeError::ComponentRange) - } - - /// Converts this [`Time`] to a [`OffsetDateTime`] using the UTC offset - /// stored in `self.time_zone`. - /// - /// # Returns - /// - /// - `Ok(OffsetDateTime)` if the time is valid and the timezone is fully - /// specified (not `UNSPECIFIED_TIMEZONE`). - /// - `Err(ToOffsetDateTimeError::Invalid(_))` if any of the time fields are - /// out of valid EFI ranges (e.g., year < 1900, month > 12). - /// - `Err(ToOffsetDateTimeError::UnspecifiedTimezone)` if the timezone is - /// `UNSPECIFIED_TIMEZONE`, since no default local timezone was provided. - /// - `Err(ToOffsetDateTimeError::ComponentRange(_))` if the combination of - /// fields results in an invalid calendar date (e.g., February 30th). - /// - /// # Examples - /// - /// ``` - /// # use uefi_raw::time::{Time, ToOffsetDateTimeError}; - /// # use time::OffsetDateTime; - /// let t = Time { - /// year: 2024, - /// month: 3, - /// day: 15, - /// hour: 12, - /// minute: 0, - /// second: 0, - /// pad1: 0, - /// nanosecond: 0, - /// time_zone: 0, // UTC - /// daylight: Default::default(), - /// pad2: 0, - /// }; - /// - /// let odt: OffsetDateTime = t.to_offset_date_time().unwrap(); - /// assert_eq!(odt.offset().whole_seconds(), 0); - /// ``` - #[cfg(feature = "time")] - pub fn to_offset_date_time(&self) -> Result { - self._to_offset_date_time(None) - } - - /// Converts this [`Time`] to a [`OffsetDateTime`] using a provided default - /// timezone when `self.time_zone` is [`Self::UNSPECIFIED_TIMEZONE`]. - /// - /// If the stored `time_zone` field is `UNSPECIFIED_TIMEZONE`, this method - /// uses the provided `local` [`UtcOffset`] as the offset. Otherwise, the - /// stored `time_zone` in minutes is used. - /// - /// # Arguments - /// - /// - `local`: The default [`UtcOffset`] to use if `time_zone` is - /// [`Self::UNSPECIFIED_TIMEZONE`]. - /// - /// # Returns - /// - /// - `Ok(OffsetDateTime)` if the time is valid and the offset can be - /// applied successfully. - /// - `Err(ToOffsetDateTimeError::Invalid(_))` if any of the time fields are - /// out of valid EFI ranges. - /// - `Err(ToOffsetDateTimeError::ComponentRange(_))` if the combination of - /// fields results in an invalid calendar date. - /// - /// # Panics - /// - /// This function does **not panic** under normal usage. It is impossible - /// for `UnspecifiedTimezone` to be returned because the caller provides - /// a valid default offset. - /// - /// # Examples - /// - /// ``` - /// # use uefi_raw::time::{Time, ToOffsetDateTimeError}; - /// # use time::{OffsetDateTime, UtcOffset}; - /// let mut t = Time { - /// year: 2024, - /// month: 3, - /// day: 15, - /// hour: 12, - /// minute: 0, - /// second: 0, - /// pad1: 0, - /// nanosecond: 0, - /// time_zone: Time::UNSPECIFIED_TIMEZONE, - /// daylight: Default::default(), - /// pad2: 0, - /// }; - /// - /// let default_offset = UtcOffset::from_hms(1, 30, 0).unwrap(); - /// let odt: OffsetDateTime = t.to_offset_date_time_with_default_timezone(default_offset).unwrap(); - /// assert_eq!(odt.offset(), default_offset); - /// ``` - #[cfg(feature = "time")] - pub fn to_offset_date_time_with_default_timezone( - &self, - local: UtcOffset, - ) -> Result { - match self._to_offset_date_time(Some(local)) { - Err(ToOffsetDateTimeError::UnspecifiedTimezone) => unreachable!(), - other => other, - } - } - /// Create an invalid `Time` with all fields set to zero. #[must_use] pub const fn invalid() -> Self { @@ -360,79 +158,281 @@ bitflags! { } #[cfg(feature = "time")] -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub enum FromOffsetDateTimeError { - /// The year is not representable as `u16`. - InvalidYear(i32), - /// Time zone offsets with seconds are not representable. - OffsetWithSeconds(i8), - InvalidTime(InvalidTimeError), -} +mod time_crate_integration { + use super::*; + use core::error; + use time::{OffsetDateTime, UtcOffset}; -#[cfg(feature = "time")] -impl Display for FromOffsetDateTimeError { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - match self { - Self::InvalidYear(y) => { - write!( - f, - "Cannot to convert OffsetDateTime to EFI Time: invalid year {y}" - ) - } - Self::OffsetWithSeconds(s) => { - write!( - f, - "Cannot to convert OffsetDateTime to EFI Time: time zone offset has seconds ({s}) which is unsupported" - ) - } - Self::InvalidTime(t) => { - write!(f, "The time is invalid: {t}") - } + /// The time is invalid as it doesn't fulfill the requirements of + /// [`Time::is_valid`]. + #[derive(Debug, Copy, Clone, PartialEq, Eq)] + pub struct InvalidTimeError(Time); + + impl Display for InvalidTimeError { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "Invalid EFI time (not valid): {}", self.0) } } -} -#[cfg(feature = "time")] -impl error::Error for FromOffsetDateTimeError { - fn source(&self) -> Option<&(dyn error::Error + 'static)> { - match self { - Self::InvalidYear(_) => None, - Self::OffsetWithSeconds(_) => None, - Self::InvalidTime(e) => Some(e), - } + impl error::Error for InvalidTimeError {} + + /// Errors that can happen when converting a [`Time`] to a [`OffsetDateTime`]. + #[derive(Debug, Copy, Clone, PartialEq, Eq)] + pub enum ToOffsetDateTimeError { + /// See [`InvalidTimeError`]. + Invalid(InvalidTimeError), + /// The timezone is a valid EFI value but [`Time::UNSPECIFIED_TIMEZONE`] + /// means `local` which cannot be determined in a `no_std` context. + UnspecifiedTimezone, + /// The corresponding component has an invalid range (e.g. February 30th). + ComponentRange(time::error::ComponentRange), } -} -#[cfg(feature = "time")] -impl TryFrom for Time { - type Error = FromOffsetDateTimeError; - - fn try_from(value: OffsetDateTime) -> Result { - let this = Self { - year: u16::try_from(value.date().year()) - .map_err(|_| Self::Error::InvalidYear(value.date().year()))?, - // No checks needed: underlying type has repr `u8` - month: value.date().month() as u8, - day: value.date().day(), - hour: value.time().hour(), - minute: value.time().minute(), - second: value.time().second(), - pad1: 0, - nanosecond: value.time().nanosecond(), - time_zone: { - let (h, m, s) = value.offset().as_hms(); - if s != 0 { - return Err(Self::Error::OffsetWithSeconds(s)); + impl Display for ToOffsetDateTimeError { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + Self::Invalid(e) => { + write!(f, "Cannot convert to OffsetDateTime: {}", e) } - (h as i16 * 60) + (m as i16) - }, - daylight: Daylight::NONE, - pad2: 0, - }; - if this.is_valid() { - Ok(this) - } else { - Err(Self::Error::InvalidTime(InvalidTimeError(this))) + Self::UnspecifiedTimezone => write!( + f, + "Cannot convert to OffsetDateTime: the timezone (local) can't be determined in a no_std context" + ), + Self::ComponentRange(e) => { + write!(f, "Cannot convert to OffsetDateTime: {}", e) + } + } + } + } + + impl error::Error for ToOffsetDateTimeError { + fn source(&self) -> Option<&(dyn error::Error + 'static)> { + match self { + Self::Invalid(e) => Some(e), + Self::UnspecifiedTimezone => None, + Self::ComponentRange(e) => Some(e), + } + } + } + + impl Time { + fn _to_offset_date_time( + &self, + local_timezone: Option, + ) -> Result { + if !self.is_valid() { + return Err(ToOffsetDateTimeError::Invalid(InvalidTimeError(*self))); + } + + // Special handling for `UNSPECIFIED_TIMEZONE` (local): + // - specific time zone => all good + // - `UNSPECIFIED_TIMEZONE` and no default time zone => return Err + // - else: use the default time zone + let offset_hms: (i8, i8, i8) = { + if self.time_zone == Self::UNSPECIFIED_TIMEZONE { + if let Some(local_timezone) = local_timezone { + local_timezone.as_hms() + } else { + return Err(ToOffsetDateTimeError::UnspecifiedTimezone); + } + } else { + let h = self.time_zone / 60; + let m = self.time_zone % 60; + + (h as i8, m as i8, 0) + } + }; + + // Emulated try {} block to keep the `?` error propagation scoped + // (we have a different error type here) + let datetime: Result = (|| { + let month = time::Month::try_from(self.month)?; + let date = time::Date::from_calendar_date(self.year as i32, month, self.day)?; + let time = time::Time::from_hms_nano( + self.hour, + self.minute, + self.second, + self.nanosecond, + )?; + let offset = UtcOffset::from_hms(offset_hms.0, offset_hms.1, offset_hms.2)?; + Ok(OffsetDateTime::new_in_offset(date, time, offset)) + })(); + + datetime.map_err(ToOffsetDateTimeError::ComponentRange) + } + + /// Converts this [`Time`] to a [`OffsetDateTime`] using the UTC offset + /// stored in `self.time_zone`. + /// + /// # Returns + /// + /// - `Ok(OffsetDateTime)` if the time is valid and the timezone is fully + /// specified (not `UNSPECIFIED_TIMEZONE`). + /// - `Err(ToOffsetDateTimeError::Invalid(_))` if any of the time fields are + /// out of valid EFI ranges (e.g., year < 1900, month > 12). + /// - `Err(ToOffsetDateTimeError::UnspecifiedTimezone)` if the timezone is + /// `UNSPECIFIED_TIMEZONE`, since no default local timezone was provided. + /// - `Err(ToOffsetDateTimeError::ComponentRange(_))` if the combination of + /// fields results in an invalid calendar date (e.g., February 30th). + /// + /// # Examples + /// + /// ``` + /// # use uefi_raw::time::{Time, ToOffsetDateTimeError}; + /// # use time::OffsetDateTime; + /// let t = Time { + /// year: 2024, + /// month: 3, + /// day: 15, + /// hour: 12, + /// minute: 0, + /// second: 0, + /// pad1: 0, + /// nanosecond: 0, + /// time_zone: 0, // UTC + /// daylight: Default::default(), + /// pad2: 0, + /// }; + /// + /// let odt: OffsetDateTime = t.to_offset_date_time().unwrap(); + /// assert_eq!(odt.offset().whole_seconds(), 0); + /// ``` + pub fn to_offset_date_time(&self) -> Result { + self._to_offset_date_time(None) + } + + /// Converts this [`Time`] to a [`OffsetDateTime`] using a provided default + /// timezone when `self.time_zone` is [`Self::UNSPECIFIED_TIMEZONE`]. + /// + /// If the stored `time_zone` field is `UNSPECIFIED_TIMEZONE`, this method + /// uses the provided `local` [`UtcOffset`] as the offset. Otherwise, the + /// stored `time_zone` in minutes is used. + /// + /// # Arguments + /// + /// - `local`: The default [`UtcOffset`] to use if `time_zone` is + /// [`Self::UNSPECIFIED_TIMEZONE`]. + /// + /// # Returns + /// + /// - `Ok(OffsetDateTime)` if the time is valid and the offset can be + /// applied successfully. + /// - `Err(ToOffsetDateTimeError::Invalid(_))` if any of the time fields are + /// out of valid EFI ranges. + /// - `Err(ToOffsetDateTimeError::ComponentRange(_))` if the combination of + /// fields results in an invalid calendar date. + /// + /// # Panics + /// + /// This function does **not panic** under normal usage. It is impossible + /// for `UnspecifiedTimezone` to be returned because the caller provides + /// a valid default offset. + /// + /// # Examples + /// + /// ``` + /// # use uefi_raw::time::{Time, ToOffsetDateTimeError}; + /// # use time::{OffsetDateTime, UtcOffset}; + /// let mut t = Time { + /// year: 2024, + /// month: 3, + /// day: 15, + /// hour: 12, + /// minute: 0, + /// second: 0, + /// pad1: 0, + /// nanosecond: 0, + /// time_zone: Time::UNSPECIFIED_TIMEZONE, + /// daylight: Default::default(), + /// pad2: 0, + /// }; + /// + /// let default_offset = UtcOffset::from_hms(1, 30, 0).unwrap(); + /// let odt: OffsetDateTime = t.to_offset_date_time_with_default_timezone(default_offset).unwrap(); + /// assert_eq!(odt.offset(), default_offset); + /// ``` + pub fn to_offset_date_time_with_default_timezone( + &self, + local: UtcOffset, + ) -> Result { + match self._to_offset_date_time(Some(local)) { + Err(ToOffsetDateTimeError::UnspecifiedTimezone) => unreachable!(), + other => other, + } + } + } + + #[derive(Debug, Copy, Clone, PartialEq, Eq)] + pub enum FromOffsetDateTimeError { + /// The year is not representable as `u16`. + InvalidYear(i32), + /// Time zone offsets with seconds are not representable. + OffsetWithSeconds(i8), + InvalidTime(InvalidTimeError), + } + + impl Display for FromOffsetDateTimeError { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + Self::InvalidYear(y) => { + write!( + f, + "Cannot to convert OffsetDateTime to EFI Time: invalid year {y}" + ) + } + Self::OffsetWithSeconds(s) => { + write!( + f, + "Cannot to convert OffsetDateTime to EFI Time: time zone offset has seconds ({s}) which is unsupported" + ) + } + Self::InvalidTime(t) => { + write!(f, "The time is invalid: {t}") + } + } + } + } + + impl error::Error for FromOffsetDateTimeError { + fn source(&self) -> Option<&(dyn error::Error + 'static)> { + match self { + Self::InvalidYear(_) => None, + Self::OffsetWithSeconds(_) => None, + Self::InvalidTime(e) => Some(e), + } + } + } + + impl TryFrom for Time { + type Error = FromOffsetDateTimeError; + + fn try_from(value: OffsetDateTime) -> Result { + let this = Self { + year: u16::try_from(value.date().year()) + .map_err(|_| Self::Error::InvalidYear(value.date().year()))?, + // No checks needed: underlying type has repr `u8` + month: value.date().month() as u8, + day: value.date().day(), + hour: value.time().hour(), + minute: value.time().minute(), + second: value.time().second(), + pad1: 0, + nanosecond: value.time().nanosecond(), + time_zone: { + let (h, m, s) = value.offset().as_hms(); + if s != 0 { + return Err(Self::Error::OffsetWithSeconds(s)); + } + (h as i16 * 60) + (m as i16) + }, + daylight: Daylight::NONE, + pad2: 0, + }; + if this.is_valid() { + Ok(this) + } else { + Err(Self::Error::InvalidTime(InvalidTimeError(this))) + } } } }