@@ -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) ]
359437mod 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