std: sys: pal: uefi: tests: Add systemtime tests

Add tests to ensure that extream system times are still representable.

Signed-off-by: Ayush Singh <ayush@beagleboard.org>
This commit is contained in:
Ayush Singh 2025-05-24 17:06:02 +05:30
parent 0ccbe8fc71
commit 35906eaabc
No known key found for this signature in database
GPG key ID: 05CEF5C789E55A74
2 changed files with 97 additions and 25 deletions

View file

@ -2,10 +2,12 @@
//! to a separate project when modifying any related code.
use super::alloc::*;
use super::time::*;
use super::time::system_time_internal::{from_uefi, to_uefi};
use crate::io::{IoSlice, IoSliceMut};
use crate::time::Duration;
const SECS_IN_MINUTE: u64 = 60;
#[test]
fn align() {
// UEFI ABI specifies that allocation alignment minimum is always 8. So this can be
@ -27,21 +29,61 @@ fn align() {
}
#[test]
fn epoch() {
let t = r_efi::system::Time {
year: 1970,
fn systemtime_start() {
let t = r_efi::efi::Time {
year: 1900,
month: 1,
day: 1,
hour: 0,
minute: 0,
second: 0,
nanosecond: 0,
timezone: r_efi::efi::UNSPECIFIED_TIMEZONE,
timezone: -1440,
daylight: 0,
pad1: 0,
pad2: 0,
};
assert_eq!(system_time_internal::uefi_time_to_duration(t), Duration::new(0, 0));
assert_eq!(from_uefi(&t), Duration::new(0, 0));
assert_eq!(t, to_uefi(&from_uefi(&t), -1440, 0).unwrap());
assert!(to_uefi(&from_uefi(&t), 0, 0).is_none());
}
#[test]
fn systemtime_utc_start() {
let t = r_efi::efi::Time {
year: 1900,
month: 1,
day: 1,
hour: 0,
minute: 0,
second: 0,
pad1: 0,
nanosecond: 0,
timezone: 0,
daylight: 0,
pad2: 0,
};
assert_eq!(from_uefi(&t), Duration::new(1440 * SECS_IN_MINUTE, 0));
assert_eq!(t, to_uefi(&from_uefi(&t), 0, 0).unwrap());
assert!(to_uefi(&from_uefi(&t), -1440, 0).is_some());
}
#[test]
fn systemtime_end() {
let t = r_efi::efi::Time {
year: 9999,
month: 12,
day: 31,
hour: 23,
minute: 59,
second: 59,
pad1: 0,
nanosecond: 0,
timezone: 1440,
daylight: 0,
pad2: 0,
};
assert!(to_uefi(&from_uefi(&t), 1440, 0).is_some());
assert!(to_uefi(&from_uefi(&t), 1439, 0).is_none());
}
// UEFI IoSlice and IoSliceMut Tests

View file

@ -6,7 +6,7 @@ pub struct Instant(Duration);
/// When a Timezone is specified, the stored Duration is in UTC. If timezone is unspecified, then
/// the timezone is assumed to be in UTC.
///
/// UEFI SystemTime is stored as Duration from 1900-01-01-00:00:00
/// UEFI SystemTime is stored as Duration from 1900-01-01-00:00:00 with timezone -1440 as anchor
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)]
pub struct SystemTime(Duration);
@ -24,6 +24,20 @@ pub const UNIX_EPOCH: SystemTime = SystemTime::from_uefi(r_efi::efi::Time {
pad2: 0,
});
const MAX_UEFI_TIME: SystemTime = SystemTime::from_uefi(r_efi::efi::Time {
year: 9999,
month: 12,
day: 31,
hour: 23,
minute: 59,
second: 59,
nanosecond: 999_999_999,
timezone: 1440,
daylight: 0,
pad1: 0,
pad2: 0,
});
impl Instant {
pub fn now() -> Instant {
// If we have a timestamp protocol, use it.
@ -56,6 +70,7 @@ impl SystemTime {
Self(system_time_internal::from_uefi(&t))
}
#[expect(dead_code)]
pub(crate) const fn to_uefi(self, timezone: i16, daylight: u8) -> Option<r_efi::efi::Time> {
system_time_internal::to_uefi(&self.0, timezone, daylight)
}
@ -73,14 +88,11 @@ impl SystemTime {
let temp = Self(self.0.checked_add(*other)?);
// Check if can be represented in UEFI
if temp.to_uefi(0, 0).is_some() { Some(temp) } else { None }
if temp <= MAX_UEFI_TIME { Some(temp) } else { None }
}
pub fn checked_sub_duration(&self, other: &Duration) -> Option<SystemTime> {
let temp = Self(self.0.checked_sub(*other)?);
// Check if can be represented in UEFI
if temp.to_uefi(0, 0).is_some() { Some(temp) } else { None }
self.0.checked_sub(*other).map(Self)
}
}
@ -112,13 +124,30 @@ pub(crate) mod system_time_internal {
Some(SystemTime::from_uefi(t))
}
/// This algorithm is a modified form of the one described in the post
/// https://blog.reverberate.org/2020/05/12/optimizing-date-algorithms.html
///
/// The changes are to use 1900-01-01-00:00:00 with timezone -1440 as anchor instead of UNIX
/// epoch used in the original algorithm.
pub(crate) const fn from_uefi(t: &Time) -> Duration {
assert!(t.month <= 12);
assert!(t.month != 0);
assert!(t.month <= 12 && t.month != 0);
assert!(t.year >= 1900 && t.year <= 9999);
assert!(t.day <= 31 && t.day != 0);
assert!(t.second < 60);
assert!(t.minute < 60);
assert!(t.hour < 24);
assert!(t.nanosecond < 1_000_000_000);
assert!(
(t.timezone <= 1440 && t.timezone >= -1440)
|| t.timezone == r_efi::efi::UNSPECIFIED_TIMEZONE
);
const YEAR_BASE: u32 = 4800; /* Before min year, multiple of 400. */
// Calculate the number of days since 1/1/1900
// Calculate the number of days since 1/1/1900. This is the earliest supported date in UEFI
// time.
// Use 1 March as the start
let (m_adj, overflow): (u32, bool) = (t.month as u32).overflowing_sub(3);
let (carry, adjust): (u32, u32) = if overflow { (1, 12) } else { (0, 0) };
@ -146,19 +175,20 @@ pub(crate) mod system_time_internal {
Duration::new(epoch, t.nanosecond)
}
/// This algorithm is a modifed version of the one described in the post:
/// https://howardhinnant.github.io/date_algorithms.html#clive_from_days
///
/// The changes are to use 1900-01-01-00:00:00 with timezone -1440 as anchor instead of UNIX
/// epoch used in the original algorithm.
pub(crate) const fn to_uefi(dur: &Duration, timezone: i16, daylight: u8) -> Option<Time> {
// Check timzone validity
assert!(timezone <= 1440);
assert!(timezone >= -1440);
assert!(timezone <= 1440 && timezone >= -1440);
let secs: u64 = if timezone == r_efi::efi::UNSPECIFIED_TIMEZONE {
dur.as_secs()
} else {
// FIXME: use checked_sub_signed once stablized
dur.as_secs().checked_add_signed((-timezone as i64) * SECS_IN_MINUTE as i64).unwrap()
};
// FIXME(#126043): use checked_sub_signed once stablized
let secs =
dur.as_secs().checked_add_signed((-timezone as i64) * SECS_IN_MINUTE as i64).unwrap();
// Convert to UTC
// Convert to seconds since 1900-01-01-00:00:00 in timezone.
let Some(secs) = secs.checked_sub(TIMEZONE_DELTA) else { return None };
let days = secs / SECS_IN_DAY;