Skip to content

Commit 1e8af15

Browse files
committed
std: sys: pal: uefi: Overhaul Time
Use a time representation with 1900-01-01-00:00:00 as anchor. This is the earliest time supported in UEFI. Signed-off-by: Ayush Singh <[email protected]>
1 parent 092a284 commit 1e8af15

File tree

2 files changed

+151
-36
lines changed

2 files changed

+151
-36
lines changed

library/std/src/sys/pal/uefi/tests.rs

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
11
use super::alloc::*;
2-
use super::time::*;
3-
use crate::time::Duration;
42

53
#[test]
64
fn align() {
@@ -21,21 +19,3 @@ fn align() {
2119
}
2220
}
2321
}
24-
25-
#[test]
26-
fn epoch() {
27-
let t = r_efi::system::Time {
28-
year: 1970,
29-
month: 1,
30-
day: 1,
31-
hour: 0,
32-
minute: 0,
33-
second: 0,
34-
nanosecond: 0,
35-
timezone: r_efi::efi::UNSPECIFIED_TIMEZONE,
36-
daylight: 0,
37-
pad1: 0,
38-
pad2: 0,
39-
};
40-
assert_eq!(system_time_internal::uefi_time_to_duration(t), Duration::new(0, 0));
41-
}

library/std/src/sys/pal/uefi/time.rs

Lines changed: 151 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,76 @@
1+
use crate::cmp::Ordering;
12
use crate::time::Duration;
23

3-
const SECS_IN_MINUTE: u64 = 60;
4-
const SECS_IN_HOUR: u64 = SECS_IN_MINUTE * 60;
5-
const SECS_IN_DAY: u64 = SECS_IN_HOUR * 24;
6-
74
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)]
85
pub struct Instant(Duration);
96

10-
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)]
11-
pub struct SystemTime(Duration);
7+
#[derive(Copy, Clone, Debug)]
8+
pub struct SystemTime {
9+
// Duration from 1900-01-01-00:00:00
10+
dur: Duration,
11+
timezone: i16,
12+
daylight: u8,
13+
}
14+
15+
/// Deriving does not work because we need to account for timezone
16+
impl Ord for SystemTime {
17+
fn cmp(&self, other: &Self) -> Ordering {
18+
// Comparison between unspecified timezone and normal timezone is not allowed
19+
if (self.timezone == r_efi::efi::UNSPECIFIED_TIMEZONE
20+
|| other.timezone == r_efi::efi::UNSPECIFIED_TIMEZONE)
21+
&& (self.timezone != other.timezone)
22+
{
23+
panic!("Cannot compare unspecified timezone with a normal timezone")
24+
} else {
25+
self.dur.cmp(&other.dur)
26+
}
27+
}
28+
}
29+
30+
impl PartialOrd for SystemTime {
31+
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
32+
Some(self.cmp(other))
33+
}
34+
}
35+
36+
/// Deriving does not work because we need to account for timezone
37+
impl PartialEq for SystemTime {
38+
fn eq(&self, other: &Self) -> bool {
39+
if (self.timezone == r_efi::efi::UNSPECIFIED_TIMEZONE
40+
|| other.timezone == r_efi::efi::UNSPECIFIED_TIMEZONE)
41+
&& (self.timezone != other.timezone)
42+
{
43+
false
44+
} else {
45+
self.dur.eq(&other.dur)
46+
}
47+
}
48+
}
49+
50+
impl Eq for SystemTime {}
51+
52+
impl crate::hash::Hash for SystemTime {
53+
fn hash<H: crate::hash::Hasher>(&self, state: &mut H) {
54+
self.dur.hash(state);
55+
if self.timezone == r_efi::efi::UNSPECIFIED_TIMEZONE {
56+
self.timezone.hash(state);
57+
}
58+
}
59+
}
1260

13-
pub const UNIX_EPOCH: SystemTime = SystemTime(Duration::from_secs(0));
61+
pub const UNIX_EPOCH: SystemTime = SystemTime::from_uefi(r_efi::efi::Time {
62+
year: 1970,
63+
month: 1,
64+
day: 1,
65+
hour: 0,
66+
minute: 0,
67+
second: 0,
68+
nanosecond: 0,
69+
timezone: 0,
70+
daylight: 0,
71+
pad1: 0,
72+
pad2: 0,
73+
});
1474

