Skip to content

std: sys: pal: uefi: Overhaul Time #139806

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 0 additions & 20 deletions library/std/src/sys/pal/uefi/tests.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
use super::alloc::*;
use super::time::*;
use crate::time::Duration;

#[test]
fn align() {
Expand All @@ -21,21 +19,3 @@ fn align() {
}
}
}

#[test]
fn epoch() {
let t = r_efi::system::Time {
year: 1970,
month: 1,
day: 1,
hour: 0,
minute: 0,
second: 0,
nanosecond: 0,
timezone: r_efi::efi::UNSPECIFIED_TIMEZONE,
daylight: 0,
pad1: 0,
pad2: 0,
};
assert_eq!(system_time_internal::uefi_time_to_duration(t), Duration::new(0, 0));
}
102 changes: 89 additions & 13 deletions library/std/src/sys/pal/uefi/time.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,28 @@
use crate::time::Duration;

const SECS_IN_MINUTE: u64 = 60;
const SECS_IN_HOUR: u64 = SECS_IN_MINUTE * 60;
const SECS_IN_DAY: u64 = SECS_IN_HOUR * 24;

#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)]
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
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)]
pub struct SystemTime(Duration);

pub const UNIX_EPOCH: SystemTime = SystemTime(Duration::from_secs(0));
pub const UNIX_EPOCH: SystemTime = SystemTime::from_uefi(r_efi::efi::Time {
year: 1970,
month: 1,
day: 1,
hour: 0,
minute: 0,
second: 0,
nanosecond: 0,
timezone: 0,
daylight: 0,
pad1: 0,
pad2: 0,
});

impl Instant {
pub fn now() -> Instant {
Expand Down Expand Up @@ -40,6 +52,14 @@ impl Instant {
}

impl SystemTime {
pub(crate) const fn from_uefi(t: r_efi::efi::Time) -> Self {
Self(system_time_internal::from_uefi(&t))
}

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)
}

pub fn now() -> SystemTime {
system_time_internal::now()
.unwrap_or_else(|| panic!("time not implemented on this platform"))
Expand All @@ -50,11 +70,17 @@ impl SystemTime {
}

pub fn checked_add_duration(&self, other: &Duration) -> Option<SystemTime> {
Some(SystemTime(self.0.checked_add(*other)?))
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 }
Comment on lines +75 to +76
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Check if can be represented in UEFI
if temp.to_uefi(0, 0).is_some() { Some(temp) } else { None }
Some(temp)

I don't think this check is necessary here. Many other platforms (e.g. many 32-bit Unix targets such as x86 FreeBSD) allow SystemTime to have a greater range than the underlying system time representation. If a SystemTime doesn't fit in the underlying system time representation, then any operations that need to convert to the underlying system representation (currently only File::set_times) will fail with an InvalidInput IO error. Additionally, what time's are representable in the UEFI time format will depend on the timezone, so this check disallow some representable times that are representable in the UEFI time format in some timezones but not UTC.

}

pub fn checked_sub_duration(&self, other: &Duration) -> Option<SystemTime> {
Some(SystemTime(self.0.checked_sub(*other)?))
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 }
}
}

Expand All @@ -66,25 +92,26 @@ pub(crate) mod system_time_internal {
use crate::mem::MaybeUninit;
use crate::ptr::NonNull;

const SECS_IN_MINUTE: u64 = 60;
const SECS_IN_HOUR: u64 = SECS_IN_MINUTE * 60;
const SECS_IN_DAY: u64 = SECS_IN_HOUR * 24;

pub fn now() -> Option<SystemTime> {
let runtime_services: NonNull<RuntimeServices> = helpers::runtime_services()?;
let mut t: MaybeUninit<Time> = MaybeUninit::uninit();
let r = unsafe {
((*runtime_services.as_ptr()).get_time)(t.as_mut_ptr(), crate::ptr::null_mut())
};

if r.is_error() {
return None;
}

let t = unsafe { t.assume_init() };

Some(SystemTime(uefi_time_to_duration(t)))
Some(SystemTime::from_uefi(t))
}

// This algorithm is based on the one described in the post
// https://blog.reverberate.org/2020/05/12/optimizing-date-algorithms.html
pub(crate) const fn uefi_time_to_duration(t: r_efi::system::Time) -> Duration {
pub(crate) const fn from_uefi(t: &Time) -> Duration {
assert!(t.month <= 12);
assert!(t.month != 0);

Expand All @@ -97,7 +124,7 @@ pub(crate) mod system_time_internal {
let y_adj: u32 = (t.year as u32) + YEAR_BASE - carry;
let month_days: u32 = (m_adj.wrapping_add(adjust) * 62719 + 769) / 2048;
let leap_days: u32 = y_adj / 4 - y_adj / 100 + y_adj / 400;
let days: u32 = y_adj * 365 + leap_days + month_days + (t.day as u32 - 1) - 2472632;
let days: u32 = y_adj * 365 + leap_days + month_days + (t.day as u32 - 1) - 2447065;

let localtime_epoch: u64 = (days as u64) * SECS_IN_DAY
+ (t.second as u64)
Expand All @@ -112,6 +139,55 @@ pub(crate) mod system_time_internal {

Duration::new(utc_epoch, t.nanosecond)
}

pub(crate) const fn to_uefi(dur: &Duration, timezone: i16, daylight: u8) -> Option<Time> {
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()
};

let days = secs / SECS_IN_DAY;
let remaining_secs = secs % SECS_IN_DAY;

let z = days + 693901;
let era = z / 146097;
let doe = z - (era * 146097);
let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365;
let mut y = yoe + era * 400;
let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
let mp = (5 * doy + 2) / 153;
let d = doy - (153 * mp + 2) / 5 + 1;
let m = if mp < 10 { mp + 3 } else { mp - 9 };

if m <= 2 {
y += 1;
}

let hour = (remaining_secs / SECS_IN_HOUR) as u8;
let minute = ((remaining_secs % SECS_IN_HOUR) / SECS_IN_MINUTE) as u8;
let second = (remaining_secs % SECS_IN_MINUTE) as u8;

// Check Bounds
if y >= 1900 && y <= 9999 {
Some(Time {
year: y as u16,
month: m as u8,
day: d as u8,
hour,
minute,
second,
nanosecond: dur.subsec_nanos(),
timezone,
daylight,
pad1: 0,
pad2: 0,
})
} else {
None
}
}
}

pub(crate) mod instant_internal {
Expand Down
Loading