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