11use 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 ) ]
84pub 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 ) ]
1111pub 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
1541impl Instant {
1642 pub fn now ( ) -> Instant {
@@ -40,6 +66,15 @@ impl Instant {
4066}
4167
4268impl 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