Skip to content

Commit c1a9b0d

Browse files
authored
Rollup merge of rust-lang#139806 - Ayush1325:uefi-systemtime, r=joboet
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. I have tested the conversions and calculations, but I am not sure if there is a way to run unit tests for platform specific code in Rust source. The only real benefit from using UEFI Time representation is that to and fro conversion will preserve `daylight` and `timezone` values. r? ```@joboet```
2 parents 9c55211 + 35906ea commit c1a9b0d

File tree

2 files changed

+190
-26
lines changed

2 files changed

+190
-26
lines changed

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

Lines changed: 51 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
1+
//! These tests are not run automatically right now. Please run these tests manually by copying them
2+
//! to a separate project when modifying any related code.
3+
14
use super::alloc::*;
2-
use super::time::*;
5+
use super::time::system_time_internal::{from_uefi, to_uefi};
36
use crate::io::{IoSlice, IoSliceMut};
47
use crate::time::Duration;
58

9+
const SECS_IN_MINUTE: u64 = 60;
10+
611
#[test]
712
fn align() {
813
// UEFI ABI specifies that allocation alignment minimum is always 8. So this can be
@@ -24,21 +29,61 @@ fn align() {
2429
}
2530

2631
#[test]
27-
fn epoch() {
28-
let t = r_efi::system::Time {
29-
year: 1970,
32+
fn systemtime_start() {
33+
let t = r_efi::efi::Time {
34+
year: 1900,
35+
month: 1,
36+
day: 1,
37+
hour: 0,
38+
minute: 0,
39+
second: 0,
40+
nanosecond: 0,
41+
timezone: -1440,
42+
daylight: 0,
43+
pad2: 0,
44+
};
45+
assert_eq!(from_uefi(&t), Duration::new(0, 0));
46+
assert_eq!(t, to_uefi(&from_uefi(&t), -1440, 0).unwrap());
47+
assert!(to_uefi(&from_uefi(&t), 0, 0).is_none());
48+
}
49+
50+
#[test]
51+
fn systemtime_utc_start() {
52+
let t = r_efi::efi::Time {
53+
year: 1900,
3054
month: 1,
3155
day: 1,
3256
hour: 0,
3357
minute: 0,
3458
second: 0,
59+
pad1: 0,
3560
nanosecond: 0,
36-
timezone: r_efi::efi::UNSPECIFIED_TIMEZONE,
61+
timezone: 0,
3762
daylight: 0,
63+
pad2: 0,
64+
};
65+
assert_eq!(from_uefi(&t), Duration::new(1440 * SECS_IN_MINUTE, 0));
66+
assert_eq!(t, to_uefi(&from_uefi(&t), 0, 0).unwrap());
67+
assert!(to_uefi(&from_uefi(&t), -1440, 0).is_some());
68+
}
69+
70+
#[test]
71+
fn systemtime_end() {
72+
let t = r_efi::efi::Time {
73+
year: 9999,
74+
month: 12,
75+
day: 31,
76+
hour: 23,
77+
minute: 59,
78+
second: 59,
3879
pad1: 0,
80+
nanosecond: 0,
81+
timezone: 1440,
82+
daylight: 0,
3983
pad2: 0,
4084
};
41-
assert_eq!(system_time_internal::uefi_time_to_duration(t), Duration::new(0, 0));
85+
assert!(to_uefi(&from_uefi(&t), 1440, 0).is_some());
86+
assert!(to_uefi(&from_uefi(&t), 1439, 0).is_none());
4287
}
4388

4489
// UEFI IoSlice and IoSliceMut Tests

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

Lines changed: 139 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,42 @@
11
use crate::time::Duration;
22

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

6+
/// When a Timezone is specified, the stored Duration is in UTC. If timezone is unspecified, then
7+
/// the timezone is assumed to be in UTC.
8+
///
9+
/// UEFI SystemTime is stored as Duration from 1900-01-01-00:00:00 with timezone -1440 as anchor
1010
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)]
1111
pub struct SystemTime(Duration);
1212

13-
pub const UNIX_EPOCH: SystemTime = SystemTime(Duration::from_secs(0));
13+
pub const UNIX_EPOCH: SystemTime = SystemTime::from_uefi(r_efi::efi::Time {
14+
year: 1970,
15+
month: 1,
16+
day: 1,
17+
hour: 0,
18+
minute: 0,
19+
second: 0,
20+
nanosecond: 0,
21+
timezone: 0,
22+
daylight: 0,
23+
pad1: 0,
24+
pad2: 0,
25+
});
26+
27+
const MAX_UEFI_TIME: SystemTime = SystemTime::from_uefi(r_efi::efi::Time {
28+
year: 9999,
29+
month: 12,
30+
day: 31,
31+
hour: 23,
32+
minute: 59,
33+
second: 59,
34+
nanosecond: 999_999_999,
35+
timezone: 1440,
36+
daylight: 0,
37+
pad1: 0,
38+
pad2: 0,
39+
});
1440

1541
impl Instant {
1642
pub fn now() -> Instant {
@@ -40,6 +66,15 @@ impl Instant {
4066
}
4167

4268
impl SystemTime {
69+
pub(crate) const fn from_uefi(t: r_efi::efi::Time) -> Self {
70+
Self(system_time_internal::from_uefi(&t))
71+
}
72+
73+
#[expect(dead_code)]
74+
pub(crate) const fn to_uefi(self, timezone: i16, daylight: u8) -> Option<r_efi::efi::Time> {
75+
system_time_internal::to_uefi(&self.0, timezone, daylight)
76+
}
77+
4378
pub fn now() -> SystemTime {
4479
system_time_internal::now()
4580
.unwrap_or_else(|| panic!("time not implemented on this platform"))
@@ -50,11 +85,14 @@ impl SystemTime {
5085
}
5186

5287
pub fn checked_add_duration(&self, other: &Duration) -> Option<SystemTime> {
53-
Some(SystemTime(self.0.checked_add(*other)?))
88+
let temp = Self(self.0.checked_add(*other)?);
89+
90+
// Check if can be represented in UEFI
91+
if temp <= MAX_UEFI_TIME { Some(temp) } else { None }
5492
}
5593

5694
pub fn checked_sub_duration(&self, other: &Duration) -> Option<SystemTime> {
57-
Some(SystemTime(self.0.checked_sub(*other)?))
95+
self.0.checked_sub(*other).map(Self)
5896
}
5997
}
6098

@@ -66,51 +104,132 @@ pub(crate) mod system_time_internal {
66104
use crate::mem::MaybeUninit;
67105
use crate::ptr::NonNull;
68106

107+
const SECS_IN_MINUTE: u64 = 60;
108+
const SECS_IN_HOUR: u64 = SECS_IN_MINUTE * 60;
109+
const SECS_IN_DAY: u64 = SECS_IN_HOUR * 24;
110+
const TIMEZONE_DELTA: u64 = 1440 * SECS_IN_MINUTE;
111+
69112
pub fn now() -> Option<SystemTime> {
70113
let runtime_services: NonNull<RuntimeServices> = helpers::runtime_services()?;
71114
let mut t: MaybeUninit<Time> = MaybeUninit::uninit();
72115
let r = unsafe {
73116
((*runtime_services.as_ptr()).get_time)(t.as_mut_ptr(), crate::ptr::null_mut())
74117
};
75-
76118
if r.is_error() {
77119
return None;
78120
}
79121

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

82-
Some(SystemTime(uefi_time_to_duration(t)))
124+
Some(SystemTime::from_uefi(t))
83125
}
84126

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);
127+
/// This algorithm is a modified form of the one described in the post
128+
/// https://blog.reverberate.org/2020/05/12/optimizing-date-algorithms.html
129+
///
130+
/// The changes are to use 1900-01-01-00:00:00 with timezone -1440 as anchor instead of UNIX
131+
/// epoch used in the original algorithm.
132+
pub(crate) const fn from_uefi(t: &Time) -> Duration {
133+
assert!(t.month <= 12 && t.month != 0);
134+
assert!(t.year >= 1900 && t.year <= 9999);
135+
assert!(t.day <= 31 && t.day != 0);
136+
137+
assert!(t.second < 60);
138+
assert!(t.minute < 60);
139+
assert!(t.hour < 24);
140+
assert!(t.nanosecond < 1_000_000_000);
141+
142+
assert!(
143+
(t.timezone <= 1440 && t.timezone >= -1440)
144+
|| t.timezone == r_efi::efi::UNSPECIFIED_TIMEZONE
145+
);
90146

91147
const YEAR_BASE: u32 = 4800; /* Before min year, multiple of 400. */
92148

93-
// Calculate the number of days since 1/1/1970
149+
// Calculate the number of days since 1/1/1900. This is the earliest supported date in UEFI
150+
// time.
94151
// Use 1 March as the start
95152
let (m_adj, overflow): (u32, bool) = (t.month as u32).overflowing_sub(3);
96153
let (carry, adjust): (u32, u32) = if overflow { (1, 12) } else { (0, 0) };
97154
let y_adj: u32 = (t.year as u32) + YEAR_BASE - carry;
98155
let month_days: u32 = (m_adj.wrapping_add(adjust) * 62719 + 769) / 2048;
99156
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;
157+
let days: u32 = y_adj * 365 + leap_days + month_days + (t.day as u32 - 1) - 2447065;
101158

102159
let localtime_epoch: u64 = (days as u64) * SECS_IN_DAY
103160
+ (t.second as u64)
104161
+ (t.minute as u64) * SECS_IN_MINUTE
105162
+ (t.hour as u64) * SECS_IN_HOUR;
106163

107-
let utc_epoch: u64 = if t.timezone == r_efi::efi::UNSPECIFIED_TIMEZONE {
108-
localtime_epoch
164+
// Calculate the offset from 1/1/1900 at timezone -1440 min
165+
let adjusted_localtime_epoc: u64 = localtime_epoch + TIMEZONE_DELTA;
166+
167+
let epoch: u64 = if t.timezone == r_efi::efi::UNSPECIFIED_TIMEZONE {
168+
adjusted_localtime_epoc
109169
} else {
110-
(localtime_epoch as i64 + (t.timezone as i64) * SECS_IN_MINUTE as i64) as u64
170+
adjusted_localtime_epoc
171+
.checked_add_signed((t.timezone as i64) * SECS_IN_MINUTE as i64)
172+
.unwrap()
111173
};
112174

113-
Duration::new(utc_epoch, t.nanosecond)
175+
Duration::new(epoch, t.nanosecond)
176+
}
177+
178+
/// This algorithm is a modifed version of the one described in the post:
179+
/// https://howardhinnant.github.io/date_algorithms.html#clive_from_days
180+
///
181+
/// The changes are to use 1900-01-01-00:00:00 with timezone -1440 as anchor instead of UNIX
182+
/// epoch used in the original algorithm.
183+
pub(crate) const fn to_uefi(dur: &Duration, timezone: i16, daylight: u8) -> Option<Time> {
184+
// Check timzone validity
185+
assert!(timezone <= 1440 && timezone >= -1440);
186+
187+
// FIXME(#126043): use checked_sub_signed once stablized
188+
let secs =
189+
dur.as_secs().checked_add_signed((-timezone as i64) * SECS_IN_MINUTE as i64).unwrap();
190+
191+
// Convert to seconds since 1900-01-01-00:00:00 in timezone.
192+
let Some(secs) = secs.checked_sub(TIMEZONE_DELTA) else { return None };
193+
194+
let days = secs / SECS_IN_DAY;
195+
let remaining_secs = secs % SECS_IN_DAY;
196+
197+
let z = days + 693901;
198+
let era = z / 146097;
199+
let doe = z - (era * 146097);
200+
let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365;
201+
let mut y = yoe + era * 400;
202+
let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
203+
let mp = (5 * doy + 2) / 153;
204+
let d = doy - (153 * mp + 2) / 5 + 1;
205+
let m = if mp < 10 { mp + 3 } else { mp - 9 };
206+
207+
if m <= 2 {
208+
y += 1;
209+
}
210+
211+
let hour = (remaining_secs / SECS_IN_HOUR) as u8;
212+
let minute = ((remaining_secs % SECS_IN_HOUR) / SECS_IN_MINUTE) as u8;
213+
let second = (remaining_secs % SECS_IN_MINUTE) as u8;
214+
215+
// Check Bounds
216+
if y >= 1900 && y <= 9999 {
217+
Some(Time {
218+
year: y as u16,
219+
month: m as u8,
220+
day: d as u8,
221+
hour,
222+
minute,
223+
second,
224+
nanosecond: dur.subsec_nanos(),
225+
timezone,
226+
daylight,
227+
pad1: 0,
228+
pad2: 0,
229+
})
230+
} else {
231+
None
232+
}
114233
}
115234
}
116235

0 commit comments

Comments
 (0)