Skip to content

Commit 3b96089

Browse files
committed
uefi-raw: impl TryFrom for OffsetDateTime to Time
1 parent 9e581bb commit 3b96089

File tree

2 files changed

+178
-1
lines changed

2 files changed

+178
-1
lines changed

uefi-raw/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
`OffsetDateTime` from the [time crate](https://crates.io/crates/time).
1717
- `Time::to_offset_date_time`
1818
- `Time::to_offset_date_time_with_default_timezone`
19+
- implemented `TryFrom` from `OffsetDateTime` for `Time`
1920

2021
## Changed
2122
- Switched `*const Self` to `*mut Self` in `SerialIoProtocol::set_attributes()`

uefi-raw/src/time.rs

Lines changed: 177 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ impl error::Error for ToOffsetDateTimeError {
7272
///
7373
/// - `Time::to_offset_date_time`
7474
/// - `Time::to_offset_date_time_with_default_timezone`
75+
/// - [`TryFrom`] from `OffsetDateTime` to [`Time`]
7576
///
7677
/// [time]: https://crates.io/crates/time
7778
#[derive(Debug, Default, Copy, Clone, Eq)]
@@ -355,6 +356,83 @@ bitflags! {
355356
}
356357
}
357358

359+
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
360+
pub enum FromOffsetDateTimeError {
361+
/// The year is not representable as `u16`.
362+
InvalidYear(i32),
363+
/// Time zone offsets with seconds are not representable.
364+
OffsetWithSeconds(i8),
365+
InvalidTime(InvalidTimeError),
366+
}
367+
368+
#[cfg(feature = "time")]
369+
impl Display for FromOffsetDateTimeError {
370+
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
371+
match self {
372+
FromOffsetDateTimeError::InvalidYear(y) => {
373+
write!(
374+
f,
375+
"Cannot to convert OffsetDateTime to EFI Time: invalid year {y}"
376+
)
377+
}
378+
FromOffsetDateTimeError::OffsetWithSeconds(s) => {
379+
write!(
380+
f,
381+
"Cannot to convert OffsetDateTime to EFI Time: time zone offset has seconds ({s}) which is unsupported"
382+
)
383+
}
384+
FromOffsetDateTimeError::InvalidTime(t) => {
385+
write!(f, "The time is invalid: {t}")
386+
}
387+
}
388+
}
389+
}
390+
391+
#[cfg(feature = "time")]
392+
impl error::Error for FromOffsetDateTimeError {
393+
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
394+
match self {
395+
FromOffsetDateTimeError::InvalidYear(_) => None,
396+
FromOffsetDateTimeError::OffsetWithSeconds(_) => None,
397+
FromOffsetDateTimeError::InvalidTime(e) => Some(e),
398+
}
399+
}
400+
}
401+
402+
#[cfg(feature = "time")]
403+
impl TryFrom<OffsetDateTime> for Time {
404+
type Error = FromOffsetDateTimeError;
405+
406+
fn try_from(value: OffsetDateTime) -> Result<Self, Self::Error> {
407+
let this = Self {
408+
year: u16::try_from(value.date().year())
409+
.map_err(|_| Self::Error::InvalidYear(value.date().year()))?,
410+
// No checks needed: underlying type has repr `u8`
411+
month: value.date().month() as u8,
412+
day: value.date().day(),
413+
hour: value.time().hour(),
414+
minute: value.time().minute(),
415+
second: value.time().second(),
416+
pad1: 0,
417+
nanosecond: value.time().nanosecond(),
418+
time_zone: {
419+
let (h, m, s) = value.offset().as_hms();
420+
if s != 0 {
421+
return Err(Self::Error::OffsetWithSeconds(s));
422+
}
423+
(h as i16 * 60) + (m as i16)
424+
},
425+
daylight: Daylight::NONE,
426+
pad2: 0,
427+
};
428+
if this.is_valid() {
429+
Ok(this)
430+
} else {
431+
Err(Self::Error::InvalidTime(InvalidTimeError(this)))
432+
}
433+
}
434+
}
435+
358436
#[cfg(test)]
359437
mod tests {
360438
extern crate alloc;
@@ -388,7 +466,7 @@ mod tests {
388466
}
389467

390468
#[cfg(all(test, feature = "time"))]
391-
mod time_crate_tests {
469+
mod to_offset_date_time_tests {
392470
use super::*;
393471
use time::UtcOffset;
394472

@@ -506,3 +584,101 @@ mod time_crate_tests {
506584
assert!(t.to_offset_date_time().is_ok());
507585
}
508586
}
587+
588+
#[cfg(all(test, feature = "time"))]
589+
mod try_from_offset_datetime_tests {
590+
use super::*;
591+
use time::{Date, OffsetDateTime, UtcOffset};
592+
593+
fn make_odt(
594+
year: i32,
595+
month: u8,
596+
day: u8,
597+
hour: u8,
598+
minute: u8,
599+
second: u8,
600+
nanosecond: u32,
601+
offset_minutes: i16,
602+
) -> OffsetDateTime {
603+
let date =
604+
Date::from_calendar_date(year, time::Month::try_from(month).unwrap(), day).unwrap();
605+
let time = time::Time::from_hms_nano(hour, minute, second, nanosecond).unwrap();
606+
let offset =
607+
UtcOffset::from_hms((offset_minutes / 60) as i8, (offset_minutes % 60) as i8, 0)
608+
.unwrap();
609+
OffsetDateTime::new_in_offset(date, time, offset)
610+
}
611+
612+
#[test]
613+
fn test_happy_path() {
614+
let odt = make_odt(2024, 3, 15, 12, 30, 45, 123_456_789, 120);
615+
let t = Time::try_from(odt).unwrap();
616+
617+
assert_eq!(t.year, 2024);
618+
assert_eq!(t.month, 3);
619+
assert_eq!(t.day, 15);
620+
assert_eq!(t.hour, 12);
621+
assert_eq!(t.minute, 30);
622+
assert_eq!(t.second, 45);
623+
assert_eq!(t.nanosecond, 123_456_789);
624+
assert_eq!(t.time_zone, 120);
625+
}
626+
627+
#[test]
628+
fn test_negative_timezone() {
629+
let odt = make_odt(2024, 1, 1, 0, 0, 0, 0, -330);
630+
let t = Time::try_from(odt).unwrap();
631+
assert_eq!(t.time_zone, -330);
632+
}
633+
634+
#[test]
635+
fn test_offset_with_seconds_fails() {
636+
let date = Date::from_calendar_date(2024, time::Month::January, 1).unwrap();
637+
let time = time::Time::from_hms_nano(0, 0, 0, 0).unwrap();
638+
let offset = UtcOffset::from_hms(1, 0, 30).unwrap();
639+
let odt = OffsetDateTime::new_in_offset(date, time, offset);
640+
641+
let result = Time::try_from(odt);
642+
assert!(matches!(
643+
result,
644+
Err(FromOffsetDateTimeError::OffsetWithSeconds(30))
645+
));
646+
}
647+
648+
#[test]
649+
fn test_invalid_year_too_low() {
650+
let odt = make_odt(1800, 1, 1, 0, 0, 0, 0, 0);
651+
let result = Time::try_from(odt);
652+
assert!(matches!(
653+
result,
654+
Err(FromOffsetDateTimeError::InvalidTime(_))
655+
));
656+
}
657+
658+
#[test]
659+
fn test_boundary_years() {
660+
let min_valid = make_odt(1900, 1, 1, 0, 0, 0, 0, 0);
661+
assert!(Time::try_from(min_valid).is_ok());
662+
663+
let max_valid = make_odt(9999, 12, 31, 23, 59, 59, 999_999_999, 0);
664+
assert!(Time::try_from(max_valid).is_ok());
665+
}
666+
667+
#[test]
668+
fn test_round_trip_conversion() {
669+
let odt = make_odt(2024, 5, 6, 7, 8, 9, 987_654_321, 180);
670+
let t = Time::try_from(odt).unwrap();
671+
let odt2 = t
672+
.to_offset_date_time_with_default_timezone(UtcOffset::from_hms(3, 0, 0).unwrap())
673+
.unwrap();
674+
675+
assert_eq!(odt.year(), odt2.year());
676+
assert_eq!(odt.month(), odt2.month());
677+
assert_eq!(odt.day(), odt2.day());
678+
assert_eq!(odt.hour(), odt2.hour());
679+
assert_eq!(odt.minute(), odt2.minute());
680+
assert_eq!(odt.second(), odt2.second());
681+
assert_eq!(odt.nanosecond(), odt2.nanosecond());
682+
assert_eq!(odt.offset().whole_minutes(), odt2.offset().whole_minutes());
683+
}
684+
}

0 commit comments

Comments
 (0)