@@ -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,44 @@ 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+ }
366+
367+ #[ cfg( feature = "time" ) ]
368+ impl TryFrom < OffsetDateTime > for Time {
369+ type Error = FromOffsetDateTimeError ;
370+
371+ fn try_from ( value : OffsetDateTime ) -> Result < Self , Self :: Error > {
372+ let this = Self {
373+ year : u16:: try_from ( value. date ( ) . year ( ) )
374+ . map_err ( |_| Self :: Error :: InvalidYear ( value. date ( ) . year ( ) ) ) ?,
375+ // No checks needed: underlying type has repr `u8`
376+ month : value. date ( ) . month ( ) as u8 ,
377+ day : value. date ( ) . day ( ) ,
378+ hour : value. time ( ) . hour ( ) ,
379+ minute : value. time ( ) . minute ( ) ,
380+ second : value. time ( ) . second ( ) ,
381+ pad1 : 0 ,
382+ nanosecond : value. time ( ) . nanosecond ( ) ,
383+ time_zone : {
384+ let ( h, m, s) = value. offset ( ) . as_hms ( ) ;
385+ if s != 0 {
386+ return Err ( Self :: Error :: OffsetWithSeconds ( s) ) ;
387+ }
388+ ( h as i16 * 60 ) + ( m as i16 )
389+ } ,
390+ daylight : Daylight :: NONE ,
391+ pad2 : 0 ,
392+ } ;
393+ Ok ( this)
394+ }
395+ }
396+
358397#[ cfg( test) ]
359398mod tests {
360399 extern crate alloc;
@@ -388,9 +427,9 @@ mod tests {
388427}
389428
390429#[ cfg( all( test, feature = "time" ) ) ]
391- mod time_crate_tests {
430+ mod to_offset_date_time_tests {
392431 use super :: * ;
393- use time:: { Date , Month , OffsetDateTime , Time as TimeOfDay , UtcOffset } ;
432+ use time:: { Date , Month , OffsetDateTime , UtcOffset } ;
394433
395434 /// A valid base time for testing
396435 fn valid_base_time ( ) -> Time {
@@ -416,7 +455,7 @@ mod time_crate_tests {
416455
417456 let expected = OffsetDateTime :: new_in_offset (
418457 Date :: from_calendar_date ( 2024 , Month :: March , 15 ) . unwrap ( ) ,
419- TimeOfDay :: from_hms_nano ( 12 , 34 , 56 , 123_456_789 ) . unwrap ( ) ,
458+ time :: Time :: from_hms_nano ( 12 , 34 , 56 , 123_456_789 ) . unwrap ( ) ,
420459 UtcOffset :: UTC ,
421460 ) ;
422461
@@ -432,7 +471,7 @@ mod time_crate_tests {
432471
433472 let expected = OffsetDateTime :: new_in_offset (
434473 Date :: from_calendar_date ( 2024 , Month :: March , 15 ) . unwrap ( ) ,
435- TimeOfDay :: from_hms_nano ( 12 , 34 , 56 , 123_456_789 ) . unwrap ( ) ,
474+ time :: Time :: from_hms_nano ( 12 , 34 , 56 , 123_456_789 ) . unwrap ( ) ,
436475 offset,
437476 ) ;
438477
@@ -603,3 +642,105 @@ mod time_crate_tests {
603642 assert_eq ! ( odt. offset( ) , offset) ;
604643 }
605644}
645+
646+ #[ cfg( all( test, feature = "time" ) ) ]
647+ mod from_offset_datetime_tests {
648+ use super :: * ;
649+ use time:: { Date , Month , OffsetDateTime , Time as TimeOfDay , UtcOffset } ;
650+
651+ #[ test]
652+ fn test_normal_conversion_utc ( ) {
653+ let odt = OffsetDateTime :: new_in_offset (
654+ Date :: from_calendar_date ( 2024 , Month :: March , 15 ) . unwrap ( ) ,
655+ TimeOfDay :: from_hms_nano ( 12 , 34 , 56 , 123_456_789 ) . unwrap ( ) ,
656+ UtcOffset :: UTC ,
657+ ) ;
658+
659+ let t = Time :: try_from ( odt) . unwrap ( ) ;
660+ assert_eq ! ( t. year, 2024 ) ;
661+ assert_eq ! ( t. month, 3 ) ;
662+ assert_eq ! ( t. day, 15 ) ;
663+ assert_eq ! ( t. hour, 12 ) ;
664+ assert_eq ! ( t. minute, 34 ) ;
665+ assert_eq ! ( t. second, 56 ) ;
666+ assert_eq ! ( t. nanosecond, 123_456_789 ) ;
667+ assert_eq ! ( t. time_zone, 0 ) ;
668+ }
669+
670+ #[ test]
671+ fn test_conversion_positive_offset ( ) {
672+ let offset = UtcOffset :: from_hms ( 1 , 30 , 0 ) . unwrap ( ) ; // +1:30
673+ let odt = OffsetDateTime :: new_in_offset (
674+ Date :: from_calendar_date ( 2024 , Month :: March , 15 ) . unwrap ( ) ,
675+ TimeOfDay :: from_hms ( 8 , 0 , 0 ) . unwrap ( ) ,
676+ offset,
677+ ) ;
678+
679+ let t = Time :: try_from ( odt) . unwrap ( ) ;
680+ assert_eq ! ( t. time_zone, 90 ) ; // 1*60 + 30
681+ }
682+
683+ #[ test]
684+ fn test_conversion_negative_offset ( ) {
685+ let offset = UtcOffset :: from_hms ( -2 , -15 , 0 ) . unwrap ( ) ; // -2:15
686+ let odt = OffsetDateTime :: new_in_offset (
687+ Date :: from_calendar_date ( 2024 , Month :: March , 15 ) . unwrap ( ) ,
688+ TimeOfDay :: from_hms ( 20 , 0 , 0 ) . unwrap ( ) ,
689+ offset,
690+ ) ;
691+
692+ let t = Time :: try_from ( odt) . unwrap ( ) ;
693+ assert_eq ! ( t. time_zone, -135 ) ; // -2*60 + -15
694+ }
695+
696+ #[ test]
697+ fn test_offset_with_seconds_should_fail ( ) {
698+ let offset = UtcOffset :: from_hms ( 0 , 0 , 1 ) . unwrap ( ) ; // +0:0:1
699+ let odt = OffsetDateTime :: new_in_offset (
700+ Date :: from_calendar_date ( 2024 , Month :: March , 15 ) . unwrap ( ) ,
701+ TimeOfDay :: from_hms ( 12 , 0 , 0 ) . unwrap ( ) ,
702+ offset,
703+ ) ;
704+
705+ let err = Time :: try_from ( odt) . unwrap_err ( ) ;
706+ assert ! ( matches!( err, FromOffsetDateTimeError :: OffsetWithSeconds ( 1 ) ) ) ;
707+ }
708+
709+ #[ test]
710+ fn test_invalid_year_negative ( ) {
711+ let odt = OffsetDateTime :: new_in_offset (
712+ Date :: from_calendar_date ( -1 , Month :: January , 1 ) . unwrap ( ) ,
713+ TimeOfDay :: from_hms ( 0 , 0 , 0 ) . unwrap ( ) ,
714+ UtcOffset :: UTC ,
715+ ) ;
716+
717+ let err = Time :: try_from ( odt) . unwrap_err ( ) ;
718+ assert ! ( matches!( err, FromOffsetDateTimeError :: InvalidYear ( -1 ) ) ) ;
719+ }
720+
721+ #[ test]
722+ fn test_maximum_supported_year ( ) {
723+ let odt = OffsetDateTime :: new_in_offset (
724+ Date :: from_calendar_date ( 9999 , Month :: December , 31 ) . unwrap ( ) ,
725+ TimeOfDay :: from_hms ( 23 , 59 , 59 ) . unwrap ( ) ,
726+ UtcOffset :: UTC ,
727+ ) ;
728+
729+ let t = Time :: try_from ( odt) . unwrap ( ) ;
730+ assert_eq ! ( t. year, 9999 ) ;
731+ assert_eq ! ( t. month, 12 ) ;
732+ assert_eq ! ( t. day, 31 ) ;
733+ }
734+
735+ #[ test]
736+ fn test_nanosecond_boundary ( ) {
737+ let odt = OffsetDateTime :: new_in_offset (
738+ Date :: from_calendar_date ( 2024 , Month :: March , 15 ) . unwrap ( ) ,
739+ TimeOfDay :: from_hms_nano ( 12 , 0 , 0 , 999_999_999 ) . unwrap ( ) ,
740+ UtcOffset :: UTC ,
741+ ) ;
742+
743+ let t = Time :: try_from ( odt) . unwrap ( ) ;
744+ assert_eq ! ( t. nanosecond, 999_999_999 ) ;
745+ }
746+ }
0 commit comments