use num_traits::FromPrimitive;
use time::Duration;
use crate::enums::error::MinarrowError;
use crate::enums::time_units::{TimePeriod, TimeUnit};
use crate::traits::masked_array::MaskedArray;
use crate::traits::type_unions::Integer;
use crate::{Bitmask, DatetimeArray};
pub fn truncate_into<T: Integer + FromPrimitive>(
src: &DatetimeArray<T>,
period: TimePeriod,
out: &mut [T],
mut out_mask: Option<&mut Bitmask>,
) {
let time_unit = src.time_unit;
let calendar: Option<fn(time::OffsetDateTime) -> Option<time::OffsetDateTime>> = match period {
TimePeriod::Year => Some(|dt| {
time::Date::from_calendar_date(dt.year(), time::Month::January, 1)
.ok()
.and_then(|d| d.with_hms(0, 0, 0).ok())
.map(|pdt| pdt.assume_utc())
}),
TimePeriod::Month => Some(|dt| {
time::Date::from_calendar_date(dt.year(), dt.month(), 1)
.ok()
.and_then(|d| d.with_hms(0, 0, 0).ok())
.map(|pdt| pdt.assume_utc())
}),
TimePeriod::Week => Some(|dt| {
let days_to_sunday = (dt.weekday().number_from_sunday() - 1) as i64;
dt.checked_sub(time::Duration::days(days_to_sunday))
.and_then(|week_start| week_start.date().with_hms(0, 0, 0).ok())
.map(|pdt| pdt.assume_utc())
}),
TimePeriod::Day => Some(|dt| dt.date().with_hms(0, 0, 0).ok().map(|pdt| pdt.assume_utc())),
TimePeriod::Hour => {
Some(|dt| dt.date().with_hms(dt.hour(), 0, 0).ok().map(|pdt| pdt.assume_utc()))
}
TimePeriod::Minute => Some(|dt| {
dt.date().with_hms(dt.hour(), dt.minute(), 0).ok().map(|pdt| pdt.assume_utc())
}),
TimePeriod::Second => Some(|dt| {
dt.date()
.with_hms(dt.hour(), dt.minute(), dt.second())
.ok()
.map(|pdt| pdt.assume_utc())
}),
TimePeriod::Millisecond | TimePeriod::Microsecond => None,
};
if let Some(trunc) = calendar {
for i in 0..src.len() {
let original = src.data[i];
out[i] = original;
let valid = if src.is_null(i) {
false
} else {
match original
.to_i64()
.and_then(|v| DatetimeArray::<T>::i64_to_datetime(v, time_unit))
.and_then(trunc)
.map(|dt| DatetimeArray::<T>::datetime_to_i64(dt, time_unit))
.and_then(T::from_i64)
{
Some(t) => {
out[i] = t;
true
}
None => false,
}
};
if let Some(mask) = out_mask.as_deref_mut() {
mask.set(i, valid);
}
}
return;
}
let divisor: Option<i64> = match (period, time_unit) {
(TimePeriod::Microsecond, TimeUnit::Nanoseconds) => Some(1_000),
(TimePeriod::Millisecond, TimeUnit::Nanoseconds) => Some(1_000_000),
(TimePeriod::Millisecond, TimeUnit::Microseconds) => Some(1_000),
_ => None,
};
for i in 0..src.len() {
let original = src.data[i];
out[i] = original;
let valid = if src.is_null(i) {
false
} else {
match divisor {
None => true,
Some(divisor) => {
match original.to_i64().map(|v| (v / divisor) * divisor).and_then(T::from_i64) {
Some(t) => {
out[i] = t;
true
}
None => false,
}
}
}
};
if let Some(mask) = out_mask.as_deref_mut() {
mask.set(i, valid);
}
}
}
pub fn add_duration_into<T: Integer + FromPrimitive>(
src: &DatetimeArray<T>,
duration: Duration,
out: &mut [T],
mut out_mask: Option<&mut Bitmask>,
) -> Result<(), MinarrowError> {
let duration_value: i64 = match src.time_unit {
TimeUnit::Seconds => duration.whole_seconds(),
TimeUnit::Milliseconds => {
duration.whole_milliseconds().try_into().map_err(|_| MinarrowError::Overflow {
value: format!("{} ms", duration.whole_milliseconds()),
target: "i64",
})?
}
TimeUnit::Microseconds => {
duration.whole_microseconds().try_into().map_err(|_| MinarrowError::Overflow {
value: format!("{} μs", duration.whole_microseconds()),
target: "i64",
})?
}
TimeUnit::Nanoseconds => {
duration.whole_nanoseconds().try_into().map_err(|_| MinarrowError::Overflow {
value: format!("{} ns", duration.whole_nanoseconds()),
target: "i64",
})?
}
TimeUnit::Days => duration.whole_days(),
};
for i in 0..src.len() {
let original = src.data[i];
out[i] = original;
let valid = if src.is_null(i) {
false
} else {
match original.to_i64().and_then(|v| v.checked_add(duration_value)).and_then(T::from_i64)
{
Some(t) => {
out[i] = t;
true
}
None => false,
}
};
if let Some(mask) = out_mask.as_deref_mut() {
mask.set(i, valid);
}
}
Ok(())
}
pub fn add_months_into<T: Integer + FromPrimitive>(
src: &DatetimeArray<T>,
months: i32,
out: &mut [T],
mut out_mask: Option<&mut Bitmask>,
) {
let time_unit = src.time_unit;
for i in 0..src.len() {
let original = src.data[i];
out[i] = original;
let computed = if src.is_null(i) {
None
} else {
original
.to_i64()
.and_then(|v| DatetimeArray::<T>::i64_to_datetime(v, time_unit))
.and_then(|dt| {
let date = dt.date();
let total_months = date.year() * 12 + (date.month() as i32) - 1 + months;
let new_year = total_months / 12;
let new_month = (total_months % 12 + 1) as u8;
let new_month_enum = time::Month::try_from(new_month).ok()?;
let days_in_month = new_month_enum.length(new_year);
let day = date.day().min(days_in_month);
let new_date =
time::Date::from_calendar_date(new_year, new_month_enum, day).ok()?;
let new_dt = new_date.with_time(dt.time()).assume_utc();
T::from_i64(DatetimeArray::<T>::datetime_to_i64(new_dt, time_unit))
})
};
let valid = match computed {
Some(t) => {
out[i] = t;
true
}
None => false,
};
if let Some(mask) = out_mask.as_deref_mut() {
mask.set(i, valid);
}
}
}