@@ -540,6 +540,8 @@ impl From<jiff::Error> for PyErr {
540
540
#[ cfg( test) ]
541
541
mod tests {
542
542
use super :: * ;
543
+ #[ cfg( not( Py_LIMITED_API ) ) ]
544
+ use crate :: types:: timezone_utc;
543
545
use crate :: { types:: PyTuple , BoundObject } ;
544
546
use jiff:: tz:: Offset ;
545
547
use std:: cmp:: Ordering ;
@@ -562,7 +564,7 @@ mod tests {
562
564
Some ( & locals) ,
563
565
)
564
566
. unwrap ( ) ;
565
- let result: PyResult < FixedOffset > = locals. get_item ( "zi" ) . unwrap ( ) . unwrap ( ) . extract ( ) ;
567
+ let result: PyResult < Offset > = locals. get_item ( "zi" ) . unwrap ( ) . unwrap ( ) . extract ( ) ;
566
568
assert ! ( result. is_err( ) ) ;
567
569
let res = result. err ( ) . unwrap ( ) ;
568
570
// Also check the error message is what we expect
@@ -601,6 +603,40 @@ mod tests {
601
603
} ) ;
602
604
}
603
605
606
+ #[ test]
607
+ fn test_invalid_types_fail ( ) {
608
+ Python :: with_gil ( |py| {
609
+ let none = py. None ( ) . into_bound ( py) ;
610
+ assert_eq ! (
611
+ none. extract:: <Span >( ) . unwrap_err( ) . to_string( ) ,
612
+ "TypeError: 'NoneType' object cannot be converted to 'PyDelta'"
613
+ ) ;
614
+ assert_eq ! (
615
+ none. extract:: <Offset >( ) . unwrap_err( ) . to_string( ) ,
616
+ "TypeError: 'NoneType' object cannot be converted to 'PyTzInfo'"
617
+ ) ;
618
+ assert_eq ! (
619
+ none. extract:: <TimeZone >( ) . unwrap_err( ) . to_string( ) ,
620
+ "TypeError: 'NoneType' object cannot be converted to 'PyTzInfo'"
621
+ ) ;
622
+ assert_eq ! (
623
+ none. extract:: <Time >( ) . unwrap_err( ) . to_string( ) ,
624
+ "TypeError: 'NoneType' object cannot be converted to 'PyTime'"
625
+ ) ;
626
+ assert_eq ! (
627
+ none. extract:: <Date >( ) . unwrap_err( ) . to_string( ) ,
628
+ "TypeError: 'NoneType' object cannot be converted to 'PyDate'"
629
+ ) ;
630
+ assert_eq ! (
631
+ none. extract:: <DateTime >( ) . unwrap_err( ) . to_string( ) ,
632
+ "TypeError: 'NoneType' object cannot be converted to 'PyDateTime'"
633
+ ) ;
634
+ assert_eq ! (
635
+ none. extract:: <Zoned >( ) . unwrap_err( ) . to_string( ) ,
636
+ "TypeError: 'NoneType' object cannot be converted to 'PyDateTime'"
637
+ ) ;
638
+ } ) ;
639
+ }
604
640
605
641
#[ test]
606
642
fn test_pyo3_date_into_pyobject ( ) {
@@ -722,11 +758,9 @@ mod tests {
722
758
#[ cfg( all( Py_3_9 , not( windows) ) ) ]
723
759
fn test_pyo3_datetime_into_pyobject_tz ( ) {
724
760
Python :: with_gil ( |py| {
725
- let datetime = NaiveDate :: from_ymd_opt ( 2024 , 12 , 11 )
726
- . unwrap ( )
727
- . and_hms_opt ( 23 , 3 , 13 )
761
+ let datetime = DateTime :: new ( 2024 , 12 , 11 , 23 , 3 , 13 , 0 )
728
762
. unwrap ( )
729
- . and_local_timezone ( chrono_tz :: Tz :: Europe__London )
763
+ . to_zoned ( TimeZone :: get ( "Europe/London" ) . unwrap ( ) )
730
764
. unwrap ( ) ;
731
765
let datetime = datetime. into_pyobject ( py) . unwrap ( ) ;
732
766
let py_datetime = new_py_datetime_ob (
@@ -747,6 +781,30 @@ mod tests {
747
781
} )
748
782
}
749
783
784
+ #[ test]
785
+ fn test_pyo3_datetime_frompyobject_utc ( ) {
786
+ Python :: with_gil ( |py| {
787
+ let year = 2014 ;
788
+ let month = 5 ;
789
+ let day = 6 ;
790
+ let hour = 7 ;
791
+ let minute = 8 ;
792
+ let second = 9 ;
793
+ let micro = 999_999 ;
794
+ let tz_utc = timezone_utc ( py) ;
795
+ let py_datetime = new_py_datetime_ob (
796
+ py,
797
+ "datetime" ,
798
+ ( year, month, day, hour, minute, second, micro, tz_utc) ,
799
+ ) ;
800
+ let py_datetime: Zoned = py_datetime. extract ( ) . unwrap ( ) ;
801
+ let datetime = DateTime :: new ( year, month, day, hour, minute, second, micro * 1000 )
802
+ . unwrap ( )
803
+ . to_zoned ( TimeZone :: UTC )
804
+ . unwrap ( ) ;
805
+ assert_eq ! ( py_datetime, datetime, ) ;
806
+ } )
807
+ }
750
808
751
809
#[ test]
752
810
fn test_pyo3_datetime_frompyobject_fixed_offset ( ) {
@@ -774,6 +832,103 @@ mod tests {
774
832
} )
775
833
}
776
834
835
+ #[ test]
836
+ fn test_pyo3_offset_fixed_into_pyobject ( ) {
837
+ Python :: with_gil ( |py| {
838
+ // Chrono offset
839
+ let offset = Offset :: from_seconds ( 3600 )
840
+ . unwrap ( )
841
+ . into_pyobject ( py)
842
+ . unwrap ( ) ;
843
+ // Python timezone from timedelta
844
+ let td = new_py_datetime_ob ( py, "timedelta" , ( 0 , 3600 , 0 ) ) ;
845
+ let py_timedelta = new_py_datetime_ob ( py, "timezone" , ( td, ) ) ;
846
+ // Should be equal
847
+ assert ! ( offset. eq( py_timedelta) . unwrap( ) ) ;
848
+
849
+ // Same but with negative values
850
+ let offset = Offset :: from_seconds ( -3600 )
851
+ . unwrap ( )
852
+ . into_pyobject ( py)
853
+ . unwrap ( ) ;
854
+ let td = new_py_datetime_ob ( py, "timedelta" , ( 0 , -3600 , 0 ) ) ;
855
+ let py_timedelta = new_py_datetime_ob ( py, "timezone" , ( td, ) ) ;
856
+ assert ! ( offset. eq( py_timedelta) . unwrap( ) ) ;
857
+ } )
858
+ }
859
+
860
+ #[ test]
861
+ fn test_pyo3_offset_fixed_frompyobject ( ) {
862
+ Python :: with_gil ( |py| {
863
+ let py_timedelta = new_py_datetime_ob ( py, "timedelta" , ( 0 , 3600 , 0 ) ) ;
864
+ let py_tzinfo = new_py_datetime_ob ( py, "timezone" , ( py_timedelta, ) ) ;
865
+ let offset: Offset = py_tzinfo. extract ( ) . unwrap ( ) ;
866
+ assert_eq ! ( Offset :: from_seconds( 3600 ) . unwrap( ) , offset) ;
867
+ } )
868
+ }
869
+
870
+ #[ test]
871
+ fn test_pyo3_offset_utc_into_pyobject ( ) {
872
+ Python :: with_gil ( |py| {
873
+ let utc = Offset :: UTC . into_pyobject ( py) . unwrap ( ) ;
874
+ let py_utc = python_utc ( py) ;
875
+ assert ! ( utc. is( & py_utc) ) ;
876
+ } )
877
+ }
878
+
879
+ #[ test]
880
+ fn test_pyo3_offset_utc_frompyobject ( ) {
881
+ Python :: with_gil ( |py| {
882
+ let py_utc = python_utc ( py) ;
883
+ let py_utc: Offset = py_utc. extract ( ) . unwrap ( ) ;
884
+ assert_eq ! ( Offset :: UTC , py_utc) ;
885
+
886
+ let py_timedelta = new_py_datetime_ob ( py, "timedelta" , ( 0 , 0 , 0 ) ) ;
887
+ let py_timezone_utc = new_py_datetime_ob ( py, "timezone" , ( py_timedelta, ) ) ;
888
+ let py_timezone_utc: Offset = py_timezone_utc. extract ( ) . unwrap ( ) ;
889
+ assert_eq ! ( Offset :: UTC , py_timezone_utc) ;
890
+
891
+ let py_timedelta = new_py_datetime_ob ( py, "timedelta" , ( 0 , 3600 , 0 ) ) ;
892
+ let py_timezone = new_py_datetime_ob ( py, "timezone" , ( py_timedelta, ) ) ;
893
+ assert_ne ! ( Offset :: UTC , py_timezone. extract:: <Offset >( ) . unwrap( ) ) ;
894
+ } )
895
+ }
896
+
897
+ #[ test]
898
+ fn test_pyo3_time_into_pyobject ( ) {
899
+ Python :: with_gil ( |py| {
900
+ let check_time = |name : & ' static str , hour, minute, second, ms, py_ms| {
901
+ let time = Time :: new ( hour, minute, second, ms * 1000 )
902
+ . unwrap ( )
903
+ . into_pyobject ( py)
904
+ . unwrap ( ) ;
905
+ let py_time = new_py_datetime_ob ( py, "time" , ( hour, minute, second, py_ms) ) ;
906
+ assert ! (
907
+ time. eq( & py_time) . unwrap( ) ,
908
+ "{}: {} != {}" ,
909
+ name,
910
+ time,
911
+ py_time
912
+ ) ;
913
+ } ;
914
+
915
+ check_time ( "regular" , 3 , 5 , 7 , 999_999 , 999_999 ) ;
916
+ } )
917
+ }
918
+
919
+ #[ test]
920
+ fn test_pyo3_time_frompyobject ( ) {
921
+ let hour = 3 ;
922
+ let minute = 5 ;
923
+ let second = 7 ;
924
+ let micro = 999_999 ;
925
+ Python :: with_gil ( |py| {
926
+ let py_time = new_py_datetime_ob ( py, "time" , ( hour, minute, second, micro) ) ;
927
+ let py_time: Time = py_time. extract ( ) . unwrap ( ) ;
928
+ let time = Time :: new ( hour, minute, second, micro * 1000 ) . unwrap ( ) ;
929
+ assert_eq ! ( py_time, time) ;
930
+ } )
931
+ }
777
932
778
933
fn new_py_datetime_ob < ' py , A > ( py : Python < ' py > , name : & str , args : A ) -> Bound < ' py , PyAny >
779
934
where
0 commit comments