@@ -610,6 +610,23 @@ macro_rules! cast_decimal_to_int32_up {
610610 } } ;
611611}
612612
613+ macro_rules! cast_int_to_timestamp_impl {
614+ ( $array: expr, $builder: expr, $primitive_type: ty) => { {
615+ let arr = $array. as_primitive:: <$primitive_type>( ) ;
616+ for i in 0 ..arr. len( ) {
617+ if arr. is_null( i) {
618+ $builder. append_null( ) ;
619+ } else {
620+ // saturating_mul limits to i64::MIN/MAX on overflow instead of panicking,
621+ // which could occur when converting extreme values (e.g., Long.MIN_VALUE)
622+ // matching spark behavior (irrespective of EvalMode)
623+ let micros = ( arr. value( i) as i64 ) . saturating_mul( MICROS_PER_SECOND ) ;
624+ $builder. append_value( micros) ;
625+ }
626+ }
627+ } } ;
628+ }
629+
613630// copied from arrow::dataTypes::Decimal128Type since Decimal128Type::format_decimal can't be called directly
614631fn format_decimal_str ( value_str : & str , precision : usize , scale : i8 ) -> String {
615632 let ( sign, rest) = match value_str. strip_prefix ( '-' ) {
@@ -912,6 +929,7 @@ pub(crate) fn cast_array(
912929 ( Boolean , Decimal128 ( precision, scale) ) => {
913930 cast_boolean_to_decimal ( & array, * precision, * scale)
914931 }
932+ ( Int8 | Int16 | Int32 | Int64 , Timestamp ( _, tz) ) => cast_int_to_timestamp ( & array, tz) ,
915933 _ if cast_options. is_adapting_schema
916934 || is_datafusion_spark_compatible ( from_type, to_type) =>
917935 {
@@ -930,6 +948,29 @@ pub(crate) fn cast_array(
930948 Ok ( spark_cast_postprocess ( cast_result?, from_type, to_type) )
931949}
932950
951+ fn cast_int_to_timestamp (
952+ array_ref : & ArrayRef ,
953+ target_tz : & Option < Arc < str > > ,
954+ ) -> SparkResult < ArrayRef > {
955+ // Input is seconds since epoch, multiply by MICROS_PER_SECOND to get microseconds.
956+ let mut builder = TimestampMicrosecondBuilder :: with_capacity ( array_ref. len ( ) ) ;
957+
958+ match array_ref. data_type ( ) {
959+ DataType :: Int8 => cast_int_to_timestamp_impl ! ( array_ref, builder, Int8Type ) ,
960+ DataType :: Int16 => cast_int_to_timestamp_impl ! ( array_ref, builder, Int16Type ) ,
961+ DataType :: Int32 => cast_int_to_timestamp_impl ! ( array_ref, builder, Int32Type ) ,
962+ DataType :: Int64 => cast_int_to_timestamp_impl ! ( array_ref, builder, Int64Type ) ,
963+ dt => {
964+ return Err ( SparkError :: Internal ( format ! (
965+ "Unsupported type for cast_int_to_timestamp: {:?}" ,
966+ dt
967+ ) ) )
968+ }
969+ }
970+
971+ Ok ( Arc :: new ( builder. finish ( ) . with_timezone_opt ( target_tz. clone ( ) ) ) as ArrayRef )
972+ }
973+
933974fn cast_date_to_timestamp (
934975 array_ref : & ArrayRef ,
935976 cast_options : & SparkCastOptions ,
@@ -3399,4 +3440,94 @@ mod tests {
33993440 assert_eq ! ( r#"[null]"# , string_array. value( 2 ) ) ;
34003441 assert_eq ! ( r#"[]"# , string_array. value( 3 ) ) ;
34013442 }
3443+
3444+ #[ test]
3445+ fn test_cast_int_to_timestamp ( ) {
3446+ let timezones: [ Option < Arc < str > > ; 6 ] = [
3447+ Some ( Arc :: from ( "UTC" ) ) ,
3448+ Some ( Arc :: from ( "America/New_York" ) ) ,
3449+ Some ( Arc :: from ( "America/Los_Angeles" ) ) ,
3450+ Some ( Arc :: from ( "Europe/London" ) ) ,
3451+ Some ( Arc :: from ( "Asia/Tokyo" ) ) ,
3452+ Some ( Arc :: from ( "Australia/Sydney" ) ) ,
3453+ ] ;
3454+
3455+ for tz in & timezones {
3456+ let int8_array: ArrayRef = Arc :: new ( Int8Array :: from ( vec ! [
3457+ Some ( 0 ) ,
3458+ Some ( 1 ) ,
3459+ Some ( -1 ) ,
3460+ Some ( 127 ) ,
3461+ Some ( -128 ) ,
3462+ None ,
3463+ ] ) ) ;
3464+
3465+ let result = cast_int_to_timestamp ( & int8_array, tz) . unwrap ( ) ;
3466+ let ts_array = result. as_primitive :: < TimestampMicrosecondType > ( ) ;
3467+
3468+ assert_eq ! ( ts_array. value( 0 ) , 0 ) ;
3469+ assert_eq ! ( ts_array. value( 1 ) , 1_000_000 ) ;
3470+ assert_eq ! ( ts_array. value( 2 ) , -1_000_000 ) ;
3471+ assert_eq ! ( ts_array. value( 3 ) , 127_000_000 ) ;
3472+ assert_eq ! ( ts_array. value( 4 ) , -128_000_000 ) ;
3473+ assert ! ( ts_array. is_null( 5 ) ) ;
3474+ assert_eq ! ( ts_array. timezone( ) , tz. as_ref( ) . map( |s| s. as_ref( ) ) ) ;
3475+
3476+ let int16_array: ArrayRef = Arc :: new ( Int16Array :: from ( vec ! [
3477+ Some ( 0 ) ,
3478+ Some ( 1 ) ,
3479+ Some ( -1 ) ,
3480+ Some ( 32767 ) ,
3481+ Some ( -32768 ) ,
3482+ None ,
3483+ ] ) ) ;
3484+
3485+ let result = cast_int_to_timestamp ( & int16_array, tz) . unwrap ( ) ;
3486+ let ts_array = result. as_primitive :: < TimestampMicrosecondType > ( ) ;
3487+
3488+ assert_eq ! ( ts_array. value( 0 ) , 0 ) ;
3489+ assert_eq ! ( ts_array. value( 1 ) , 1_000_000 ) ;
3490+ assert_eq ! ( ts_array. value( 2 ) , -1_000_000 ) ;
3491+ assert_eq ! ( ts_array. value( 3 ) , 32_767_000_000_i64 ) ;
3492+ assert_eq ! ( ts_array. value( 4 ) , -32_768_000_000_i64 ) ;
3493+ assert ! ( ts_array. is_null( 5 ) ) ;
3494+ assert_eq ! ( ts_array. timezone( ) , tz. as_ref( ) . map( |s| s. as_ref( ) ) ) ;
3495+
3496+ let int32_array: ArrayRef = Arc :: new ( Int32Array :: from ( vec ! [
3497+ Some ( 0 ) ,
3498+ Some ( 1 ) ,
3499+ Some ( -1 ) ,
3500+ Some ( 1704067200 ) ,
3501+ None ,
3502+ ] ) ) ;
3503+
3504+ let result = cast_int_to_timestamp ( & int32_array, tz) . unwrap ( ) ;
3505+ let ts_array = result. as_primitive :: < TimestampMicrosecondType > ( ) ;
3506+
3507+ assert_eq ! ( ts_array. value( 0 ) , 0 ) ;
3508+ assert_eq ! ( ts_array. value( 1 ) , 1_000_000 ) ;
3509+ assert_eq ! ( ts_array. value( 2 ) , -1_000_000 ) ;
3510+ assert_eq ! ( ts_array. value( 3 ) , 1_704_067_200_000_000_i64 ) ;
3511+ assert ! ( ts_array. is_null( 4 ) ) ;
3512+ assert_eq ! ( ts_array. timezone( ) , tz. as_ref( ) . map( |s| s. as_ref( ) ) ) ;
3513+
3514+ let int64_array: ArrayRef = Arc :: new ( Int64Array :: from ( vec ! [
3515+ Some ( 0 ) ,
3516+ Some ( 1 ) ,
3517+ Some ( -1 ) ,
3518+ Some ( i64 :: MAX ) ,
3519+ Some ( i64 :: MIN ) ,
3520+ ] ) ) ;
3521+
3522+ let result = cast_int_to_timestamp ( & int64_array, tz) . unwrap ( ) ;
3523+ let ts_array = result. as_primitive :: < TimestampMicrosecondType > ( ) ;
3524+
3525+ assert_eq ! ( ts_array. value( 0 ) , 0 ) ;
3526+ assert_eq ! ( ts_array. value( 1 ) , 1_000_000_i64 ) ;
3527+ assert_eq ! ( ts_array. value( 2 ) , -1_000_000_i64 ) ;
3528+ assert_eq ! ( ts_array. value( 3 ) , i64 :: MAX ) ;
3529+ assert_eq ! ( ts_array. value( 4 ) , i64 :: MIN ) ;
3530+ assert_eq ! ( ts_array. timezone( ) , tz. as_ref( ) . map( |s| s. as_ref( ) ) ) ;
3531+ }
3532+ }
34023533}
0 commit comments