| use r_efi::efi::{RuntimeServices, Time}; | |
| use super::helpers; | |
| use crate::mem::MaybeUninit; | |
| use crate::ptr::NonNull; | |
| use crate::time::Duration; | |
| const SECS_IN_MINUTE: u64 = 60; | |
| const SECS_IN_HOUR: u64 = SECS_IN_MINUTE * 60; | |
| const SECS_IN_DAY: u64 = SECS_IN_HOUR * 24; | |
| const SYSTEMTIME_TIMEZONE: i64 = -1440 * SECS_IN_MINUTE as i64; | |
| pub(crate) fn now() -> Time { | |
| let runtime_services: NonNull<RuntimeServices> = | |
| helpers::runtime_services().expect("Runtime services are not available"); | |
| let mut t: MaybeUninit<Time> = MaybeUninit::uninit(); | |
| let r = | |
| unsafe { ((*runtime_services.as_ptr()).get_time)(t.as_mut_ptr(), crate::ptr::null_mut()) }; | |
| if r.is_error() { | |
| panic!("time not implemented on this platform"); | |
| } | |
| unsafe { t.assume_init() } | |
| } | |
| /// 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) -> Option<Duration> { | |
| if !(t.month <= 12 | |
| && t.month != 0 | |
| && t.year >= 1900 | |
| && t.year <= 9999 | |
| && t.day <= 31 | |
| && t.day != 0 | |
| && t.second < 60 | |
| && t.minute <= 60 | |
| && t.hour < 24 | |
| && t.nanosecond < 1_000_000_000 | |
| && ((t.timezone <= 1440 && t.timezone >= -1440) | |
| || t.timezone == r_efi::efi::UNSPECIFIED_TIMEZONE)) | |
| { | |
| return None; | |
| } | |
| const YEAR_BASE: u32 = 4800; /* Before min year, multiple of 400. */ | |
| // 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) }; | |
| let y_adj: u32 = (t.year as u32) + YEAR_BASE - carry; | |
| let month_days: u32 = (m_adj.wrapping_add(adjust) * 62719 + 769) / 2048; | |
| let leap_days: u32 = y_adj / 4 - y_adj / 100 + y_adj / 400; | |
| let days: u32 = y_adj * 365 + leap_days + month_days + (t.day as u32 - 1) - 2447065; | |
| let localtime_epoch: u64 = (days as u64) * SECS_IN_DAY | |
| + (t.second as u64) | |
| + (t.minute as u64) * SECS_IN_MINUTE | |
| + (t.hour as u64) * SECS_IN_HOUR; | |
| let normalized_timezone = if t.timezone == r_efi::efi::UNSPECIFIED_TIMEZONE { | |
| -SYSTEMTIME_TIMEZONE | |
| } else { | |
| (t.timezone as i64) * SECS_IN_MINUTE as i64 - SYSTEMTIME_TIMEZONE | |
| }; | |
| // Calculate the offset from 1/1/1900 at timezone -1440 min | |
| let epoch = localtime_epoch.checked_add_signed(normalized_timezone).unwrap(); | |
| Some(Duration::new(epoch, t.nanosecond)) | |
| } | |
| /// This algorithm is a modified 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) -> Result<Time, i16> { | |
| const MIN_IN_HOUR: u64 = 60; | |
| const MIN_IN_DAY: u64 = MIN_IN_HOUR * 24; | |
| // Check timezone validity | |
| assert!(timezone <= 1440 && timezone >= -1440); | |
| // Convert to seconds since 1900-01-01-00:00:00 in timezone. | |
| let Some(secs) = dur | |
| .as_secs() | |
| .checked_add_signed(SYSTEMTIME_TIMEZONE - (timezone as i64 * SECS_IN_MINUTE as i64)) | |
| else { | |
| // If the current timezone cannot be used, find the closest timezone that will allow the | |
| // conversion to succeed. | |
| let new_tz = (dur.as_secs() / SECS_IN_MINUTE) as i16 | |
| + (SYSTEMTIME_TIMEZONE / SECS_IN_MINUTE as i64) as i16; | |
| return Err(new_tz); | |
| }; | |
| let days = secs / SECS_IN_DAY; | |
| let remaining_secs = secs % SECS_IN_DAY; | |
| let z = days + 693901; | |
| let era = z / 146097; | |
| let doe = z - (era * 146097); | |
| let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365; | |
| let mut y = yoe + era * 400; | |
| let doy = doe - (365 * yoe + yoe / 4 - yoe / 100); | |
| let mp = (5 * doy + 2) / 153; | |
| let d = doy - (153 * mp + 2) / 5 + 1; | |
| let m = if mp < 10 { mp + 3 } else { mp - 9 }; | |
| if m <= 2 { | |
| y += 1; | |
| } | |
| let hour = (remaining_secs / SECS_IN_HOUR) as u8; | |
| let minute = ((remaining_secs % SECS_IN_HOUR) / SECS_IN_MINUTE) as u8; | |
| let second = (remaining_secs % SECS_IN_MINUTE) as u8; | |
| // At this point, invalid time will be greater than MAX representable time. It cannot be less | |
| // than minimum time since we already take care of that case above. | |
| if y <= 9999 { | |
| Ok(Time { | |
| year: y as u16, | |
| month: m as u8, | |
| day: d as u8, | |
| hour, | |
| minute, | |
| second, | |
| nanosecond: dur.subsec_nanos(), | |
| timezone, | |
| daylight, | |
| pad1: 0, | |
| pad2: 0, | |
| }) | |
| } else { | |
| assert!(y == 10000); | |
| assert!(m == 1); | |
| let delta = ((d - 1) as u64 * MIN_IN_DAY | |
| + hour as u64 * MIN_IN_HOUR | |
| + minute as u64 | |
| + if second == 0 { 0 } else { 1 }) as i16; | |
| let new_tz = timezone + delta; | |
| assert!(new_tz <= 1440 && new_tz >= -1440); | |
| Err(new_tz) | |
| } | |
| } |