Skip to content

Commit 451a576

Browse files
committed
std: sys: pal: uefi: Overhaul Time
Use UEFI time format for SystemTime. All calculations and comparisons are being done using UnixTime since UEFI Time format does not seem good fit for those calculations. Signed-off-by: Ayush Singh <[email protected]>
1 parent 092a284 commit 451a576

File tree

2 files changed

+238
-62
lines changed

2 files changed

+238
-62
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: 238 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,55 @@
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(r_efi::efi::Time);
9+
10+
/// Deriving does not work because we need to account for timezone
11+
impl Ord for SystemTime {
12+
fn cmp(&self, other: &Self) -> Ordering {
13+
system_time_internal::UnixTime::from_uefi(&self.0)
14+
.cmp(&system_time_internal::UnixTime::from_uefi(&other.0))
15+
}
16+
}
17+
18+
impl PartialOrd for SystemTime {
19+
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
20+
Some(self.cmp(other))
21+
}
22+
}
23+
24+
/// Deriving does not work because we need to account for timezone
25+
impl PartialEq for SystemTime {
26+
fn eq(&self, other: &Self) -> bool {
27+
system_time_internal::UnixTime::from_uefi(&self.0)
28+
== system_time_internal::UnixTime::from_uefi(&other.0)
29+
}
30+
}
31+
32+
impl Eq for SystemTime {}
1233

13-
pub const UNIX_EPOCH: SystemTime = SystemTime(Duration::from_secs(0));
34+
impl crate::hash::Hash for SystemTime {
35+
fn hash<H: crate::hash::Hasher>(&self, state: &mut H) {
36+
system_time_internal::UnixTime::from_uefi(&self.0).hash(state);
37+
}
38+
}
39+
40+
pub const UNIX_EPOCH: SystemTime = SystemTime(r_efi::efi::Time {
41+
year: 1970,
42+
month: 1,
43+
day: 1,
44+
hour: 0,
45+
minute: 0,
46+
second: 0,
47+
nanosecond: 0,
48+
timezone: 0,
49+
daylight: 0,
50+
pad1: 0,
51+
pad2: 0,
52+
});
1453

