From c4e9053e65974a76c5f7b60355ea25d4ca14972f Mon Sep 17 00:00:00 2001 From: Anand Hegde Date: Sun, 13 Sep 2026 21:14:26 +0530 Subject: [PATCH] Move temporal cast helpers into a private submodule Follow-up to #5125, continuing the extractions in #5537 and friends. `arrow-cast/src/cast/mod.rs` still holds the timestamp, date, duration, interval and timezone helpers. Move them into a private `temporal` submodule, leaving `cast_with_options` as the top-level dispatcher. The function bodies are moved verbatim; the only edit is the `pub(crate)` visibility they need to stay reachable from `mod.rs`, matching the convention in `string.rs` and `list.rs`. `cast_reinterpret_arrays` stays in `mod.rs`: it sat inside this block but is a generic primitive helper used across the numeric, temporal and dictionary paths. No behavioural change and no public API change. --- arrow-cast/src/cast/mod.rs | 235 +---------------------------- arrow-cast/src/cast/temporal.rs | 257 ++++++++++++++++++++++++++++++++ 2 files changed, 260 insertions(+), 232 deletions(-) create mode 100644 arrow-cast/src/cast/temporal.rs diff --git a/arrow-cast/src/cast/mod.rs b/arrow-cast/src/cast/mod.rs index 7ba864336e2c..f9d13f1c37f5 100644 --- a/arrow-cast/src/cast/mod.rs +++ b/arrow-cast/src/cast/mod.rs @@ -43,6 +43,7 @@ mod list; mod map; mod run_array; mod string; +mod temporal; mod union; use crate::cast::decimal::*; @@ -51,11 +52,11 @@ use crate::cast::list::*; use crate::cast::map::*; use crate::cast::run_array::*; use crate::cast::string::*; +use crate::cast::temporal::*; pub use crate::cast::union::*; -use arrow_buffer::IntervalMonthDayNano; use arrow_data::ByteView; -use chrono::{NaiveTime, Offset, TimeZone, Utc}; +use chrono::{TimeZone, Utc}; use std::cmp::Ordering; use std::sync::Arc; @@ -443,130 +444,6 @@ where Ok(Arc::new(array.with_precision_and_scale(precision, scale)?)) } -/// Cast the array from interval year month to month day nano -fn cast_interval_year_month_to_interval_month_day_nano( - array: &dyn Array, - _cast_options: &CastOptions, -) -> Result { - let array = array.as_primitive::(); - - Ok(Arc::new(array.unary::<_, IntervalMonthDayNanoType>(|v| { - let months = IntervalYearMonthType::to_months(v); - IntervalMonthDayNanoType::make_value(months, 0, 0) - }))) -} - -/// Cast the array from interval day time to month day nano -fn cast_interval_day_time_to_interval_month_day_nano( - array: &dyn Array, - _cast_options: &CastOptions, -) -> Result { - let array = array.as_primitive::(); - let mul = 1_000_000; - - Ok(Arc::new(array.unary::<_, IntervalMonthDayNanoType>(|v| { - let (days, ms) = IntervalDayTimeType::to_parts(v); - IntervalMonthDayNanoType::make_value(0, days, ms as i64 * mul) - }))) -} - -/// Cast the array from interval to duration -fn cast_month_day_nano_to_duration>( - array: &dyn Array, - cast_options: &CastOptions, -) -> Result { - let array = array.as_primitive::(); - let scale = match D::DATA_TYPE { - DataType::Duration(TimeUnit::Second) => 1_000_000_000, - DataType::Duration(TimeUnit::Millisecond) => 1_000_000, - DataType::Duration(TimeUnit::Microsecond) => 1_000, - DataType::Duration(TimeUnit::Nanosecond) => 1, - _ => unreachable!(), - }; - - if cast_options.safe { - let iter = array.iter().map(|v| { - let v = v?; - (v.days == 0 && v.months == 0).then_some(v.nanoseconds / scale) - }); - Ok(Arc::new(unsafe { - PrimitiveArray::::from_trusted_len_iter(iter) - })) - } else { - let vec = array - .iter() - .map(|v| { - v.map(|v| match v.days == 0 && v.months == 0 { - true => Ok((v.nanoseconds) / scale), - _ => Err(ArrowError::ComputeError( - "Cannot convert interval containing non-zero months or days to duration" - .to_string(), - )), - }) - .transpose() - }) - .collect::, _>>()?; - Ok(Arc::new(unsafe { - PrimitiveArray::::from_trusted_len_iter(vec.iter()) - })) - } -} - -/// Cast the array from duration and interval -fn cast_duration_to_interval>( - array: &dyn Array, - cast_options: &CastOptions, -) -> Result { - let array = array - .as_any() - .downcast_ref::>() - .ok_or_else(|| { - ArrowError::ComputeError( - "Internal Error: Cannot cast duration to DurationArray of expected type" - .to_string(), - ) - })?; - - let scale = match array.data_type() { - DataType::Duration(TimeUnit::Second) => 1_000_000_000, - DataType::Duration(TimeUnit::Millisecond) => 1_000_000, - DataType::Duration(TimeUnit::Microsecond) => 1_000, - DataType::Duration(TimeUnit::Nanosecond) => 1, - _ => unreachable!(), - }; - - if cast_options.safe { - let iter = array.iter().map(|v| { - v?.checked_mul(scale) - .map(|v| IntervalMonthDayNano::new(0, 0, v)) - }); - Ok(Arc::new(unsafe { - PrimitiveArray::::from_trusted_len_iter(iter) - })) - } else { - let vec = array - .iter() - .map(|v| { - v.map(|v| { - if let Ok(v) = v.mul_checked(scale) { - Ok(IntervalMonthDayNano::new(0, 0, v)) - } else { - Err(ArrowError::ComputeError(format!( - "Cannot cast to {:?}. Overflowing on {:?}", - IntervalMonthDayNanoType::DATA_TYPE, - v - ))) - } - }) - .transpose() - }) - .collect::, _>>()?; - Ok(Arc::new(unsafe { - PrimitiveArray::::from_trusted_len_iter(vec.iter()) - })) - } -} - /// Cast the primitive array using [`PrimitiveArray::reinterpret_cast`] fn cast_reinterpret_arrays>( array: &dyn Array, @@ -574,90 +451,6 @@ fn cast_reinterpret_arrays().reinterpret_cast::())) } -fn make_timestamp_array( - array: &PrimitiveArray, - unit: TimeUnit, - tz: Option>, -) -> ArrayRef { - match unit { - TimeUnit::Second => Arc::new( - array - .reinterpret_cast::() - .with_timezone_opt(tz), - ), - TimeUnit::Millisecond => Arc::new( - array - .reinterpret_cast::() - .with_timezone_opt(tz), - ), - TimeUnit::Microsecond => Arc::new( - array - .reinterpret_cast::() - .with_timezone_opt(tz), - ), - TimeUnit::Nanosecond => Arc::new( - array - .reinterpret_cast::() - .with_timezone_opt(tz), - ), - } -} - -fn make_duration_array(array: &PrimitiveArray, unit: TimeUnit) -> ArrayRef { - match unit { - TimeUnit::Second => Arc::new(array.reinterpret_cast::()), - TimeUnit::Millisecond => Arc::new(array.reinterpret_cast::()), - TimeUnit::Microsecond => Arc::new(array.reinterpret_cast::()), - TimeUnit::Nanosecond => Arc::new(array.reinterpret_cast::()), - } -} - -fn as_time_res_with_timezone( - v: i64, - tz: Option, -) -> Result { - let time = match tz { - Some(tz) => as_datetime_with_timezone::(v, tz).map(|d| d.time()), - None => as_datetime::(v).map(|d| d.time()), - }; - - time.ok_or_else(|| { - ArrowError::CastError(format!( - "Failed to create naive time with {} {}", - std::any::type_name::(), - v - )) - }) -} - -fn timestamp_to_date32( - array: &PrimitiveArray, -) -> Result { - let err = |x: i64| { - ArrowError::CastError(format!( - "Cannot convert {} {x} to datetime", - std::any::type_name::() - )) - }; - - let array: Date32Array = match array.timezone() { - Some(tz) => { - let tz: Tz = tz.parse()?; - array.try_unary(|x| { - as_datetime_with_timezone::(x, tz) - .ok_or_else(|| err(x)) - .map(|d| Date32Type::from_naive_date(d.date_naive())) - })? - } - None => array.try_unary(|x| { - as_datetime::(x) - .ok_or_else(|| err(x)) - .map(|d| Date32Type::from_naive_date(d.date())) - })?, - }; - Ok(Arc::new(array)) -} - /// Try to cast `array` to `to_type` if possible. /// /// Returns a new Array with type `to_type` if possible. @@ -2626,28 +2419,6 @@ fn cast_numeric_to_binary( )?)) } -fn adjust_timestamp_to_timezone( - array: PrimitiveArray, - to_tz: &Tz, - cast_options: &CastOptions, -) -> Result, ArrowError> { - let adjust = |o| { - let local = as_datetime::(o)?; - let offset = to_tz.offset_from_local_datetime(&local).single()?; - T::from_naive_datetime(local - offset.fix(), None) - }; - let adjusted = if cast_options.safe { - array.unary_opt::<_, Int64Type>(adjust) - } else { - array.try_unary::<_, Int64Type, _>(|o| { - adjust(o).ok_or_else(|| { - ArrowError::CastError("Cannot cast timezone to different timezone".to_string()) - }) - })? - }; - Ok(adjusted) -} - /// Cast numeric types to Boolean /// /// Any zero value returns `false` while non-zero returns `true` diff --git a/arrow-cast/src/cast/temporal.rs b/arrow-cast/src/cast/temporal.rs new file mode 100644 index 000000000000..c3dc5c98b29b --- /dev/null +++ b/arrow-cast/src/cast/temporal.rs @@ -0,0 +1,257 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +//! Cast support for temporal arrays: timestamps, dates, durations, +//! intervals, and timezones. + +use arrow_array::{cast::*, temporal_conversions::*, timezone::Tz, types::*, *}; +use arrow_buffer::IntervalMonthDayNano; +use arrow_schema::{ArrowError, DataType, TimeUnit}; +use chrono::{NaiveTime, Offset, TimeZone}; +use std::sync::Arc; + +use super::CastOptions; + +/// Cast the array from interval year month to month day nano +pub(crate) fn cast_interval_year_month_to_interval_month_day_nano( + array: &dyn Array, + _cast_options: &CastOptions, +) -> Result { + let array = array.as_primitive::(); + + Ok(Arc::new(array.unary::<_, IntervalMonthDayNanoType>(|v| { + let months = IntervalYearMonthType::to_months(v); + IntervalMonthDayNanoType::make_value(months, 0, 0) + }))) +} + +/// Cast the array from interval day time to month day nano +pub(crate) fn cast_interval_day_time_to_interval_month_day_nano( + array: &dyn Array, + _cast_options: &CastOptions, +) -> Result { + let array = array.as_primitive::(); + let mul = 1_000_000; + + Ok(Arc::new(array.unary::<_, IntervalMonthDayNanoType>(|v| { + let (days, ms) = IntervalDayTimeType::to_parts(v); + IntervalMonthDayNanoType::make_value(0, days, ms as i64 * mul) + }))) +} + +/// Cast the array from interval to duration +pub(crate) fn cast_month_day_nano_to_duration>( + array: &dyn Array, + cast_options: &CastOptions, +) -> Result { + let array = array.as_primitive::(); + let scale = match D::DATA_TYPE { + DataType::Duration(TimeUnit::Second) => 1_000_000_000, + DataType::Duration(TimeUnit::Millisecond) => 1_000_000, + DataType::Duration(TimeUnit::Microsecond) => 1_000, + DataType::Duration(TimeUnit::Nanosecond) => 1, + _ => unreachable!(), + }; + + if cast_options.safe { + let iter = array.iter().map(|v| { + let v = v?; + (v.days == 0 && v.months == 0).then_some(v.nanoseconds / scale) + }); + Ok(Arc::new(unsafe { + PrimitiveArray::::from_trusted_len_iter(iter) + })) + } else { + let vec = array + .iter() + .map(|v| { + v.map(|v| match v.days == 0 && v.months == 0 { + true => Ok((v.nanoseconds) / scale), + _ => Err(ArrowError::ComputeError( + "Cannot convert interval containing non-zero months or days to duration" + .to_string(), + )), + }) + .transpose() + }) + .collect::, _>>()?; + Ok(Arc::new(unsafe { + PrimitiveArray::::from_trusted_len_iter(vec.iter()) + })) + } +} + +/// Cast the array from duration and interval +pub(crate) fn cast_duration_to_interval>( + array: &dyn Array, + cast_options: &CastOptions, +) -> Result { + let array = array + .as_any() + .downcast_ref::>() + .ok_or_else(|| { + ArrowError::ComputeError( + "Internal Error: Cannot cast duration to DurationArray of expected type" + .to_string(), + ) + })?; + + let scale = match array.data_type() { + DataType::Duration(TimeUnit::Second) => 1_000_000_000, + DataType::Duration(TimeUnit::Millisecond) => 1_000_000, + DataType::Duration(TimeUnit::Microsecond) => 1_000, + DataType::Duration(TimeUnit::Nanosecond) => 1, + _ => unreachable!(), + }; + + if cast_options.safe { + let iter = array.iter().map(|v| { + v?.checked_mul(scale) + .map(|v| IntervalMonthDayNano::new(0, 0, v)) + }); + Ok(Arc::new(unsafe { + PrimitiveArray::::from_trusted_len_iter(iter) + })) + } else { + let vec = array + .iter() + .map(|v| { + v.map(|v| { + if let Ok(v) = v.mul_checked(scale) { + Ok(IntervalMonthDayNano::new(0, 0, v)) + } else { + Err(ArrowError::ComputeError(format!( + "Cannot cast to {:?}. Overflowing on {:?}", + IntervalMonthDayNanoType::DATA_TYPE, + v + ))) + } + }) + .transpose() + }) + .collect::, _>>()?; + Ok(Arc::new(unsafe { + PrimitiveArray::::from_trusted_len_iter(vec.iter()) + })) + } +} + +pub(crate) fn make_timestamp_array( + array: &PrimitiveArray, + unit: TimeUnit, + tz: Option>, +) -> ArrayRef { + match unit { + TimeUnit::Second => Arc::new( + array + .reinterpret_cast::() + .with_timezone_opt(tz), + ), + TimeUnit::Millisecond => Arc::new( + array + .reinterpret_cast::() + .with_timezone_opt(tz), + ), + TimeUnit::Microsecond => Arc::new( + array + .reinterpret_cast::() + .with_timezone_opt(tz), + ), + TimeUnit::Nanosecond => Arc::new( + array + .reinterpret_cast::() + .with_timezone_opt(tz), + ), + } +} + +pub(crate) fn make_duration_array(array: &PrimitiveArray, unit: TimeUnit) -> ArrayRef { + match unit { + TimeUnit::Second => Arc::new(array.reinterpret_cast::()), + TimeUnit::Millisecond => Arc::new(array.reinterpret_cast::()), + TimeUnit::Microsecond => Arc::new(array.reinterpret_cast::()), + TimeUnit::Nanosecond => Arc::new(array.reinterpret_cast::()), + } +} + +pub(crate) fn as_time_res_with_timezone( + v: i64, + tz: Option, +) -> Result { + let time = match tz { + Some(tz) => as_datetime_with_timezone::(v, tz).map(|d| d.time()), + None => as_datetime::(v).map(|d| d.time()), + }; + + time.ok_or_else(|| { + ArrowError::CastError(format!( + "Failed to create naive time with {} {}", + std::any::type_name::(), + v + )) + }) +} + +pub(crate) fn timestamp_to_date32( + array: &PrimitiveArray, +) -> Result { + let err = |x: i64| { + ArrowError::CastError(format!( + "Cannot convert {} {x} to datetime", + std::any::type_name::() + )) + }; + + let array: Date32Array = match array.timezone() { + Some(tz) => { + let tz: Tz = tz.parse()?; + array.try_unary(|x| { + as_datetime_with_timezone::(x, tz) + .ok_or_else(|| err(x)) + .map(|d| Date32Type::from_naive_date(d.date_naive())) + })? + } + None => array.try_unary(|x| { + as_datetime::(x) + .ok_or_else(|| err(x)) + .map(|d| Date32Type::from_naive_date(d.date())) + })?, + }; + Ok(Arc::new(array)) +} + +pub(crate) fn adjust_timestamp_to_timezone( + array: PrimitiveArray, + to_tz: &Tz, + cast_options: &CastOptions, +) -> Result, ArrowError> { + let adjust = |o| { + let local = as_datetime::(o)?; + let offset = to_tz.offset_from_local_datetime(&local).single()?; + T::from_naive_datetime(local - offset.fix(), None) + }; + let adjusted = if cast_options.safe { + array.unary_opt::<_, Int64Type>(adjust) + } else { + array.try_unary::<_, Int64Type, _>(|o| { + adjust(o).ok_or_else(|| { + ArrowError::CastError("Cannot cast timezone to different timezone".to_string()) + }) + })? + }; + Ok(adjusted) +}