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
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+ } ) ;
1426
1527impl Instant {
1628 pub fn now ( ) -> Instant {
@@ -40,6 +52,14 @@ impl Instant {
4052}
4153
4254impl SystemTime {
55+ pub ( crate ) const fn from_uefi ( t : r_efi:: efi:: Time ) -> Self {
56+ Self ( system_time_internal:: from_uefi ( & t) )
57+ }
58+
59+ pub ( crate ) const fn to_uefi ( self , timezone : i16 , daylight : u8 ) -> Option < r_efi:: efi:: Time > {
60+ system_time_internal:: to_uefi ( & self . 0 , timezone, daylight)
61+ }
62+
4363 pub fn now ( ) -> SystemTime {
4464 system_time_internal:: now ( )
4565 . unwrap_or_else ( || panic ! ( "time not implemented on this platform" ) )
@@ -50,11 +70,17 @@ impl SystemTime {
5070 }
5171
5272 pub fn checked_add_duration ( & self , other : & Duration ) -> Option < SystemTime > {
53- Some ( SystemTime ( self . 0 . checked_add ( * other) ?) )
73+ let temp = Self ( self . 0 . checked_add ( * other) ?) ;
74+
75+ // Check if can be represented in UEFI
76+ if temp. to_uefi ( 0 , 0 ) . is_some ( ) { Some ( temp) } else { None }
5477 }
5578
5679 pub fn checked_sub_duration ( & self , other : & Duration ) -> Option < SystemTime > {
57- Some ( SystemTime ( self . 0 . checked_sub ( * other) ?) )
80+ let temp = Self ( self . 0 . checked_sub ( * other) ?) ;
81+
82+ // Check if can be represented in UEFI
83+ if temp. to_uefi ( 0 , 0 ) . is_some ( ) { Some ( temp) } else { None }
5884 }
5985}
6086
@@ -66,51 +92,114 @@ pub(crate) mod system_time_internal {
6692 use crate :: mem:: MaybeUninit ;
6793 use crate :: ptr:: NonNull ;
6894
95+ const SECS_IN_MINUTE : u64 = 60 ;
96+ const SECS_IN_HOUR : u64 = SECS_IN_MINUTE * 60 ;
97+ const SECS_IN_DAY : u64 = SECS_IN_HOUR * 24 ;
98+ const TIMEZONE_DELTA : u64 = 1440 * SECS_IN_MINUTE ;
99+
69100 pub fn now ( ) -> Option < SystemTime > {
70101 let runtime_services: NonNull < RuntimeServices > = helpers:: runtime_services ( ) ?;
71102 let mut t: MaybeUninit < Time > = MaybeUninit :: uninit ( ) ;
72103 let r = unsafe {
73104 ( ( * runtime_services. as_ptr ( ) ) . get_time ) ( t. as_mut_ptr ( ) , crate :: ptr:: null_mut ( ) )
74105 } ;
75-
76106 if r. is_error ( ) {
77107 return None ;
78108 }
79109
80110 let t = unsafe { t. assume_init ( ) } ;
81111
82- Some ( SystemTime ( uefi_time_to_duration ( t ) ) )
112+ Some ( SystemTime :: from_uefi ( t ) )
83113 }
84114
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 {
115+ pub ( crate ) const fn from_uefi ( t : & Time ) -> Duration {
88116 assert ! ( t. month <= 12 ) ;
89117 assert ! ( t. month != 0 ) ;
90118
91119 const YEAR_BASE : u32 = 4800 ; /* Before min year, multiple of 400. */
92120
93- // Calculate the number of days since 1/1/1970
121+ // Calculate the number of days since 1/1/1900
94122 // Use 1 March as the start
95123 let ( m_adj, overflow) : ( u32 , bool ) = ( t. month as u32 ) . overflowing_sub ( 3 ) ;
96124 let ( carry, adjust) : ( u32 , u32 ) = if overflow { ( 1 , 12 ) } else { ( 0 , 0 ) } ;
97125 let y_adj: u32 = ( t. year as u32 ) + YEAR_BASE - carry;
98126 let month_days: u32 = ( m_adj. wrapping_add ( adjust) * 62719 + 769 ) / 2048 ;
99127 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 ;
128+ let days: u32 = y_adj * 365 + leap_days + month_days + ( t. day as u32 - 1 ) - 2447065 ;
101129
102130 let localtime_epoch: u64 = ( days as u64 ) * SECS_IN_DAY
103131 + ( t. second as u64 )
104132 + ( t. minute as u64 ) * SECS_IN_MINUTE
105133 + ( t. hour as u64 ) * SECS_IN_HOUR ;
106134
107- let utc_epoch: u64 = if t. timezone == r_efi:: efi:: UNSPECIFIED_TIMEZONE {
108- localtime_epoch
135+ // Calculate the offset from 1/1/1900 at timezone -1440 min
136+ let adjusted_localtime_epoc: u64 = localtime_epoch + TIMEZONE_DELTA ;
137+
138+ let epoch: u64 = if t. timezone == r_efi:: efi:: UNSPECIFIED_TIMEZONE {
139+ adjusted_localtime_epoc
140+ } else {
141+ adjusted_localtime_epoc
142+ . checked_add_signed ( ( t. timezone as i64 ) * SECS_IN_MINUTE as i64 )
143+ . unwrap ( )
144+ } ;
145+
146+ Duration :: new ( epoch, t. nanosecond )
147+ }
148+
149+ pub ( crate ) const fn to_uefi ( dur : & Duration , timezone : i16 , daylight : u8 ) -> Option < Time > {
150+ // Check timzone validity
151+ assert ! ( timezone <= 1440 ) ;
152+ assert ! ( timezone >= -1440 ) ;
153+
154+ let secs: u64 = if timezone == r_efi:: efi:: UNSPECIFIED_TIMEZONE {
155+ dur. as_secs ( )
109156 } else {
110- ( localtime_epoch as i64 + ( t. timezone as i64 ) * SECS_IN_MINUTE as i64 ) as u64
157+ // FIXME: use checked_sub_signed once stablized
158+ dur. as_secs ( ) . checked_add_signed ( ( -timezone as i64 ) * SECS_IN_MINUTE as i64 ) . unwrap ( )
111159 } ;
112160
113- Duration :: new ( utc_epoch, t. nanosecond )
161+ // Convert to UTC
162+ let Some ( secs) = secs. checked_sub ( TIMEZONE_DELTA ) else { return None } ;
163+
164+ let days = secs / SECS_IN_DAY ;
165+ let remaining_secs = secs % SECS_IN_DAY ;
166+
167+ let z = days + 693901 ;
168+ let era = z / 146097 ;
169+ let doe = z - ( era * 146097 ) ;
170+ let yoe = ( doe - doe / 1460 + doe / 36524 - doe / 146096 ) / 365 ;
171+ let mut y = yoe + era * 400 ;
172+ let doy = doe - ( 365 * yoe + yoe / 4 - yoe / 100 ) ;
173+ let mp = ( 5 * doy + 2 ) / 153 ;
174+ let d = doy - ( 153 * mp + 2 ) / 5 + 1 ;
175+ let m = if mp < 10 { mp + 3 } else { mp - 9 } ;
176+
177+ if m <= 2 {
178+ y += 1 ;
179+ }
180+
181+ let hour = ( remaining_secs / SECS_IN_HOUR ) as u8 ;
182+ let minute = ( ( remaining_secs % SECS_IN_HOUR ) / SECS_IN_MINUTE ) as u8 ;
183+ let second = ( remaining_secs % SECS_IN_MINUTE ) as u8 ;
184+
185+ // Check Bounds
186+ if y >= 1900 && y <= 9999 {
187+ Some ( Time {
188+ year : y as u16 ,
189+ month : m as u8 ,
190+ day : d as u8 ,
191+ hour,
192+ minute,
193+ second,
194+ nanosecond : dur. subsec_nanos ( ) ,
195+ timezone,
196+ daylight,
197+ pad1 : 0 ,
198+ pad2 : 0 ,
199+ } )
200+ } else {
201+ None
202+ }
114203 }
115204}
116205
0 commit comments