blob: 557a49b27c2d10e691c48c6888be9ff50c23428a [file] [log] [blame] [edit]
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)
}
}