use std::ops::{Add, Sub};
use num_traits::AsPrimitive;
use crate::{
array::{Array, PrimitiveArray},
compute::arity::binary,
datatypes::{DataType, TimeUnit},
error::{ArrowError, Result},
temporal_conversions,
types::{months_days_ns, NativeType},
};
fn create_scale(lhs: &DataType, rhs: &DataType) -> Result<f64> {
let scale = match (lhs, rhs) {
(DataType::Timestamp(timeunit_a, _), DataType::Duration(timeunit_b))
| (DataType::Time32(timeunit_a), DataType::Duration(timeunit_b))
| (DataType::Time64(timeunit_a), DataType::Duration(timeunit_b)) => {
temporal_conversions::timeunit_scale(*timeunit_a, *timeunit_b)
}
(DataType::Date32, DataType::Duration(timeunit)) => {
temporal_conversions::timeunit_scale(TimeUnit::Second, *timeunit)
/ temporal_conversions::SECONDS_IN_DAY as f64
}
(DataType::Date64, DataType::Duration(timeunit)) => {
temporal_conversions::timeunit_scale(TimeUnit::Millisecond, *timeunit)
}
_ => {
return Err(ArrowError::InvalidArgumentError(
"Incorrect data type for the arguments".to_string(),
));
}
};
Ok(scale)
}
pub fn add_duration<T>(
time: &PrimitiveArray<T>,
duration: &PrimitiveArray<i64>,
) -> Result<PrimitiveArray<T>>
where
f64: AsPrimitive<T>,
T: NativeType + Add<T, Output = T>,
{
let scale = create_scale(time.data_type(), duration.data_type())?;
let op = move |a: T, b: i64| a + (b as f64 * scale).as_();
binary(time, duration, time.data_type().clone(), op)
}
pub fn subtract_duration<T>(
time: &PrimitiveArray<T>,
duration: &PrimitiveArray<i64>,
) -> Result<PrimitiveArray<T>>
where
f64: AsPrimitive<T>,
T: NativeType + Sub<T, Output = T>,
{
let scale = create_scale(time.data_type(), duration.data_type())?;
let op = move |a: T, b: i64| a - (b as f64 * scale).as_();
binary(time, duration, time.data_type().clone(), op)
}
pub fn subtract_timestamps(
lhs: &PrimitiveArray<i64>,
rhs: &PrimitiveArray<i64>,
) -> Result<PrimitiveArray<i64>> {
match (lhs.data_type(), rhs.data_type()) {
(DataType::Timestamp(timeunit_a, None), DataType::Timestamp(timeunit_b, None)) => {
let scale = temporal_conversions::timeunit_scale(*timeunit_a, *timeunit_b);
let op = move |a, b| a - (b as f64 * scale) as i64;
binary(lhs, rhs, DataType::Duration(*timeunit_a), op)
}
_ => Err(ArrowError::InvalidArgumentError(
"Incorrect data type for the arguments".to_string(),
)),
}
}
pub fn add_interval(
timestamp: &PrimitiveArray<i64>,
interval: &PrimitiveArray<months_days_ns>,
) -> Result<PrimitiveArray<i64>> {
match timestamp.data_type().to_logical_type() {
DataType::Timestamp(time_unit, Some(timezone_str)) => {
let time_unit = *time_unit;
let timezone = temporal_conversions::parse_offset(timezone_str);
match timezone {
Ok(timezone) => binary(
timestamp,
interval,
timestamp.data_type().clone(),
|timestamp, interval| {
temporal_conversions::add_interval(
timestamp, time_unit, interval, &timezone,
)
},
),
#[cfg(feature = "chrono-tz")]
Err(_) => {
let timezone = temporal_conversions::parse_offset_tz(timezone_str)?;
binary(
timestamp,
interval,
timestamp.data_type().clone(),
|timestamp, interval| {
temporal_conversions::add_interval(
timestamp, time_unit, interval, &timezone,
)
},
)
}
#[cfg(not(feature = "chrono-tz"))]
_ => Err(ArrowError::InvalidArgumentError(format!(
"timezone \"{}\" cannot be parsed (feature chrono-tz is not active)",
timezone_str
))),
}
}
DataType::Timestamp(time_unit, None) => {
let time_unit = *time_unit;
binary(
timestamp,
interval,
timestamp.data_type().clone(),
|timestamp, interval| {
temporal_conversions::add_naive_interval(timestamp, time_unit, interval)
},
)
}
_ => Err(ArrowError::InvalidArgumentError(
"Adding an interval is only supported for `DataType::Timestamp`".to_string(),
)),
}
}