@@ -613,6 +613,23 @@ macro_rules! cast_decimal_to_int32_up {
613613 } } ;
614614}
615615
616+ macro_rules! cast_int_to_timestamp_impl {
617+ ( $array: expr, $builder: expr, $primitive_type: ty) => { {
618+ let arr = $array. as_primitive:: <$primitive_type>( ) ;
619+ for i in 0 ..arr. len( ) {
620+ if arr. is_null( i) {
621+ $builder. append_null( ) ;
622+ } else {
623+ // saturating_mul limits to i64::MIN/MAX on overflow instead of panicking,
624+ // which could occur when converting extreme values (e.g., Long.MIN_VALUE)
625+ // matching spark behavior (irrespective of EvalMode)
626+ let micros = ( arr. value( i) as i64 ) . saturating_mul( MICROS_PER_SECOND ) ;
627+ $builder. append_value( micros) ;
628+ }
629+ }
630+ } } ;
631+ }
632+
616633// copied from arrow::dataTypes::Decimal128Type since Decimal128Type::format_decimal can't be called directly
617634fn format_decimal_str ( value_str : & str , precision : usize , scale : i8 ) -> String {
618635 let ( sign, rest) = match value_str. strip_prefix ( '-' ) {
@@ -915,6 +932,7 @@ fn cast_array(
915932 ( Boolean , Decimal128 ( precision, scale) ) => {
916933 cast_boolean_to_decimal ( & array, * precision, * scale)
917934 }
935+ ( Int8 | Int16 | Int32 | Int64 , Timestamp ( _, tz) ) => cast_int_to_timestamp ( & array, tz) ,
918936 _ if cast_options. is_adapting_schema
919937 || is_datafusion_spark_compatible ( from_type, to_type) =>
920938 {
@@ -933,6 +951,29 @@ fn cast_array(
933951 Ok ( spark_cast_postprocess ( cast_result?, from_type, to_type) )
934952}
935953
954+ fn cast_int_to_timestamp (
955+ array_ref : & ArrayRef ,
956+ target_tz : & Option < Arc < str > > ,
957+ ) -> SparkResult < ArrayRef > {
958+ // Input is seconds since epoch, multiply by MICROS_PER_SECOND to get microseconds.
959+ let mut builder = TimestampMicrosecondBuilder :: with_capacity ( array_ref. len ( ) ) ;
960+
961+ match array_ref. data_type ( ) {
962+ DataType :: Int8 => cast_int_to_timestamp_impl ! ( array_ref, builder, Int8Type ) ,
963+ DataType :: Int16 => cast_int_to_timestamp_impl ! ( array_ref, builder, Int16Type ) ,
964+ DataType :: Int32 => cast_int_to_timestamp_impl ! ( array_ref, builder, Int32Type ) ,
965+ DataType :: Int64 => cast_int_to_timestamp_impl ! ( array_ref, builder, Int64Type ) ,
966+ dt => {
967+ return Err ( SparkError :: Internal ( format ! (
968+ "Unsupported type for cast_int_to_timestamp: {:?}" ,
969+ dt
970+ ) ) )
971+ }
972+ }
973+
974+ Ok ( Arc :: new ( builder. finish ( ) . with_timezone_opt ( target_tz. clone ( ) ) ) as ArrayRef )
975+ }
976+
936977fn cast_date_to_timestamp (
937978 array_ref : & ArrayRef ,
938979 cast_options : & SparkCastOptions ,
@@ -3519,4 +3560,94 @@ mod tests {
35193560 assert_eq ! ( r#"[null]"# , string_array. value( 2 ) ) ;
35203561 assert_eq ! ( r#"[]"# , string_array. value( 3 ) ) ;
35213562 }
3563+
3564+ #[ test]
3565+ fn test_cast_int_to_timestamp ( ) {
3566+ let timezones: [ Option < Arc < str > > ; 6 ] = [
3567+ Some ( Arc :: from ( "UTC" ) ) ,
3568+ Some ( Arc :: from ( "America/New_York" ) ) ,
3569+ Some ( Arc :: from ( "America/Los_Angeles" ) ) ,
3570+ Some ( Arc :: from ( "Europe/London" ) ) ,
3571+ Some ( Arc :: from ( "Asia/Tokyo" ) ) ,
3572+ Some ( Arc :: from ( "Australia/Sydney" ) ) ,
3573+ ] ;
3574+
3575+ for tz in & timezones {
3576+ let int8_array: ArrayRef = Arc :: new ( Int8Array :: from ( vec ! [
3577+ Some ( 0 ) ,
3578+ Some ( 1 ) ,
3579+ Some ( -1 ) ,
3580+ Some ( 127 ) ,
3581+ Some ( -128 ) ,
3582+ None ,
3583+ ] ) ) ;
3584+
3585+ let result = cast_int_to_timestamp ( & int8_array, tz) . unwrap ( ) ;
3586+ let ts_array = result. as_primitive :: < TimestampMicrosecondType > ( ) ;
3587+
3588+ assert_eq ! ( ts_array. value( 0 ) , 0 ) ;
3589+ assert_eq ! ( ts_array. value( 1 ) , 1_000_000 ) ;
3590+ assert_eq ! ( ts_array. value( 2 ) , -1_000_000 ) ;
3591+ assert_eq ! ( ts_array. value( 3 ) , 127_000_000 ) ;
3592+ assert_eq ! ( ts_array. value( 4 ) , -128_000_000 ) ;
3593+ assert ! ( ts_array. is_null( 5 ) ) ;
3594+ assert_eq ! ( ts_array. timezone( ) , tz. as_ref( ) . map( |s| s. as_ref( ) ) ) ;
3595+
3596+ let int16_array: ArrayRef = Arc :: new ( Int16Array :: from ( vec ! [
3597+ Some ( 0 ) ,
3598+ Some ( 1 ) ,
3599+ Some ( -1 ) ,
3600+ Some ( 32767 ) ,
3601+ Some ( -32768 ) ,
3602+ None ,
3603+ ] ) ) ;
3604+
3605+ let result = cast_int_to_timestamp ( & int16_array, tz) . unwrap ( ) ;
3606+ let ts_array = result. as_primitive :: < TimestampMicrosecondType > ( ) ;
3607+
3608+ assert_eq ! ( ts_array. value( 0 ) , 0 ) ;
3609+ assert_eq ! ( ts_array. value( 1 ) , 1_000_000 ) ;
3610+ assert_eq ! ( ts_array. value( 2 ) , -1_000_000 ) ;
3611+ assert_eq ! ( ts_array. value( 3 ) , 32_767_000_000_i64 ) ;
3612+ assert_eq ! ( ts_array. value( 4 ) , -32_768_000_000_i64 ) ;
3613+ assert ! ( ts_array. is_null( 5 ) ) ;
3614+ assert_eq ! ( ts_array. timezone( ) , tz. as_ref( ) . map( |s| s. as_ref( ) ) ) ;
3615+
3616+ let int32_array: ArrayRef = Arc :: new ( Int32Array :: from ( vec ! [
3617+ Some ( 0 ) ,
3618+ Some ( 1 ) ,
3619+ Some ( -1 ) ,
3620+ Some ( 1704067200 ) ,
3621+ None ,
3622+ ] ) ) ;
3623+
3624+ let result = cast_int_to_timestamp ( & int32_array, tz) . unwrap ( ) ;
3625+ let ts_array = result. as_primitive :: < TimestampMicrosecondType > ( ) ;
3626+
3627+ assert_eq ! ( ts_array. value( 0 ) , 0 ) ;
3628+ assert_eq ! ( ts_array. value( 1 ) , 1_000_000 ) ;
3629+ assert_eq ! ( ts_array. value( 2 ) , -1_000_000 ) ;
3630+ assert_eq ! ( ts_array. value( 3 ) , 1_704_067_200_000_000_i64 ) ;
3631+ assert ! ( ts_array. is_null( 4 ) ) ;
3632+ assert_eq ! ( ts_array. timezone( ) , tz. as_ref( ) . map( |s| s. as_ref( ) ) ) ;
3633+
3634+ let int64_array: ArrayRef = Arc :: new ( Int64Array :: from ( vec ! [
3635+ Some ( 0 ) ,
3636+ Some ( 1 ) ,
3637+ Some ( -1 ) ,
3638+ Some ( i64 :: MAX ) ,
3639+ Some ( i64 :: MIN ) ,
3640+ ] ) ) ;
3641+
3642+ let result = cast_int_to_timestamp ( & int64_array, tz) . unwrap ( ) ;
3643+ let ts_array = result. as_primitive :: < TimestampMicrosecondType > ( ) ;
3644+
3645+ assert_eq ! ( ts_array. value( 0 ) , 0 ) ;
3646+ assert_eq ! ( ts_array. value( 1 ) , 1_000_000_i64 ) ;
3647+ assert_eq ! ( ts_array. value( 2 ) , -1_000_000_i64 ) ;
3648+ assert_eq ! ( ts_array. value( 3 ) , i64 :: MAX ) ;
3649+ assert_eq ! ( ts_array. value( 4 ) , i64 :: MIN ) ;
3650+ assert_eq ! ( ts_array. timezone( ) , tz. as_ref( ) . map( |s| s. as_ref( ) ) ) ;
3651+ }
3652+ }
35223653}
0 commit comments