about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--library/std/src/sys/pal/uefi/tests.rs54
-rw-r--r--library/std/src/sys/pal/uefi/time.rs66
2 files changed, 96 insertions, 24 deletions
diff --git a/library/std/src/sys/pal/uefi/tests.rs b/library/std/src/sys/pal/uefi/tests.rs
index 6a15c03184c..56ca999cc7e 100644
--- a/library/std/src/sys/pal/uefi/tests.rs
+++ b/library/std/src/sys/pal/uefi/tests.rs
@@ -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,
+        pad2: 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_eq!(system_time_internal::uefi_time_to_duration(t), Duration::new(0, 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
diff --git a/library/std/src/sys/pal/uefi/time.rs b/library/std/src/sys/pal/uefi/time.rs
index 32c3cfd143c..a5ab7690327 100644
--- a/library/std/src/sys/pal/uefi/time.rs
+++ b/library/std/src/sys/pal/uefi/time.rs
@@ -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;