@@ -934,26 +934,45 @@ fn python_value_to_scalar_value(value: &PyObject, py: Python) -> PyDataFusionRes
934
934
935
935
if let Ok ( datetime_cls) = datetime_result {
936
936
if let Ok ( true ) = value. is_instance ( datetime_cls) {
937
- if value. is_instance_of :: < pyo3:: types:: PyDateTime > ( py) {
938
- if let Ok ( naive_dt) = value. extract :: < chrono:: NaiveDateTime > ( py) {
939
- return Ok ( ScalarValue :: TimestampNanosecond (
940
- Some ( naive_dt. timestamp_nanos ( ) ) ,
941
- None ,
942
- ) ) ;
937
+ if let Ok ( dt) = value. cast_as :: < pyo3:: types:: PyDateTime > ( py) {
938
+ // Convert Python datetime to timestamp in nanoseconds
939
+ let year = dt. get_year ( ) as i32 ;
940
+ let month = dt. get_month ( ) as u8 ;
941
+ let day = dt. get_day ( ) as u8 ;
942
+ let hour = dt. get_hour ( ) as u8 ;
943
+ let minute = dt. get_minute ( ) as u8 ;
944
+ let second = dt. get_second ( ) as u8 ;
945
+ let micro = dt. get_microsecond ( ) as u32 ;
946
+
947
+ // Use DataFusion's timestamp conversion logic
948
+ if let Ok ( ts) =
949
+ date_to_timestamp ( year, month, day, hour, minute, second, micro * 1000 )
950
+ {
951
+ return Ok ( ScalarValue :: TimestampNanosecond ( Some ( ts) , None ) ) ;
943
952
}
944
953
}
945
- // Check for date (not datetime)
946
- let date_result = py. import ( "datetime" ) . and_then ( |m| m. getattr ( "date"<
10000
/span>) ) ;
947
- if let Ok ( date_cls) = date_result {
948
- if let Ok ( true ) = value. is_instance ( date_cls) {
949
- if let Ok ( naive_date) = value. extract :: < chrono:: NaiveDate > ( py) {
950
- return Ok ( ScalarValue :: Date32 ( Some (
951
- naive_date. num_days_from_ce ( ) - 719163 , // Convert from CE to Unix epoch
952
- ) ) ) ;
953
- }
954
+
955
+ let msg = "Failed to convert Python datetime" ;
956
+ return Err ( PyDataFusionError :: Common ( msg. to_string ( ) ) ) ;
957
+ }
958
+ }
959
+
960
+ // Check for date (not datetime)
961
+ let date_result = py. import ( "datetime" ) . and_then ( |m| m. getattr ( "date" ) ) ;
962
+ if let Ok ( date_cls) = date_result {
963
+ if let Ok ( true ) = value. is_instance ( date_cls) {
964
+ if let Ok ( date) = value. cast_as :: < pyo3:: types:: PyDate > ( py) {
965
+ let year = date. get_year ( ) as i32 ;
966
+ let month = date. get_month ( ) as u8 ;
967
+ let day = date. get_day ( ) as u8 ;
968
+
969
+ // Calculate days since Unix epoch (1970-01-01)
970
+ if let Ok ( days) = date_to_days_since_epoch ( year, month, day) {
971
+ return Ok ( ScalarValue :: Date32 ( Some ( days) ) ) ;
954
972
}
955
973
}
956
- let msg = "Unsupported datetime type format" ;
974
+
975
+ let msg = "Failed to convert Python date" ;
957
976
return Err ( PyDataFusionError :: Common ( msg. to_string ( ) ) ) ;
958
977
}
959
978
}
@@ -973,3 +992,90 @@ fn python_value_to_scalar_value(value: &PyObject, py: Python) -> PyDataFusionRes
973
992
}
974
993
}
975
994
}
995
+
996
+ /// Helper function to convert date components to timestamp in nanoseconds
997
+ fn date_to_timestamp (
998
+ year : i32 ,
999
+ month : u8 ,
1000
+ day : u8 ,
1001
+ hour : u8 ,
1002
+ minute : u8 ,
1003
+ second : u8 ,
1004
+ nano : u32 ,
1005
+ ) -> Result < i64 , String > {
1006
+ // This is a simplified implementation
1007
+ // For production code, consider using a more complete date/time library
1008
+
1009
+ // Number of days in each month (non-leap year)
1010
+ const DAYS_IN_MONTH : [ u8 ; 12 ] = [ 31 , 28 , 31 , 30 , 31 , 30 , 31 , 31 , 30 , 31 , 30 , 31 ] ;
1011
+
1012
+ // Validate inputs
1013
+ if month < 1 || month > 12 {
1014
+ return Err ( "Invalid month" . to_string ( ) ) ;
1015
+ }
1016
+
1017
+ let max_days = if month == 2 && is_leap_year ( year) {
1018
+ 29
1019
+ } else {
1020
+ DAYS_IN_MONTH [ ( month - 1 ) as usize ]
1021
+ } ;
1022
+
1023
+ if day < 1 || day > max_days {
1024
+ return Err ( "Invalid day" . to_string ( ) ) ;
1025
+ }
1026
+
1027
+ if hour > 23 || minute > 59 || second > 59 {
1028
+ return Err ( "Invalid time" . to_string ( ) ) ;
1029
+ }
1030
+
1031
+ // Calculate days since epoch
1032
+ let days = date_to_days_since_epoch ( year, month, day) ?;
1033
+
1034
+ // Convert to seconds and add time components
1035
+ let seconds =
1036
+
341A
days as i64 * 86400 + ( hour as i64 ) * 3600 + ( minute as i64 ) * 60 + ( second as i64 ) ;
1037
+
1038
+ // Convert to nanoseconds
1039
+ Ok ( seconds * 1_000_000_000 + nano as i64 )
1040
+ }
1041
+
1042
+ /// Helper function to check if a year is a leap year
1043
+ fn is_leap_year ( year : i32 ) -> bool {
1044
+ ( year % 4 == 0 && year % 100 != 0 ) || ( year % 400 == 0 )
1045
+ }
1046
+
1047
+ /// Helper function to convert date to days since Unix epoch (1970-01-01)
1048
+ fn date_to_days_since_epoch ( year : i32 , month : u8 , day : u8 ) -> Result < i32 , String > {
1049
+ // This is a simplified implementation to calculate days since epoch
1050
+ if year < 1970 {
1051
+ return Err ( "Dates before 1970 not supported in this implementation" . to_string ( ) ) ;
1052
+ }
1053
+
1054
+ let mut days = 0 ;
1055
+
1056
+ // Add days for each year since 1970
1057
+ for y in 1970 ..year {
1058
+ days += if is_leap_year ( y) { 366 } else { 365 } ;
1059
+ }
1060
+
1061
+ // Add days for each month in the current year
1062
+ for m in 1 ..month {
1063
+ days += match m {
1064
+ 1 | 3 | 5 | 7 | 8 | 10 | 12 => 31 ,
1065
+ 4 | 6 | 9 | 11 => 30 ,
1066
+ 2 => {
1067
+ if is_leap_year ( year) {
1068
+ 29
1069
+ } else {
1070
+ 28
1071
+ }
1072
+ }
1073
+ _ => return Err ( "Invalid month" . to_string ( ) ) ,
1074
+ } ;
1075
+ }
1076
+
1077
+ // Add days in current month
1078
+ days += day as i32 - 1 ; // Subtract 1 because we're counting from the start of the month
1079
+
1080
+ Ok ( days)
1081
+ }
0 commit comments