Skip to content

Commit 730cd38

Browse files
committed
uefi-raw: impl TryFrom for OffsetDateTime to Time
1 parent d0203b3 commit 730cd38

File tree

2 files changed

+179
-1
lines changed

2 files changed

+179
-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: 178 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ impl error::Error for ToOffsetDateTimeError {
7575
///
7676
/// - `Time::to_offset_date_time`
7777
/// - `Time::to_offset_date_time_with_default_timezone`
78+
/// - [`TryFrom`] from `OffsetDateTime` to [`Time`]
7879
///
7980
/// [time]: https://crates.io/crates/time
8081
#[derive(Debug, Default, Copy, Clone, Eq)]
@@ -358,6 +359,84 @@ bitflags! {
358359
}
359360
}
360361

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

393472
#[cfg(all(test, feature = "time"))]
394-
mod time_crate_tests {
473+
mod to_offset_date_time_tests {
395474
use super::*;
396475
use time::UtcOffset;
397476

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

0 commit comments

Comments
 (0)