1
1
use crate :: time:: Duration ;
2
2
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
-
7
3
#[ derive( Copy , Clone , PartialEq , Eq , PartialOrd , Ord , Debug , Hash ) ]
8
4
pub struct Instant ( Duration ) ;
9
5
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
10
10
#[ derive( Copy , Clone , PartialEq , Eq , PartialOrd , Ord , Debug , Hash ) ]
11
11
pub struct SystemTime ( Duration ) ;
12
12
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
+ } ) ;
14
40
15
41
impl Instant {
16
42
pub fn now ( ) -> Instant {
@@ -40,6 +66,15 @@ impl Instant {
40
66
}
41
67
42
68
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
+
43
78
pub fn now ( ) -> SystemTime {
44
79
system_time_internal:: now ( )
45
80
. unwrap_or_else ( || panic ! ( "time not implemented on this platform" ) )
@@ -50,11 +85,14 @@ impl SystemTime {
50
85
}
51
86
52
87
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 }
54
92
}
55
93
56
94
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 )
58
96
}
59
97
}
60
98
@@ -66,51 +104,132 @@ pub(crate) mod system_time_internal {
66
104
use crate :: mem:: MaybeUninit ;
67
105
use crate :: ptr:: NonNull ;
68
106
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
+
69
112
pub fn now ( ) -> Option < SystemTime > {
70
113
let runtime_services: NonNull < RuntimeServices > = helpers:: runtime_services ( ) ?;
71
114
let mut t: MaybeUninit < Time > = MaybeUninit :: uninit ( ) ;
72
115
let r = unsafe {
73
116
( ( * runtime_services. as_ptr ( ) ) . get_time ) ( t. as_mut_ptr ( ) , crate :: ptr:: null_mut ( ) )
74
117
} ;
75
-
76
118
if r. is_error ( ) {
77
119
return None ;
78
120
}
79
121
80
122
let t = unsafe { t. assume_init ( ) } ;
81
123
82
- Some ( SystemTime ( uefi_time_to_duration ( t ) ) )
124
+ Some ( SystemTime :: from_uefi ( t ) )
83
125
}
84
126
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
+ ) ;
90
146
91
147
const YEAR_BASE : u32 = 4800 ; /* Before min year, multiple of 400. */
92
148
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.
94
151
// Use 1 March as the start
95
152
let ( m_adj, overflow) : ( u32 , bool ) = ( t. month as u32 ) . overflowing_sub ( 3 ) ;
96
153
let ( carry, adjust) : ( u32 , u32 ) = if overflow { ( 1 , 12 ) } else { ( 0 , 0 ) } ;
97
154
let y_adj: u32 = ( t. year as u32 ) + YEAR_BASE - carry;
98
155
let month_days: u32 = ( m_adj. wrapping_add ( adjust) * 62719 + 769 ) / 2048 ;
99
156
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 ;
101
158
102
159
let localtime_epoch: u64 = ( days as u64 ) * SECS_IN_DAY
103
160
+ ( t. second as u64 )
104
161
+ ( t. minute as u64 ) * SECS_IN_MINUTE
105
162
+ ( t. hour as u64 ) * SECS_IN_HOUR ;
106
163
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
109
169
} 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 ( )
111
173
} ;
112
174
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
+ }
114
233
}
115
234
}
116
235
0 commit comments