1554
impl Instant {
1655
pub fn now() -> Instant {
@@ -40,21 +79,39 @@ impl Instant {
4079
}
4180

4281
impl SystemTime {
82+
pub(crate) const fn from_uefi(t: r_efi::efi::Time) -> Self {
83+
Self(t)
84+
}
85+
86+
#[expect(dead_code)]
87+
pub(crate) const fn to_uefi(self) -> r_efi::efi::Time {
88+
self.0
89+
}
90+
4391
pub fn now() -> SystemTime {
4492
system_time_internal::now()
4593
.unwrap_or_else(|| panic!("time not implemented on this platform"))
4694
}
4795

4896
pub fn sub_time(&self, other: &SystemTime) -> Result<Duration, Duration> {
49-
self.0.checked_sub(other.0).ok_or_else(|| other.0 - self.0)
97+
system_time_internal::UnixTime::from_uefi(&self.0)
98+
.sub_time(system_time_internal::UnixTime::from_uefi(&other.0))
5099
}
51100

52101
pub fn checked_add_duration(&self, other: &Duration) -> Option<SystemTime> {
53-
Some(SystemTime(self.0.checked_add(*other)?))
102+
Some(
103+
system_time_internal::UnixTime::from_uefi(&self.0)
104+
.checked_add(*other)
105+
.to_systemtime(self.0.timezone, self.0.daylight),
106+
)
54107
}
55108

56109
pub fn checked_sub_duration(&self, other: &Duration) -> Option<SystemTime> {
57-
Some(SystemTime(self.0.checked_sub(*other)?))
110+
Some(
111+
system_time_internal::UnixTime::from_uefi(&self.0)
112+
.checked_sub(*other)
113+
.to_systemtime(self.0.timezone, self.0.daylight),
114+
)
58115
}
59116
}
60117

@@ -63,9 +120,179 @@ pub(crate) mod system_time_internal {
63120

64121
use super::super::helpers;
65122
use super::*;
123+
use crate::cmp::Ordering;
66124
use crate::mem::MaybeUninit;
67125
use crate::ptr::NonNull;
68126

127+
const SECS_IN_MINUTE: i64 = 60;
128+
const SECS_IN_HOUR: i64 = SECS_IN_MINUTE * 60;
129+
const SECS_IN_DAY: i64 = SECS_IN_HOUR * 24;
130+
const NS_PER_SEC: u32 = 1_000_000_000;
131+
132+
#[derive(Eq, PartialEq, Hash)]
133+
pub(crate) struct UnixTime {
134+
secs: i64,
135+
nanos: u32,
136+
}
137+
138+
impl UnixTime {
139+
// This algorithm is based on the one described in the post
140+
// https://blog.reverberate.org/2020/05/12/optimizing-date-algorithms.html
141+
pub(crate) const fn from_uefi(t: &Time) -> Self {
142+
assert!(t.month <= 12);
143+
assert!(t.month != 0);
144+
145+
const YEAR_BASE: u32 = 4800; /* Before min year, multiple of 400. */
146+
147+
// Calculate the number of days since 1/1/1970
148+
// Use 1 March as the start
149+
let (m_adj, overflow): (u32, bool) = (t.month as u32).overflowing_sub(3);
150+
let (carry, adjust): (u32, u32) = if overflow { (1, 12) } else { (0, 0) };
151+
let y_adj: u32 = (t.year as u32) + YEAR_BASE - carry;
152+
let month_days: u32 = (m_adj.wrapping_add(adjust) * 62719 + 769) / 2048;
153+
let leap_days: u32 = y_adj / 4 - y_adj / 100 + y_adj / 400;
154+
155+
// Allow days to be negative to denote days before EPOCH
156+
let days: i64 =
157+
(y_adj * 365 + leap_days + month_days + (t.day as u32 - 1)) as i64 - 2472632;
158+
159+
let localtime_epoch: i64 = days * SECS_IN_DAY
160+
+ (t.second as i64)
161+
+ (t.minute as i64) * SECS_IN_MINUTE
162+
+ (t.hour as i64) * SECS_IN_HOUR;
163+
164+
let utc_epoch: i64 = if t.timezone == r_efi::efi::UNSPECIFIED_TIMEZONE {
165+
localtime_epoch
166+
} else {
167+
localtime_epoch + (t.timezone as i64) * SECS_IN_MINUTE
168+
};
169+
170+
Self { secs: utc_epoch, nanos: t.nanosecond }
171+
}
172+
173+
/// This algorithm is taken from: http://howardhinnant.github.io/date_algorithms.html
174+
pub(crate) const fn to_systemtime(&self, timezone: i16, daylight: u8) -> super::SystemTime {
175+
let secs: i64 = if timezone == r_efi::efi::UNSPECIFIED_TIMEZONE {
176+
self.secs
177+
} else {
178+
self.secs - (timezone as i64) * SECS_IN_MINUTE
179+
};
180+
181+
let (days, remaining_secs): (i64, u64) = {
182+
let days = secs / SECS_IN_DAY;
183+
let remaining_secs = secs % SECS_IN_DAY;
184+
185+
if remaining_secs < 0 {
186+
(days - 1, (SECS_IN_DAY + remaining_secs) as u64)
187+
} else {
188+
(days, remaining_secs as u64)
189+
}
190+
};
191+
192+
let z = days + 719468;
193+
let era = z / 146097;
194+
let doe = z - (era * 146097);
195+
let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365;
196+
let mut y = yoe + era * 400;
197+
let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
198+
let mp = (5 * doy + 2) / 153;
199+
let d = doy - (153 * mp + 2) / 5 + 1;
200+
let m = if mp < 10 { mp + 3 } else { mp - 9 };
201+
202+
if m <= 2 {
203+
y += 1;
204+
}
205+
206+
let hour = (remaining_secs / SECS_IN_HOUR as u64) as u8;
207+
let minute = ((remaining_secs % SECS_IN_HOUR as u64) / SECS_IN_MINUTE as u64) as u8;
208+
let second = (remaining_secs % SECS_IN_MINUTE as u64) as u8;
209+
210+
super::SystemTime::from_uefi(Time {
211+
year: y as u16,
212+
month: m as u8,
213+
day: d as u8,
214+
hour,
215+
minute,
216+
second,
217+
nanosecond: self.nanos,
218+
timezone,
219+
daylight,
220+
pad1: 0,
221+
pad2: 0,
222+
})
223+
}
224+
225+
pub(crate) const fn checked_add(&self, dur: Duration) -> Self {
226+
let temp: u32 = self.nanos + dur.subsec_nanos();
227+
let nanos: u32 = temp % NS_PER_SEC;
228+
let secs: i64 = self.secs + dur.as_secs() as i64 + (temp / NS_PER_SEC) as i64;
229+
230+
Self { secs, nanos }
231+
}
232+
233+
pub(crate) const fn checked_sub(&self, dur: Duration) -> Self {
234+
let (secs, nanos) = if self.nanos < dur.subsec_nanos() {
235+
let temp = NS_PER_SEC + self.nanos - dur.subsec_nanos();
236+
(self.secs - dur.as_secs() as i64 - 1, temp)
237+
} else {
238+
(self.secs - dur.as_secs() as i64, self.nanos - dur.subsec_nanos())
239+
};
240+
241+
Self { secs, nanos }
242+
}
243+
244+
pub(crate) fn sub_time(self, other: Self) -> Result<Duration, Duration> {
245+
if self >= other {
246+
let temp = self - other;
247+
assert!(temp.secs > 0);
248+
249+
Ok(Duration::new(temp.secs as u64, temp.nanos))
250+
} else {
251+
let temp = other - self;
252+
assert!(temp.secs > 0);
253+
254+
Err(Duration::new(temp.secs as u64, temp.nanos))
255+
}
256+
}
257+
}
258+
259+
impl Ord for UnixTime {
260+
fn cmp(&self, other: &Self) -> Ordering {
261+
if self.secs > other.secs {
262+
Ordering::Greater
263+
} else if self.secs < other.secs {
264+
Ordering::Less
265+
} else if self.nanos > other.nanos {
266+
Ordering::Greater
267+
} else if self.nanos < other.nanos {
268+
Ordering::Less
269+
} else {
270+
Ordering::Equal
271+
}
272+
}
273+
}
274+
275+
impl PartialOrd for UnixTime {
276+
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
277+
Some(self.cmp(other))
278+
}
279+
}
280+
281+
impl crate::ops::Sub for UnixTime {
282+
type Output = Self;
283+
284+
fn sub(self, other: Self) -> Self {
285+
let (secs, nanos) = if self.nanos < other.nanos {
286+
let temp = NS_PER_SEC + self.nanos - other.nanos;
287+
(self.secs - other.secs - 1, temp)
288+
} else {
289+
(self.secs - other.secs, self.nanos - other.nanos)
290+
};
291+
292+
Self { secs, nanos }
293+
}
294+
}
295+
69296
pub fn now() -> Option<SystemTime> {
70297
let runtime_services: NonNull<RuntimeServices> = helpers::runtime_services()?;
71298
let mut t: MaybeUninit<Time> = MaybeUninit::uninit();
@@ -79,38 +306,7 @@ pub(crate) mod system_time_internal {
79306

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

82-
Some(SystemTime(uefi_time_to_duration(t)))
83-
}
84-
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 {
88-
assert!(t.month <= 12);
89-
assert!(t.month != 0);
90-
91-
const YEAR_BASE: u32 = 4800; /* Before min year, multiple of 400. */
92-
93-
// Calculate the number of days since 1/1/1970
94-
// Use 1 March as the start
95-
let (m_adj, overflow): (u32, bool) = (t.month as u32).overflowing_sub(3);
96-
let (carry, adjust): (u32, u32) = if overflow { (1, 12) } else { (0, 0) };
97-
let y_adj: u32 = (t.year as u32) + YEAR_BASE - carry;
98-
let month_days: u32 = (m_adj.wrapping_add(adjust) * 62719 + 769) / 2048;
99-
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;
101-
102-
let localtime_epoch: u64 = (days as u64) * SECS_IN_DAY
103-
+ (t.second as u64)
104-
+ (t.minute as u64) * SECS_IN_MINUTE
105-
+ (t.hour as u64) * SECS_IN_HOUR;
106-
107-
let utc_epoch: u64 = if t.timezone == r_efi::efi::UNSPECIFIED_TIMEZONE {
108-
localtime_epoch
109-
} else {
110-
(localtime_epoch as i64 + (t.timezone as i64) * SECS_IN_MINUTE as i64) as u64
111-
};
112-
113-
Duration::new(utc_epoch, t.nanosecond)
309+
Some(SystemTime::from_uefi(t))
114310
}
115311
}
116312

0 commit comments

Comments
 (0)