1575
impl Instant {
1676
pub fn now() -> Instant {
@@ -40,21 +100,46 @@ impl Instant {
40100
}
41101

42102
impl SystemTime {
103+
pub(crate) const fn from_uefi(t: r_efi::efi::Time) -> Self {
104+
let dur = system_time_internal::from_uefi(&t);
105+
Self { dur, timezone: t.timezone, daylight: t.daylight }
106+
}
107+
108+
pub(crate) const fn to_uefi(self) -> Option<r_efi::efi::Time> {
109+
system_time_internal::to_uefi(&self.dur, self.timezone, self.daylight)
110+
}
111+
43112
pub fn now() -> SystemTime {
44113
system_time_internal::now()
45114
.unwrap_or_else(|| panic!("time not implemented on this platform"))
46115
}
47116

48117
pub fn sub_time(&self, other: &SystemTime) -> Result<Duration, Duration> {
49-
self.0.checked_sub(other.0).ok_or_else(|| other.0 - self.0)
118+
// Comparison between unspecified timezone and normal timezone is not allowed
119+
if (self.timezone == r_efi::efi::UNSPECIFIED_TIMEZONE
120+
|| other.timezone == r_efi::efi::UNSPECIFIED_TIMEZONE)
121+
&& (self.timezone != other.timezone)
122+
{
123+
panic!("Cannot compare unspecified timezone with a normal timezone")
124+
} else {
125+
self.dur.checked_sub(other.dur).ok_or_else(|| other.dur - self.dur)
126+
}
50127
}
51128

52129
pub fn checked_add_duration(&self, other: &Duration) -> Option<SystemTime> {
53-
Some(SystemTime(self.0.checked_add(*other)?))
130+
let mut temp = self.clone();
131+
temp.dur = self.dur.checked_add(*other)?;
132+
133+
// Check if can be represented in UEFI
134+
if temp.to_uefi().is_some() { Some(temp) } else { None }
54135
}
55136

56137
pub fn checked_sub_duration(&self, other: &Duration) -> Option<SystemTime> {
57-
Some(SystemTime(self.0.checked_sub(*other)?))
138+
let mut temp = self.clone();
139+
temp.dur = self.dur.checked_sub(*other)?;
140+
141+
// Check if can be represented in UEFI
142+
if temp.to_uefi().is_some() { Some(temp) } else { None }
58143
}
59144
}
60145

@@ -66,25 +151,26 @@ pub(crate) mod system_time_internal {
66151
use crate::mem::MaybeUninit;
67152
use crate::ptr::NonNull;
68153

154+
const SECS_IN_MINUTE: u64 = 60;
155+
const SECS_IN_HOUR: u64 = SECS_IN_MINUTE * 60;
156+
const SECS_IN_DAY: u64 = SECS_IN_HOUR * 24;
157+
69158
pub fn now() -> Option<SystemTime> {
70159
let runtime_services: NonNull<RuntimeServices> = helpers::runtime_services()?;
71160
let mut t: MaybeUninit<Time> = MaybeUninit::uninit();
72161
let r = unsafe {
73162
((*runtime_services.as_ptr()).get_time)(t.as_mut_ptr(), crate::ptr::null_mut())
74163
};
75-
76164
if r.is_error() {
77165
return None;
78166
}
79167

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

82-
Some(SystemTime(uefi_time_to_duration(t)))
170+
Some(SystemTime::from_uefi(t))
83171
}
84172

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

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

102188
let localtime_epoch: u64 = (days as u64) * SECS_IN_DAY
103189
+ (t.second as u64)
@@ -112,6 +198,55 @@ pub(crate) mod system_time_internal {
112198

113199
Duration::new(utc_epoch, t.nanosecond)
114200
}
201+
202+
pub(crate) const fn to_uefi(dur: &Duration, timezone: i16, daylight: u8) -> Option<Time> {
203+
let secs: u64 = if timezone == r_efi::efi::UNSPECIFIED_TIMEZONE {
204+
dur.as_secs()
205+
} else {
206+
// FIXME: use checked_sub_signed once stablized
207+
dur.as_secs().checked_add_signed((-timezone as i64) * SECS_IN_MINUTE as i64).unwrap()
208+
};
209+
210+
let days = secs / SECS_IN_DAY;
211+
let remaining_secs = secs % SECS_IN_DAY;
212+
213+
let z = days + 693901;
214+
let era = z / 146097;
215+
let doe = z - (era * 146097);
216+
let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365;
217+
let mut y = yoe + era * 400;
218+
let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
219+
let mp = (5 * doy + 2) / 153;
220+
let d = doy - (153 * mp + 2) / 5 + 1;
221+
let m = if mp < 10 { mp + 3 } else { mp - 9 };
222+
223+
if m <= 2 {
224+
y += 1;
225+
}
226+
227+
let hour = (remaining_secs / SECS_IN_HOUR) as u8;
228+
let minute = ((remaining_secs % SECS_IN_HOUR) / SECS_IN_MINUTE) as u8;
229+
let second = (remaining_secs % SECS_IN_MINUTE) as u8;
230+
231+
// Check Bounds
232+
if y >= 1900 && y <= 9999 {
233+
Some(Time {
234+
year: y as u16,
235+
month: m as u8,
236+
day: d as u8,
237+
hour,
238+
minute,
239+
second,
240+
nanosecond: dur.subsec_nanos(),
241+
timezone,
242+
daylight,
243+
pad1: 0,
244+
pad2: 0,
245+
})
246+
} else {
247+
None
248+
}
249+
}
115250
}
116251

117252
pub(crate) mod instant_internal {

0 commit comments

Comments
 (0)