use super::Time;
use super::integration_common::{ConversionErrorInner, TimeConversionError};
use jiff::Zoned;
use jiff::civil::DateTime;
use jiff::tz::{Offset, TimeZone};
use uefi::runtime::TimeParams;
impl TryFrom<Time> for DateTime {
type Error = TimeConversionError;
fn try_from(value: Time) -> Result<Self, Self::Error> {
if let Err(e) = value.is_valid() {
return Err(TimeConversionError(ConversionErrorInner::InvalidUefiTime(
e,
)));
}
let datetime = Self::new(
i16::try_from(value.0.year).unwrap(),
i8::try_from(value.0.month).unwrap(),
i8::try_from(value.0.day).unwrap(),
i8::try_from(value.0.hour).unwrap(),
i8::try_from(value.0.minute).unwrap(),
i8::try_from(value.0.second).unwrap(),
i32::try_from(value.0.nanosecond).unwrap(),
)
.map_err(|e| TimeConversionError(ConversionErrorInner::JiffCrateError(e)))?;
Ok(datetime)
}
}
impl TryFrom<Time> for Zoned {
type Error = TimeConversionError;
fn try_from(value: Time) -> Result<Self, Self::Error> {
if let Err(e) = value.is_valid() {
return Err(TimeConversionError(ConversionErrorInner::InvalidUefiTime(
e,
)));
}
if value.0.time_zone == Time::UNSPECIFIED_TIMEZONE {
return Err(TimeConversionError(
ConversionErrorInner::UnspecifiedTimezone,
));
}
let datetime = DateTime::try_from(value)?;
let seconds = value.0.time_zone as i32 * 60 ;
let offset = Offset::from_seconds(seconds)
.map_err(|e| TimeConversionError(ConversionErrorInner::JiffCrateError(e)))?;
let timezone = TimeZone::fixed(offset);
let zoned = datetime
.to_zoned(timezone)
.map_err(|e| TimeConversionError(ConversionErrorInner::JiffCrateError(e)))?;
Ok(zoned)
}
}
impl TryFrom<DateTime> for Time {
type Error = TimeConversionError;
fn try_from(value: DateTime) -> Result<Self, Self::Error> {
let params = TimeParams {
year: u16::try_from(value.year())
.map_err(|_e| TimeConversionError(ConversionErrorInner::InvalidComponent))?,
month: u8::try_from(value.month())
.map_err(|_e| TimeConversionError(ConversionErrorInner::InvalidComponent))?,
day: u8::try_from(value.day())
.map_err(|_e| TimeConversionError(ConversionErrorInner::InvalidComponent))?,
hour: u8::try_from(value.hour())
.map_err(|_e| TimeConversionError(ConversionErrorInner::InvalidComponent))?,
minute: u8::try_from(value.minute())
.map_err(|_e| TimeConversionError(ConversionErrorInner::InvalidComponent))?,
second: u8::try_from(value.second())
.map_err(|_e| TimeConversionError(ConversionErrorInner::InvalidComponent))?,
nanosecond: u32::try_from(value.subsec_nanosecond())
.map_err(|_e| TimeConversionError(ConversionErrorInner::InvalidComponent))?,
time_zone: None,
daylight: Default::default(),
};
Self::new(params).map_err(|e| TimeConversionError(ConversionErrorInner::InvalidUefiTime(e)))
}
}
impl TryFrom<Zoned> for Time {
type Error = TimeConversionError;
fn try_from(value: Zoned) -> Result<Self, Self::Error> {
let params = TimeParams {
year: u16::try_from(value.year())
.map_err(|_e| TimeConversionError(ConversionErrorInner::InvalidComponent))?,
month: u8::try_from(value.month())
.map_err(|_e| TimeConversionError(ConversionErrorInner::InvalidComponent))?,
day: u8::try_from(value.day())
.map_err(|_e| TimeConversionError(ConversionErrorInner::InvalidComponent))?,
hour: u8::try_from(value.hour())
.map_err(|_e| TimeConversionError(ConversionErrorInner::InvalidComponent))?,
minute: u8::try_from(value.minute())
.map_err(|_e| TimeConversionError(ConversionErrorInner::InvalidComponent))?,
second: u8::try_from(value.second())
.map_err(|_e| TimeConversionError(ConversionErrorInner::InvalidComponent))?,
nanosecond: u32::try_from(value.subsec_nanosecond())
.map_err(|_e| TimeConversionError(ConversionErrorInner::InvalidComponent))?,
time_zone: Some(
i16::try_from(value.offset().seconds() / 60 )
.map_err(|_e| TimeConversionError(ConversionErrorInner::InvalidComponent))?,
),
daylight: Default::default(),
};
Self::new(params).map_err(|e| TimeConversionError(ConversionErrorInner::InvalidUefiTime(e)))
}
}
#[cfg(test)]
mod tests {
use super::super::integration_common::test_helpers;
use super::*;
#[test]
fn primitive_roundtrip_basic() {
test_helpers::primitive_roundtrip::<DateTime>();
}
#[test]
fn zoned_roundtrip_positive_offset() {
test_helpers::zoned_roundtrip::<Zoned>();
}
#[test]
fn zoned_roundtrip_negative_offset() {
test_helpers::negative_offset_roundtrip::<Zoned>();
}
#[test]
fn nanoseconds_preserved() {
test_helpers::preserves_nanoseconds::<Zoned>();
}
#[test]
fn unspecified_timezone_is_rejected() {
test_helpers::unspecified_timezone_fails::<Zoned>();
}
#[test]
fn invalid_date_is_rejected() {
test_helpers::invalid_calendar_date_fails::<DateTime>();
}
#[test]
fn half_hour_timezone_roundtrip() {
let t = test_helpers::sample_time(90);
let z: Zoned = t.try_into().unwrap();
let back: Time = z.try_into().unwrap();
assert_eq!(back.0.time_zone, 90);
}
#[test]
fn negative_half_hour_timezone_roundtrip() {
let t = test_helpers::sample_time(-330);
let z: Zoned = t.try_into().unwrap();
let back: Time = z.try_into().unwrap();
assert_eq!(back.0.time_zone, -330);
}
}