@@ -5,6 +5,7 @@ use core::fmt;
55
66use crate :: Duration ;
77use crate :: Timestamp ;
8+ use crate :: TimestampError ;
89
910/// A point in time, represented as a date and time in the UTC timezone.
1011#[ derive( Debug , Default , Clone , Copy , PartialEq , Eq , PartialOrd , Ord ) ]
@@ -502,9 +503,7 @@ pub(crate) fn parse_timestamp(s: &str) -> Option<Timestamp> {
502503 ..DateTime :: default ( )
503504 } ;
504505
505- ensure ! ( date_time. is_valid( ) ) ;
506-
507- return Some ( Timestamp :: from ( date_time) ) ;
506+ return Timestamp :: try_from ( date_time) . ok ( ) ;
508507 }
509508
510509 // Accept either 'T' or ' ' as delimiter between date and time.
@@ -534,9 +533,7 @@ pub(crate) fn parse_timestamp(s: &str) -> Option<Timestamp> {
534533 nanos,
535534 } ;
536535
537- ensure ! ( date_time. is_valid( ) ) ;
538-
539- let Timestamp { seconds, nanos } = Timestamp :: from ( date_time) ;
536+ let Timestamp { seconds, nanos } = Timestamp :: try_from ( date_time) . ok ( ) ?;
540537
541538 let seconds =
542539 seconds. checked_sub ( i64:: from ( offset_hour) * 3600 + i64:: from ( offset_minute) * 60 ) ?;
@@ -575,21 +572,27 @@ pub(crate) fn parse_duration(s: &str) -> Option<Duration> {
575572 Some ( Duration { seconds, nanos } )
576573}
577574
578- impl From < DateTime > for Timestamp {
579- fn from ( date_time : DateTime ) -> Timestamp {
575+ impl TryFrom < DateTime > for Timestamp {
576+ type Error = TimestampError ;
577+
578+ fn try_from ( date_time : DateTime ) -> Result < Timestamp , TimestampError > {
579+ if !date_time. is_valid ( ) {
580+ return Err ( TimestampError :: InvalidDateTime ) ;
581+ }
580582 let seconds = date_time_to_seconds ( & date_time) ;
581583 let nanos = date_time. nanos ;
582- Timestamp {
584+ Ok ( Timestamp {
583585 seconds,
584586 nanos : nanos as i32 ,
585- }
587+ } )
586588 }
587589}
588590
589591#[ cfg( test) ]
590592mod tests {
591593 use super :: * ;
592594 use proptest:: prelude:: * ;
595+ use prost:: alloc:: format;
593596
594597 #[ test]
595598 fn test_min_max ( ) {
@@ -734,16 +737,16 @@ mod tests {
734737
735738 // Leap day
736739 assert_eq ! (
737- "2020-02-29T01:02:03.00Z" . parse:: <Timestamp >( ) . unwrap ( ) ,
738- Timestamp :: from ( DateTime {
740+ "2020-02-29T01:02:03.00Z" . parse:: <Timestamp >( ) ,
741+ Timestamp :: try_from ( DateTime {
739742 year: 2020 ,
740743 month: 2 ,
741744 day: 29 ,
742745 hour: 1 ,
743746 minute: 2 ,
744747 second: 3 ,
745748 nanos: 0 ,
746- } ) ,
749+ } )
747750 ) ;
748751
749752 // Test extensions to RFC 3339.
@@ -852,6 +855,94 @@ mod tests {
852855 assert ! ( "1️⃣s" . parse:: <Duration >( ) . is_err( ) ) ;
853856 }
854857
858+ #[ test]
859+ fn check_invalid_datetimes ( ) {
860+ assert_eq ! (
861+ Timestamp :: try_from( DateTime {
862+ year: i64 :: from_le_bytes( [ 178 , 2 , 0 , 0 , 0 , 0 , 0 , 128 ] ) ,
863+ month: 2 ,
864+ day: 2 ,
865+ hour: 8 ,
866+ minute: 58 ,
867+ second: 8 ,
868+ nanos: u32 :: from_le_bytes( [ 0 , 0 , 0 , 50 ] ) ,
869+ } ) ,
870+ Err ( TimestampError :: InvalidDateTime )
871+ ) ;
872+ assert_eq ! (
873+ Timestamp :: try_from( DateTime {
874+ year: i64 :: from_le_bytes( [ 132 , 7 , 0 , 0 , 0 , 0 , 0 , 128 ] ) ,
875+ month: 2 ,
876+ day: 2 ,
877+ hour: 8 ,
878+ minute: 58 ,
879+ second: 8 ,
880+ nanos: u32 :: from_le_bytes( [ 0 , 0 , 0 , 50 ] ) ,
881+ } ) ,
882+ Err ( TimestampError :: InvalidDateTime )
883+ ) ;
884+ assert_eq ! (
885+ Timestamp :: try_from( DateTime {
886+ year: i64 :: from_le_bytes( [ 80 , 96 , 32 , 240 , 99 , 0 , 32 , 180 ] ) ,
887+ month: 1 ,
888+ day: 18 ,
889+ hour: 19 ,
890+ minute: 26 ,
891+ second: 8 ,
892+ nanos: u32 :: from_le_bytes( [ 0 , 0 , 0 , 50 ] ) ,
893+ } ) ,
894+ Err ( TimestampError :: InvalidDateTime )
895+ ) ;
896+ assert_eq ! (
897+ Timestamp :: try_from( DateTime {
898+ year: DateTime :: MIN . year - 1 ,
899+ month: 0 ,
900+ day: 0 ,
901+ hour: 0 ,
902+ minute: 0 ,
903+ second: 0 ,
904+ nanos: 0 ,
905+ } ) ,
906+ Err ( TimestampError :: InvalidDateTime )
907+ ) ;
908+ assert_eq ! (
909+ Timestamp :: try_from( DateTime {
910+ year: i64 :: MIN ,
911+ month: 0 ,
912+ day: 0 ,
913+ hour: 0 ,
914+ minute: 0 ,
915+ second: 0 ,
916+ nanos: 0 ,
917+ } ) ,
918+ Err ( TimestampError :: InvalidDateTime )
919+ ) ;
920+ assert_eq ! (
921+ Timestamp :: try_from( DateTime {
922+ year: DateTime :: MAX . year + 1 ,
923+ month: u8 :: MAX ,
924+ day: u8 :: MAX ,
925+ hour: u8 :: MAX ,
926+ minute: u8 :: MAX ,
927+ second: u8 :: MAX ,
928+ nanos: u32 :: MAX ,
929+ } ) ,
930+ Err ( TimestampError :: InvalidDateTime )
931+ ) ;
932+ assert_eq ! (
933+ Timestamp :: try_from( DateTime {
934+ year: i64 :: MAX ,
935+ month: u8 :: MAX ,
936+ day: u8 :: MAX ,
937+ hour: u8 :: MAX ,
938+ minute: u8 :: MAX ,
939+ second: u8 :: MAX ,
940+ nanos: u32 :: MAX ,
941+ } ) ,
942+ Err ( TimestampError :: InvalidDateTime )
943+ ) ;
944+ }
945+
855946 proptest ! {
856947 #[ cfg( feature = "std" ) ]
857948 #[ test]
@@ -883,5 +974,50 @@ mod tests {
883974 "{}" , duration. to_string( )
884975 ) ;
885976 }
977+
978+ #[ test]
979+ fn check_timestamp_roundtrip_with_date_time(
980+ seconds in i64 :: arbitrary( ) ,
981+ nanos in i32 :: arbitrary( ) ,
982+ ) {
983+ let timestamp = Timestamp { seconds, nanos } ;
984+ let date_time = DateTime :: from( timestamp) ;
985+ let roundtrip = Timestamp :: try_from( date_time) . unwrap( ) ;
986+
987+ let mut normalized_timestamp = timestamp;
988+ normalized_timestamp. normalize( ) ;
989+
990+ prop_assert_eq!( normalized_timestamp, roundtrip) ;
991+ }
992+
993+ #[ test]
994+ fn check_date_time_roundtrip_with_timestamp(
995+ year in i64 :: arbitrary( ) ,
996+ month in u8 :: arbitrary( ) ,
997+ day in u8 :: arbitrary( ) ,
998+ hour in u8 :: arbitrary( ) ,
999+ minute in u8 :: arbitrary( ) ,
1000+ second in u8 :: arbitrary( ) ,
1001+ nanos in u32 :: arbitrary( ) ,
1002+ ) {
1003+ let date_time = DateTime {
1004+ year,
1005+ month,
1006+ day,
1007+ hour,
1008+ minute,
1009+ second,
1010+ nanos
1011+ } ;
1012+
1013+ if date_time. is_valid( ) {
1014+ let timestamp = Timestamp :: try_from( date_time) . unwrap( ) ;
1015+ let roundtrip = DateTime :: from( timestamp) ;
1016+
1017+ prop_assert_eq!( date_time, roundtrip) ;
1018+ } else {
1019+ prop_assert_eq!( Timestamp :: try_from( date_time) , Err ( TimestampError :: InvalidDateTime ) ) ;
1020+ }
1021+ }
8861022 }
8871023}
0 commit comments