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