uefi: add time03 feature and integrate time types with struct Time

This commit is contained in:
Philipp Schuster
2026-03-15 15:45:35 +01:00
parent 2a5f0d3295
commit 8956c8b986
9 changed files with 189 additions and 6 deletions
Generated
+40
View File
@@ -200,6 +200,15 @@ dependencies = [
"typenum",
]
[[package]]
name = "deranged"
version = "0.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c"
dependencies = [
"powerfmt",
]
[[package]]
name = "digest"
version = "0.10.7"
@@ -486,6 +495,12 @@ dependencies = [
"libc",
]
[[package]]
name = "num-conv"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
[[package]]
name = "once_cell"
version = "1.21.4"
@@ -511,6 +526,12 @@ version = "2.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
[[package]]
name = "powerfmt"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
[[package]]
name = "prettyplease"
version = "0.2.37"
@@ -862,6 +883,24 @@ dependencies = [
"syn",
]
[[package]]
name = "time"
version = "0.3.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9e442fc33d7fdb45aa9bfeb312c095964abdf596f7567261062b2a7107aaabd"
dependencies = [
"deranged",
"num-conv",
"powerfmt",
"time-core",
]
[[package]]
name = "time-core"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b36ee98fd31ec7426d599183e8fe26932a8dc1fb76ddb6214d05493377d34ca"
[[package]]
name = "toml"
version = "1.1.2+spec-1.1.0"
@@ -940,6 +979,7 @@ dependencies = [
"log",
"ptr_meta",
"qemu-exit",
"time",
"ucs2",
"uefi-macros",
"uefi-raw",
+1
View File
@@ -24,6 +24,7 @@ bitflags = "2.0.0"
log = { version = "0.4.5", default-features = false }
ptr_meta = { version = "0.3.0", default-features = false, features = ["derive"] }
qemu-exit = { version = "4.0.0" }
time = { version = "0.3", default-features = false }
uguid = "2.2.1"
[patch.crates-io]
+3
View File
@@ -34,6 +34,9 @@
- Added `Serial::read_exact()` and `Serial::write_exact()`
- `CStr16::from_bytes_with_nul()`: This is especially useful to transform the
retrieved value from a UEFI variable into a UCS2 (CStr16) string.
- Integration of `Time` with `time` crate
- `TryFrom`: `time::PrimitiveDateTime <--> Time` (without timezone)
- `TryFrom`: `time::OffsetDateTime <--> Time` (with timezone)
## Changed
- export all `text::{input, output}::*` types
+3
View File
@@ -20,6 +20,8 @@ rust-version.workspace = true
# KEEP this feature list in sync with doc in uefi/lib.rs!
default = [ ]
alloc = []
# Integration with time crate `>= v0.3`
time03 = ["dep:time"]
# Generic gate to code that uses unstable features of Rust, needing a nightly
# toolchain.
@@ -39,6 +41,7 @@ log-debugcon = []
bitflags.workspace = true
log.workspace = true
ptr_meta.workspace = true
time = { workspace = true, optional = true }
qemu-exit = { workspace = true, optional = true }
uguid.workspace = true
cfg-if = "1.0.0"
+3
View File
@@ -148,6 +148,9 @@
//! - `log-debugcon`: Whether the logger set up by `logger` should also log
//! to the debugcon device (available in QEMU or Cloud Hypervisor on x86).
//! - `panic_handler`: Add a default panic handler that logs to `stdout`.
//! - `time03`: Integration of [`runtime::Time`] with the `time` crate
//! (version 0.3 and possible above). Specifically, it integrates the time
//! struct with `PrimitiveDateTime` and `OffsetDateTime` via `TryFrom`.
//! - `unstable`: Enable functionality that depends on [unstable features] in
//! the Rust compiler (nightly version).
//! - `qemu`: Enable some code paths to adapt their execution when executed
@@ -40,6 +40,9 @@ pub(super) enum ConversionErrorInner {
///
/// [`Time::UNSPECIFIED_TIMEZONE`]: super::Time::UNSPECIFIED_TIMEZONE
UnspecifiedTimezone,
/// Errors raised in the [`time`] crate.
#[cfg(feature = "time03")]
TimeCrateError(time::Error),
}
impl Display for ConversionErrorInner {
@@ -48,6 +51,8 @@ impl Display for ConversionErrorInner {
Self::InvalidComponent => write!(f, "Invalid component"),
Self::InvalidUefiTime(e) => write!(f, "Invalid UEFI time: {e}"),
Self::UnspecifiedTimezone => write!(f, "Unspecified timezone"),
#[cfg(feature = "time03")]
Self::TimeCrateError(e) => write!(f, "Time crate error: {}", e),
}
}
}
@@ -58,6 +63,8 @@ impl Error for ConversionErrorInner {
Self::InvalidComponent => None,
Self::InvalidUefiTime(e) => Some(e),
Self::UnspecifiedTimezone => None,
#[cfg(feature = "time03")]
Self::TimeCrateError(e) => Some(e),
}
}
}
@@ -0,0 +1,109 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
//! Integration of the UEFI [`Time`] type with the [`time`] crate.
use super::Time;
use super::integration_common::{ConversionError, ConversionErrorInner};
use crate::runtime::TimeParams;
use time::{OffsetDateTime, PrimitiveDateTime, UtcOffset};
impl TryFrom<Time> for PrimitiveDateTime {
type Error = ConversionError;
fn try_from(value: Time) -> Result<Self, Self::Error> {
if let Err(e) = value.is_valid() {
return Err(ConversionError(ConversionErrorInner::InvalidUefiTime(e)));
}
// Emulated try {} block to keep the `?` error propagation scoped
// (we have a different error type here)
let datetime: Result<Self, time::error::ComponentRange> = (|| {
let month = time::Month::try_from(value.0.month)?;
let date = time::Date::from_calendar_date(value.0.year as i32, month, value.0.day)?;
let time = time::Time::from_hms_nano(
value.0.hour,
value.0.minute,
value.0.second,
value.0.nanosecond,
)?;
Ok(Self::new(date, time))
})();
datetime
.map_err(time::Error::ComponentRange)
.map_err(|e| ConversionError(ConversionErrorInner::TimeCrateError(e)))
}
}
impl TryFrom<Time> for OffsetDateTime {
type Error = ConversionError;
fn try_from(value: Time) -> Result<Self, Self::Error> {
if let Err(e) = value.is_valid() {
return Err(ConversionError(ConversionErrorInner::InvalidUefiTime(e)));
}
let primitive_date_time: PrimitiveDateTime = value.try_into()?;
if value.0.time_zone == Time::UNSPECIFIED_TIMEZONE {
return Err(ConversionError(ConversionErrorInner::UnspecifiedTimezone));
}
let h = (value.0.time_zone / 60) as i8;
let m = (value.0.time_zone.abs() % 60) as i8;
let offset = UtcOffset::from_hms(h, m, 0)
.map_err(time::Error::ComponentRange)
.map_err(|e| ConversionError(ConversionErrorInner::TimeCrateError(e)))?;
Ok(primitive_date_time.assume_offset(offset))
}
}
impl TryFrom<PrimitiveDateTime> for Time {
type Error = ConversionError;
fn try_from(value: PrimitiveDateTime) -> Result<Self, Self::Error> {
let params = TimeParams {
year: u16::try_from(value.year())
.map_err(|_e| ConversionError(ConversionErrorInner::InvalidComponent))?,
month: u8::from(value.month()),
day: value.day(),
hour: value.hour(),
minute: value.minute(),
second: value.second(),
nanosecond: value.nanosecond(),
time_zone: None,
daylight: Default::default(),
};
Self::new(params).map_err(|e| ConversionError(ConversionErrorInner::InvalidUefiTime(e)))
}
}
impl TryFrom<OffsetDateTime> for Time {
type Error = ConversionError;
fn try_from(value: OffsetDateTime) -> Result<Self, Self::Error> {
let timezone_offset_minutes = value.offset().whole_seconds() / 60;
if value.offset().whole_seconds() % 60 != 0 {
return Err(ConversionError(ConversionErrorInner::InvalidComponent));
}
let params = TimeParams {
year: u16::try_from(value.year())
.map_err(|_e| ConversionError(ConversionErrorInner::InvalidComponent))?,
month: u8::from(value.month()),
day: value.day(),
hour: value.hour(),
minute: value.minute(),
second: value.second(),
nanosecond: value.nanosecond(),
time_zone: Some(
i16::try_from(timezone_offset_minutes)
.map_err(|_e| ConversionError(ConversionErrorInner::InvalidComponent))?,
),
daylight: Default::default(),
};
Self::new(params).map_err(|e| ConversionError(ConversionErrorInner::InvalidUefiTime(e)))
}
}
+16 -1
View File
@@ -7,10 +7,25 @@ use core::fmt;
use core::fmt::{Debug, Display, Formatter};
use uefi_raw::time::Daylight;
#[allow(unused)]
#[cfg(feature = "time03")]
mod integration_common;
#[cfg(feature = "time03")]
mod integration_time_crate;
/// Date and time representation.
///
/// # Integration with Time-related Crates of the Ecosystem
///
/// Handling time is complicated. Therefore, we do not reinvent the wheel and
/// forward all complexity of time to well-known crates of the ecosystem. For
/// that, we provide integrations with various crates:
///
/// ## Integration with [`time`][time crate] crate
///
/// - [`TryFrom`]: `PrimitiveDateTime` <--> [`Time`] (without timezone)
/// - [`TryFrom`]: `OffsetDateTime` <--> [`Time`] (with timezone)
///
/// [time crate]: https://crates.io/crates/time
#[derive(Copy, Clone, Eq, PartialEq)]
#[repr(transparent)]
pub struct Time(uefi_raw::time::Time);
+7 -5
View File
@@ -56,6 +56,7 @@ pub enum Feature {
Unstable,
PanicHandler,
Qemu,
Time03,
// `uefi-test-runner` features.
DebugSupport,
@@ -76,6 +77,7 @@ impl Feature {
Self::Unstable => "unstable",
Self::PanicHandler => "panic_handler",
Self::Qemu => "qemu",
Self::Time03 => "time03",
Self::DebugSupport => "uefi-test-runner/debug_support",
Self::MultiProcessor => "uefi-test-runner/multi_processor",
@@ -117,7 +119,7 @@ impl Feature {
/// - `include_unstable` - add all functionality behind the `unstable` feature
/// - `runtime_features` - add all functionality that effect the runtime of Rust
pub fn more_code(include_unstable: bool, runtime_features: bool) -> Vec<Self> {
let mut base_features = vec![Self::Alloc, Self::LogDebugcon, Self::Logger];
let mut base_features = vec![Self::Alloc, Self::LogDebugcon, Self::Logger, Self::Time03];
if include_unstable {
base_features.extend([Self::Unstable])
}
@@ -387,19 +389,19 @@ mod tests {
fn test_comma_separated_features() {
assert_eq!(
Feature::comma_separated_string(&Feature::more_code(false, false)),
"alloc,log-debugcon,logger"
"alloc,log-debugcon,logger,time03"
);
assert_eq!(
Feature::comma_separated_string(&Feature::more_code(false, true)),
"alloc,log-debugcon,logger,global_allocator"
"alloc,log-debugcon,logger,time03,global_allocator"
);
assert_eq!(
Feature::comma_separated_string(&Feature::more_code(true, false)),
"alloc,log-debugcon,logger,unstable"
"alloc,log-debugcon,logger,time03,unstable"
);
assert_eq!(
Feature::comma_separated_string(&Feature::more_code(true, true)),
"alloc,log-debugcon,logger,unstable,global_allocator"
"alloc,log-debugcon,logger,time03,unstable,global_allocator"
);
}