#[cfg(feature = "datetime_ops")]
use time::Duration;
#[cfg(feature = "datetime_ops")]
use crate::{
DatetimeArray,
enums::{error::MinarrowError, time_units::TimeUnit},
structs::variants::{boolean::BooleanArray, integer::IntegerArray},
traits::{masked_array::MaskedArray, type_unions::Integer},
};
#[cfg(feature = "datetime_ops")]
use num_traits::FromPrimitive;
#[cfg(feature = "datetime_ops")]
impl<T: Integer + FromPrimitive> DatetimeArray<T> {
#[inline(always)]
pub(crate) fn i64_to_datetime(
val_i64: i64,
time_unit: TimeUnit,
) -> Option<time::OffsetDateTime> {
use time::OffsetDateTime;
match time_unit {
TimeUnit::Seconds => OffsetDateTime::from_unix_timestamp(val_i64).ok(),
TimeUnit::Milliseconds => {
OffsetDateTime::from_unix_timestamp_nanos((val_i64 as i128) * 1_000_000).ok()
}
TimeUnit::Microseconds => {
OffsetDateTime::from_unix_timestamp_nanos((val_i64 as i128) * 1_000).ok()
}
TimeUnit::Nanoseconds => {
OffsetDateTime::from_unix_timestamp_nanos(val_i64 as i128).ok()
}
TimeUnit::Days => time::Date::from_julian_day((val_i64 + 2440588) as i32)
.ok()
.and_then(|date| date.with_hms(0, 0, 0).ok())
.map(|dt| dt.assume_utc()),
}
}
#[inline(always)]
pub(crate) fn datetime_to_i64(dt: time::OffsetDateTime, time_unit: TimeUnit) -> i64 {
match time_unit {
TimeUnit::Seconds => dt.unix_timestamp(),
TimeUnit::Milliseconds => {
dt.unix_timestamp() * 1_000i64 + (dt.nanosecond() / 1_000_000) as i64
}
TimeUnit::Microseconds => {
dt.unix_timestamp() * 1_000_000i64 + (dt.nanosecond() / 1_000) as i64
}
TimeUnit::Nanoseconds => {
dt.unix_timestamp() * 1_000_000_000i64 + dt.nanosecond() as i64
}
TimeUnit::Days => dt.unix_timestamp() / 86400i64,
}
}
fn value_to_datetime(&self, i: usize) -> Option<time::OffsetDateTime> {
if self.is_null(i) || i >= self.len() {
return None;
}
let val_i64 = self.data[i].to_i64()?;
Self::i64_to_datetime(val_i64, self.time_unit)
}
}
#[cfg(feature = "datetime_ops")]
use crate::DatetimeOps;
#[cfg(feature = "datetime_ops")]
impl<T: Integer + FromPrimitive> DatetimeOps for DatetimeArray<T> {
fn add_duration(&self, duration: Duration) -> Result<Self, MinarrowError> {
let mut result = self.clone();
let len = result.len();
let data = &self.data[..];
let duration_value: i64 =
match self.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..len {
if !self.is_null(i) {
let val = unsafe { *data.get_unchecked(i) };
if let Some(val_i64) = val.to_i64() {
if let Some(new_val_i64) = val_i64.checked_add(duration_value) {
if let Some(new_val_t) = T::from_i64(new_val_i64) {
result.set(i, new_val_t);
} else {
result.set_null(i);
}
} else {
result.set_null(i); }
} else {
result.set_null(i);
}
}
}
Ok(result)
}
fn sub_duration(&self, duration: Duration) -> Result<Self, MinarrowError> {
self.add_duration(-duration)
}
fn add_days(&self, days: i64) -> Result<Self, MinarrowError> {
self.add_duration(Duration::days(days))
}
fn add_months(&self, months: i32) -> Result<Self, MinarrowError> {
let mut result = self.clone();
let len = result.len();
let data = &self.data[..];
let time_unit = self.time_unit;
for i in 0..len {
if !self.is_null(i) {
let val = unsafe { *data.get_unchecked(i) };
if let Some(val_i64) = val.to_i64() {
if let Some(dt) = Self::i64_to_datetime(val_i64, time_unit) {
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;
if let Ok(new_month_enum) = time::Month::try_from(new_month) {
let days_in_month = new_month_enum.length(new_year);
let day = date.day().min(days_in_month);
if let Ok(new_date) =
time::Date::from_calendar_date(new_year, new_month_enum, day)
{
let new_dt_primitive = new_date.with_time(dt.time());
let new_dt = new_dt_primitive.assume_utc();
let new_val_i64 = Self::datetime_to_i64(new_dt, time_unit);
if let Some(new_val_t) = T::from_i64(new_val_i64) {
result.set(i, new_val_t);
} else {
result.set_null(i);
}
} else {
result.set_null(i);
}
} else {
result.set_null(i);
}
} else {
result.set_null(i);
}
} else {
result.set_null(i);
}
}
}
Ok(result)
}
fn add_years(&self, years: i32) -> Result<Self, MinarrowError> {
self.add_months(years * 12)
}
fn diff(&self, other: &Self, unit: TimeUnit) -> Result<IntegerArray<i64>, MinarrowError> {
if self.len() != other.len() {
return Err(MinarrowError::TypeError {
from: "DatetimeArray",
to: "IntegerArray",
message: Some(format!(
"Array lengths do not match: {} vs {}",
self.len(),
other.len()
)),
});
}
let mut result =
IntegerArray::with_capacity(self.len(), self.is_nullable() || other.is_nullable());
for i in 0..self.len() {
if self.is_null(i) || other.is_null(i) {
result.push_null();
} else {
let self_dt = self.value_to_datetime(i);
let other_dt = other.value_to_datetime(i);
if let (Some(a), Some(b)) = (self_dt, other_dt) {
let diff_duration = a - b;
let diff_value = match unit {
TimeUnit::Seconds => diff_duration.whole_seconds(),
TimeUnit::Milliseconds => diff_duration.whole_milliseconds() as i64,
TimeUnit::Microseconds => diff_duration.whole_microseconds() as i64,
TimeUnit::Nanoseconds => diff_duration.whole_nanoseconds() as i64,
TimeUnit::Days => diff_duration.whole_days(),
};
result.push(diff_value);
} else {
result.push_null();
}
}
}
Ok(result)
}
fn abs_diff(&self, other: &Self, unit: TimeUnit) -> Result<IntegerArray<i64>, MinarrowError> {
let diff = self.diff(other, unit)?;
let mut result = IntegerArray::with_capacity(diff.len(), diff.is_nullable());
for i in 0..diff.len() {
if diff.is_null(i) {
result.push_null();
} else {
result.push(diff.data[i].abs());
}
}
Ok(result)
}
fn is_before(&self, other: &Self) -> Result<BooleanArray<()>, MinarrowError> {
if self.len() != other.len() {
return Err(MinarrowError::TypeError {
from: "DatetimeArray",
to: "BooleanArray",
message: Some(format!(
"Array lengths do not match: {} vs {}",
self.len(),
other.len()
)),
});
}
let mut result = BooleanArray::with_capacity(self.len(), true);
for i in 0..self.len() {
if self.is_null(i) || other.is_null(i) {
result.push_null();
} else {
let self_dt = self.value_to_datetime(i);
let other_dt = other.value_to_datetime(i);
if let (Some(a), Some(b)) = (self_dt, other_dt) {
result.push(if a < b { true } else { false });
} else {
result.push_null();
}
}
}
Ok(result)
}
fn is_after(&self, other: &Self) -> Result<BooleanArray<()>, MinarrowError> {
if self.len() != other.len() {
return Err(MinarrowError::TypeError {
from: "DatetimeArray",
to: "BooleanArray",
message: Some(format!(
"Array lengths do not match: {} vs {}",
self.len(),
other.len()
)),
});
}
let mut result = BooleanArray::with_capacity(self.len(), true);
for i in 0..self.len() {
if self.is_null(i) || other.is_null(i) {
result.push_null();
} else {
let self_dt = self.value_to_datetime(i);
let other_dt = other.value_to_datetime(i);
if let (Some(a), Some(b)) = (self_dt, other_dt) {
result.push(if a > b { true } else { false });
} else {
result.push_null();
}
}
}
Ok(result)
}
fn between(&self, start: &Self, end: &Self) -> Result<BooleanArray<()>, MinarrowError> {
if self.len() != start.len() || self.len() != end.len() {
return Err(MinarrowError::TypeError {
from: "DatetimeArray",
to: "BooleanArray",
message: Some("Array lengths do not match".to_string()),
});
}
let mut result = BooleanArray::with_capacity(self.len(), true);
for i in 0..self.len() {
if self.is_null(i) || start.is_null(i) || end.is_null(i) {
result.push_null();
} else {
let self_dt = self.value_to_datetime(i);
let start_dt = start.value_to_datetime(i);
let end_dt = end.value_to_datetime(i);
if let (Some(val), Some(s), Some(e)) = (self_dt, start_dt, end_dt) {
result.push(if val >= s && val <= e { true } else { false });
} else {
result.push_null();
}
}
}
Ok(result)
}
fn year(&self) -> IntegerArray<i32> {
let mut result = IntegerArray::with_capacity(self.len(), self.is_nullable());
for i in 0..self.len() {
if self.is_null(i) {
result.push_null();
} else if let Some(dt) = self.value_to_datetime(i) {
result.push(dt.year());
} else {
result.push_null();
}
}
result
}
fn month(&self) -> IntegerArray<i32> {
let mut result = IntegerArray::with_capacity(self.len(), self.is_nullable());
for i in 0..self.len() {
if self.is_null(i) {
result.push_null();
} else if let Some(dt) = self.value_to_datetime(i) {
result.push(dt.month() as i32);
} else {
result.push_null();
}
}
result
}
fn day(&self) -> IntegerArray<i32> {
let mut result = IntegerArray::with_capacity(self.len(), self.is_nullable());
for i in 0..self.len() {
if self.is_null(i) {
result.push_null();
} else if let Some(dt) = self.value_to_datetime(i) {
result.push(dt.day() as i32);
} else {
result.push_null();
}
}
result
}
fn hour(&self) -> IntegerArray<i32> {
let mut result = IntegerArray::with_capacity(self.len(), self.is_nullable());
for i in 0..self.len() {
if self.is_null(i) {
result.push_null();
} else if let Some(dt) = self.value_to_datetime(i) {
result.push(dt.hour() as i32);
} else {
result.push_null();
}
}
result
}
fn minute(&self) -> IntegerArray<i32> {
let mut result = IntegerArray::with_capacity(self.len(), self.is_nullable());
for i in 0..self.len() {
if self.is_null(i) {
result.push_null();
} else if let Some(dt) = self.value_to_datetime(i) {
result.push(dt.minute() as i32);
} else {
result.push_null();
}
}
result
}
fn second(&self) -> IntegerArray<i32> {
let mut result = IntegerArray::with_capacity(self.len(), self.is_nullable());
for i in 0..self.len() {
if self.is_null(i) {
result.push_null();
} else if let Some(dt) = self.value_to_datetime(i) {
result.push(dt.second() as i32);
} else {
result.push_null();
}
}
result
}
fn weekday(&self) -> IntegerArray<i32> {
let mut result = IntegerArray::with_capacity(self.len(), self.is_nullable());
for i in 0..self.len() {
if self.is_null(i) {
result.push_null();
} else if let Some(dt) = self.value_to_datetime(i) {
result.push(dt.weekday().number_from_sunday() as i32);
} else {
result.push_null();
}
}
result
}
fn day_of_year(&self) -> IntegerArray<i32> {
let mut result = IntegerArray::with_capacity(self.len(), self.is_nullable());
for i in 0..self.len() {
if self.is_null(i) {
result.push_null();
} else if let Some(dt) = self.value_to_datetime(i) {
result.push(dt.ordinal() as i32);
} else {
result.push_null();
}
}
result
}
fn iso_week(&self) -> IntegerArray<i32> {
let mut result = IntegerArray::with_capacity(self.len(), self.is_nullable());
for i in 0..self.len() {
if self.is_null(i) {
result.push_null();
} else if let Some(dt) = self.value_to_datetime(i) {
result.push(dt.iso_week() as i32);
} else {
result.push_null();
}
}
result
}
fn quarter(&self) -> IntegerArray<i32> {
let mut result = IntegerArray::with_capacity(self.len(), self.is_nullable());
for i in 0..self.len() {
if self.is_null(i) {
result.push_null();
} else if let Some(dt) = self.value_to_datetime(i) {
let month = dt.month() as i32;
let quarter = ((month - 1) / 3) + 1;
result.push(quarter);
} else {
result.push_null();
}
}
result
}
fn week_of_year(&self) -> IntegerArray<i32> {
let mut result = IntegerArray::with_capacity(self.len(), self.is_nullable());
for i in 0..self.len() {
if self.is_null(i) {
result.push_null();
} else if let Some(dt) = self.value_to_datetime(i) {
let day_of_year = dt.ordinal() as i32;
let weekday = dt.weekday().number_from_sunday() as i32;
let week = (day_of_year + 7 - weekday) / 7;
result.push(week);
} else {
result.push_null();
}
}
result
}
fn is_leap_year(&self) -> BooleanArray<()> {
let mut result = BooleanArray::with_capacity(self.len(), self.is_nullable());
for i in 0..self.len() {
if self.is_null(i) {
result.push_null();
} else if let Some(dt) = self.value_to_datetime(i) {
let year = dt.year();
let is_leap = time::util::is_leap_year(year);
result.push(is_leap);
} else {
result.push_null();
}
}
result
}
fn truncate(&self, unit: &str) -> Result<Self, MinarrowError> {
let mut result = self.clone();
let len = result.len();
let time_unit = self.time_unit;
for i in 0..len {
if !self.is_null(i) {
if let Some(val_i64) = self.data[i].to_i64() {
if let Some(dt) = Self::i64_to_datetime(val_i64, time_unit) {
let truncated_dt = match unit {
"year" => {
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())
}
"month" => 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()),
"day" => dt.date().with_hms(0, 0, 0).ok().map(|pdt| pdt.assume_utc()),
"hour" => dt
.date()
.with_hms(dt.hour(), 0, 0)
.ok()
.map(|pdt| pdt.assume_utc()),
"minute" => dt
.date()
.with_hms(dt.hour(), dt.minute(), 0)
.ok()
.map(|pdt| pdt.assume_utc()),
"second" => dt
.date()
.with_hms(dt.hour(), dt.minute(), dt.second())
.ok()
.map(|pdt| pdt.assume_utc()),
_ => {
return Err(MinarrowError::TypeError {
from: "String",
to: "TimeUnit",
message: Some(format!("Invalid truncation unit: {}", unit)),
});
}
};
if let Some(new_dt) = truncated_dt {
let new_val_i64 = Self::datetime_to_i64(new_dt, time_unit);
if let Some(new_val_t) = T::from_i64(new_val_i64) {
result.set(i, new_val_t);
} else {
result.set_null(i);
}
} else {
result.set_null(i);
}
}
}
}
}
Ok(result)
}
fn us(&self) -> Self {
let mut result = self.clone();
if self.time_unit == TimeUnit::Nanoseconds {
let len = result.len();
let data = &self.data[..];
for i in 0..len {
if !self.is_null(i) {
let val = unsafe { *data.get_unchecked(i) };
if let Some(val_i64) = val.to_i64() {
let truncated = (val_i64 / 1_000) * 1_000;
if let Some(new_val_t) = T::from_i64(truncated) {
result.set(i, new_val_t);
}
}
}
}
}
result
}
fn ms(&self) -> Self {
let mut result = self.clone();
let len = result.len();
let data = &self.data[..];
match self.time_unit {
TimeUnit::Nanoseconds => {
for i in 0..len {
if !self.is_null(i) {
let val = unsafe { *data.get_unchecked(i) };
if let Some(val_i64) = val.to_i64() {
let truncated = (val_i64 / 1_000_000) * 1_000_000;
if let Some(new_val_t) = T::from_i64(truncated) {
result.set(i, new_val_t);
}
}
}
}
}
TimeUnit::Microseconds => {
for i in 0..len {
if !self.is_null(i) {
let val = unsafe { *data.get_unchecked(i) };
if let Some(val_i64) = val.to_i64() {
let truncated = (val_i64 / 1_000) * 1_000;
if let Some(new_val_t) = T::from_i64(truncated) {
result.set(i, new_val_t);
}
}
}
}
}
_ => {}
}
result
}
fn sec(&self) -> Self {
self.truncate("second").unwrap_or_else(|_| self.clone())
}
fn min(&self) -> Self {
self.truncate("minute").unwrap_or_else(|_| self.clone())
}
fn hr(&self) -> Self {
self.truncate("hour").unwrap_or_else(|_| self.clone())
}
fn week(&self) -> Self {
let mut result = self.clone();
let len = result.len();
let time_unit = self.time_unit;
for i in 0..len {
if !self.is_null(i) {
if let Some(val_i64) = self.data[i].to_i64() {
if let Some(dt) = Self::i64_to_datetime(val_i64, time_unit) {
let weekday = dt.weekday().number_from_sunday();
let days_to_sunday = (weekday - 1) as i64;
if let Some(week_start) =
dt.checked_sub(time::Duration::days(days_to_sunday))
{
if let Some(week_start_dt) = week_start.date().with_hms(0, 0, 0).ok() {
let truncated_dt = week_start_dt.assume_utc();
let new_val_i64 = Self::datetime_to_i64(truncated_dt, time_unit);
if let Some(new_val_t) = T::from_i64(new_val_i64) {
result.set(i, new_val_t);
} else {
result.set_null(i);
}
}
}
}
}
}
}
result
}
fn cast_time_unit(&self, new_unit: TimeUnit) -> Result<Self, MinarrowError> {
let mut result =
Self::with_capacity(self.len(), self.is_nullable(), Some(new_unit.clone()));
for i in 0..self.len() {
if self.is_null(i) {
result.push_null();
} else if let Some(dt) = self.value_to_datetime(i) {
let new_val: i64 = match new_unit {
TimeUnit::Seconds => dt.unix_timestamp(),
TimeUnit::Milliseconds => {
dt.unix_timestamp() * 1_000i64 + (dt.nanosecond() / 1_000_000) as i64
}
TimeUnit::Microseconds => {
dt.unix_timestamp() * 1_000_000i64 + (dt.nanosecond() / 1_000) as i64
}
TimeUnit::Nanoseconds => {
dt.unix_timestamp() * 1_000_000_000i64 + dt.nanosecond() as i64
}
TimeUnit::Days => dt.unix_timestamp() / 86400i64,
};
if let Some(new_val_t) = T::from_i64(new_val) {
result.push(new_val_t);
} else {
result.push_null();
}
} else {
result.push_null();
}
}
Ok(result)
}
}
#[cfg(all(test, feature = "datetime_ops"))]
mod tests {
use super::*;
use crate::DatetimeOps;
use time::Duration;
use vec64::vec64;
#[test]
fn test_add_duration() {
let arr = DatetimeArray::<i64>::from_slice(&[1000, 2000, 3000], Some(TimeUnit::Seconds));
let result = arr.add_duration(Duration::seconds(10)).unwrap();
assert_eq!(result.value(0), Some(1010));
assert_eq!(result.value(1), Some(2010));
assert_eq!(result.value(2), Some(3010));
}
#[test]
fn test_sub_duration() {
let arr = DatetimeArray::<i64>::from_slice(&[1000, 2000, 3000], Some(TimeUnit::Seconds));
let result = arr.sub_duration(Duration::seconds(10)).unwrap();
assert_eq!(result.value(0), Some(990));
assert_eq!(result.value(1), Some(1990));
assert_eq!(result.value(2), Some(2990));
}
#[test]
fn test_add_days() {
let arr = DatetimeArray::<i64>::from_slice(&[0, 86400, 172800], Some(TimeUnit::Seconds));
let result = arr.add_days(1).unwrap();
assert_eq!(result.value(0), Some(86400));
assert_eq!(result.value(1), Some(172800));
assert_eq!(result.value(2), Some(259200));
}
#[test]
fn test_is_before() {
let arr1 = DatetimeArray::<i64>::from_slice(&[1000, 2000, 3000], Some(TimeUnit::Seconds));
let arr2 = DatetimeArray::<i64>::from_slice(&[1500, 2500, 2500], Some(TimeUnit::Seconds));
let result = arr1.is_before(&arr2).unwrap();
assert_eq!(result.get(0), Some(true));
assert_eq!(result.get(1), Some(true));
assert_eq!(result.get(2), Some(false));
}
#[test]
fn test_is_after() {
let arr1 = DatetimeArray::<i64>::from_slice(&[1000, 2000, 3000], Some(TimeUnit::Seconds));
let arr2 = DatetimeArray::<i64>::from_slice(&[1500, 2500, 2500], Some(TimeUnit::Seconds));
let result = arr1.is_after(&arr2).unwrap();
assert_eq!(result.get(0), Some(false));
assert_eq!(result.get(1), Some(false));
assert_eq!(result.get(2), Some(true));
}
#[test]
fn test_component_extraction() {
let timestamp = 1_700_000_000i64;
let arr = DatetimeArray::<i64>::from_slice(&[timestamp], Some(TimeUnit::Seconds));
assert_eq!(arr.year().get(0), Some(2023));
assert_eq!(arr.month().get(0), Some(11));
assert_eq!(arr.day().get(0), Some(14));
assert_eq!(arr.hour().get(0), Some(22));
assert_eq!(arr.minute().get(0), Some(13));
assert_eq!(arr.second().get(0), Some(20));
}
#[test]
fn test_add_months() {
let timestamp = 1_673_740_800i64; let arr = DatetimeArray::<i64>::from_slice(&[timestamp], Some(TimeUnit::Seconds));
let result = arr.add_months(2).unwrap();
assert_eq!(result.year().get(0), Some(2023));
assert_eq!(result.month().get(0), Some(3));
assert_eq!(result.day().get(0), Some(15));
}
#[test]
fn test_add_months_overflow_year() {
let timestamp = 1_700_006_400i64; let arr = DatetimeArray::<i64>::from_slice(&[timestamp], Some(TimeUnit::Seconds));
let result = arr.add_months(3).unwrap();
assert_eq!(result.year().get(0), Some(2024));
assert_eq!(result.month().get(0), Some(2));
assert_eq!(result.day().get(0), Some(15));
}
#[test]
fn test_add_months_end_of_month() {
let timestamp = 1_675_123_200i64; let arr = DatetimeArray::<i64>::from_slice(&[timestamp], Some(TimeUnit::Seconds));
let result = arr.add_months(1).unwrap();
assert_eq!(result.year().get(0), Some(2023));
assert_eq!(result.month().get(0), Some(2));
assert_eq!(result.day().get(0), Some(28)); }
#[test]
fn test_add_years() {
let timestamp = 1_700_000_000i64; let arr = DatetimeArray::<i64>::from_slice(&[timestamp], Some(TimeUnit::Seconds));
let result = arr.add_years(2).unwrap();
assert_eq!(result.year().get(0), Some(2025));
assert_eq!(result.month().get(0), Some(11));
assert_eq!(result.day().get(0), Some(14));
}
#[test]
fn test_diff() {
let arr1 = DatetimeArray::<i64>::from_slice(&[1000, 2000, 3000], Some(TimeUnit::Seconds));
let arr2 = DatetimeArray::<i64>::from_slice(&[1500, 2500, 2500], Some(TimeUnit::Seconds));
let result = arr1.diff(&arr2, TimeUnit::Seconds).unwrap();
assert_eq!(result.get(0), Some(-500));
assert_eq!(result.get(1), Some(-500));
assert_eq!(result.get(2), Some(500));
}
#[test]
fn test_abs_diff() {
let arr1 = DatetimeArray::<i64>::from_slice(&[1000, 2000, 3000], Some(TimeUnit::Seconds));
let arr2 = DatetimeArray::<i64>::from_slice(&[1500, 2500, 2500], Some(TimeUnit::Seconds));
let result = arr1.abs_diff(&arr2, TimeUnit::Seconds).unwrap();
assert_eq!(result.get(0), Some(500));
assert_eq!(result.get(1), Some(500));
assert_eq!(result.get(2), Some(500));
}
#[test]
fn test_diff_different_units() {
let arr1 =
DatetimeArray::<i64>::from_slice(&[1_000_000, 2_000_000], Some(TimeUnit::Milliseconds));
let arr2 =
DatetimeArray::<i64>::from_slice(&[1_500_000, 2_500_000], Some(TimeUnit::Milliseconds));
let result = arr1.diff(&arr2, TimeUnit::Milliseconds).unwrap();
assert_eq!(result.get(0), Some(-500_000));
assert_eq!(result.get(1), Some(-500_000));
}
#[test]
fn test_between() {
let arr =
DatetimeArray::<i64>::from_slice(&[1000, 2000, 3000, 4000], Some(TimeUnit::Seconds));
let start =
DatetimeArray::<i64>::from_slice(&[1500, 1500, 1500, 1500], Some(TimeUnit::Seconds));
let end =
DatetimeArray::<i64>::from_slice(&[3500, 3500, 3500, 3500], Some(TimeUnit::Seconds));
let result = arr.between(&start, &end).unwrap();
assert_eq!(result.get(0), Some(false)); assert_eq!(result.get(1), Some(true)); assert_eq!(result.get(2), Some(true)); assert_eq!(result.get(3), Some(false)); }
#[test]
fn test_weekday() {
let timestamp = 1_700_000_000i64;
let arr = DatetimeArray::<i64>::from_slice(&[timestamp], Some(TimeUnit::Seconds));
assert_eq!(arr.weekday().get(0), Some(3)); }
#[test]
fn test_weekday_all_days() {
let timestamps = vec![
1_699_747_200i64, 1_699_833_600i64, 1_699_920_000i64, 1_700_006_400i64, 1_700_092_800i64, 1_700_179_200i64, 1_700_265_600i64, ];
let arr = DatetimeArray::<i64>::from_slice(×tamps, Some(TimeUnit::Seconds));
let weekdays = arr.weekday();
assert_eq!(weekdays.get(0), Some(1)); assert_eq!(weekdays.get(1), Some(2)); assert_eq!(weekdays.get(2), Some(3)); assert_eq!(weekdays.get(3), Some(4)); assert_eq!(weekdays.get(4), Some(5)); assert_eq!(weekdays.get(5), Some(6)); assert_eq!(weekdays.get(6), Some(7)); }
#[test]
fn test_day_of_year() {
let timestamp = 1_700_000_000i64;
let arr = DatetimeArray::<i64>::from_slice(&[timestamp], Some(TimeUnit::Seconds));
assert_eq!(arr.day_of_year().get(0), Some(318));
}
#[test]
fn test_iso_week() {
let timestamp = 1_700_000_000i64;
let arr = DatetimeArray::<i64>::from_slice(&[timestamp], Some(TimeUnit::Seconds));
assert_eq!(arr.iso_week().get(0), Some(46));
}
#[test]
fn test_quarter() {
let timestamps = vec![
1_672_531_200i64, 1_680_307_200i64, 1_688_169_600i64, 1_696_118_400i64, ];
let arr = DatetimeArray::<i64>::from_slice(×tamps, Some(TimeUnit::Seconds));
let quarters = arr.quarter();
assert_eq!(quarters.get(0), Some(1));
assert_eq!(quarters.get(1), Some(2));
assert_eq!(quarters.get(2), Some(3));
assert_eq!(quarters.get(3), Some(4));
}
#[test]
fn test_week_of_year() {
let timestamp = 1_700_000_000i64;
let arr = DatetimeArray::<i64>::from_slice(&[timestamp], Some(TimeUnit::Seconds));
let week = arr.week_of_year().get(0).unwrap();
assert!(week >= 45 && week <= 47); }
#[test]
fn test_is_leap_year() {
let timestamps = vec![
1_672_531_200i64, 1_704_067_200i64, ];
let arr = DatetimeArray::<i64>::from_slice(×tamps, Some(TimeUnit::Seconds));
let leap_years = arr.is_leap_year();
assert_eq!(leap_years.get(0), Some(false));
assert_eq!(leap_years.get(1), Some(true));
}
#[test]
fn test_truncate_to_year() {
let timestamp = 1_700_000_000i64; let arr = DatetimeArray::<i64>::from_slice(&[timestamp], Some(TimeUnit::Seconds));
let result = arr.truncate("year").unwrap();
assert_eq!(result.year().get(0), Some(2023));
assert_eq!(result.month().get(0), Some(1));
assert_eq!(result.day().get(0), Some(1));
assert_eq!(result.hour().get(0), Some(0));
assert_eq!(result.minute().get(0), Some(0));
assert_eq!(result.second().get(0), Some(0));
}
#[test]
fn test_truncate_to_month() {
let timestamp = 1_700_000_000i64; let arr = DatetimeArray::<i64>::from_slice(&[timestamp], Some(TimeUnit::Seconds));
let result = arr.truncate("month").unwrap();
assert_eq!(result.year().get(0), Some(2023));
assert_eq!(result.month().get(0), Some(11));
assert_eq!(result.day().get(0), Some(1));
assert_eq!(result.hour().get(0), Some(0));
}
#[test]
fn test_truncate_to_day() {
let timestamp = 1_700_000_000i64; let arr = DatetimeArray::<i64>::from_slice(&[timestamp], Some(TimeUnit::Seconds));
let result = arr.truncate("day").unwrap();
assert_eq!(result.hour().get(0), Some(0));
assert_eq!(result.minute().get(0), Some(0));
assert_eq!(result.second().get(0), Some(0));
}
#[test]
fn test_truncate_to_hour() {
let timestamp = 1_700_000_000i64; let arr = DatetimeArray::<i64>::from_slice(&[timestamp], Some(TimeUnit::Seconds));
let result = arr.truncate("hour").unwrap();
assert_eq!(result.hour().get(0), Some(22));
assert_eq!(result.minute().get(0), Some(0));
assert_eq!(result.second().get(0), Some(0));
}
#[test]
fn test_truncate_shorthand_methods() {
let timestamp = 1_700_000_000i64;
let arr = DatetimeArray::<i64>::from_slice(&[timestamp], Some(TimeUnit::Seconds));
let sec_result = arr.sec();
let min_result = arr.min();
let hr_result = arr.hr();
assert_eq!(sec_result.second().get(0), Some(20));
assert_eq!(min_result.minute().get(0), Some(13));
assert_eq!(min_result.second().get(0), Some(0));
assert_eq!(hr_result.hour().get(0), Some(22));
assert_eq!(hr_result.minute().get(0), Some(0));
}
#[test]
fn test_truncate_week() {
let timestamp = 1_700_000_000i64; let arr = DatetimeArray::<i64>::from_slice(&[timestamp], Some(TimeUnit::Seconds));
let result = arr.week();
assert_eq!(result.day().get(0), Some(12)); assert_eq!(result.hour().get(0), Some(0));
assert_eq!(result.minute().get(0), Some(0));
}
#[test]
fn test_truncate_subsecond_us() {
let arr = DatetimeArray::<i64>::from_slice(&[1_234_567], Some(TimeUnit::Nanoseconds));
let result = arr.us();
assert_eq!(result.value(0), Some(1_234_000));
}
#[test]
fn test_truncate_subsecond_ms() {
let arr = DatetimeArray::<i64>::from_slice(&[1_234_567_890], Some(TimeUnit::Nanoseconds));
let result = arr.ms();
assert_eq!(result.value(0), Some(1_234_000_000));
}
#[test]
fn test_cast_time_unit_seconds_to_milliseconds() {
let arr = DatetimeArray::<i64>::from_slice(&[1, 2, 3], Some(TimeUnit::Seconds));
let result = arr.cast_time_unit(TimeUnit::Milliseconds).unwrap();
assert_eq!(result.value(0), Some(1_000));
assert_eq!(result.value(1), Some(2_000));
assert_eq!(result.value(2), Some(3_000));
assert_eq!(result.time_unit, TimeUnit::Milliseconds);
}
#[test]
fn test_cast_time_unit_milliseconds_to_seconds() {
let arr =
DatetimeArray::<i64>::from_slice(&[1_000, 2_000, 3_000], Some(TimeUnit::Milliseconds));
let result = arr.cast_time_unit(TimeUnit::Seconds).unwrap();
assert_eq!(result.value(0), Some(1));
assert_eq!(result.value(1), Some(2));
assert_eq!(result.value(2), Some(3));
assert_eq!(result.time_unit, TimeUnit::Seconds);
}
#[test]
fn test_cast_time_unit_preserves_datetime() {
let timestamp = 1_700_000_000i64;
let arr = DatetimeArray::<i64>::from_slice(&[timestamp], Some(TimeUnit::Seconds));
let as_ms = arr.cast_time_unit(TimeUnit::Milliseconds).unwrap();
let back_to_sec = as_ms.cast_time_unit(TimeUnit::Seconds).unwrap();
assert_eq!(back_to_sec.value(0), Some(timestamp));
}
#[test]
fn test_operations_with_nulls() {
use crate::Bitmask;
let mask = Bitmask::from_bools(&[true, false, true]);
let arr = DatetimeArray::new(
vec64![1000i64, 2000, 3000],
Some(mask),
Some(TimeUnit::Seconds),
);
let result = arr.add_duration(Duration::seconds(10)).unwrap();
assert_eq!(result.value(0), Some(1010));
assert_eq!(result.value(1), None); assert_eq!(result.value(2), Some(3010));
}
#[test]
fn test_comparison_with_nulls() {
use crate::Bitmask;
let mask1 = Bitmask::from_bools(&[true, false, true]);
let arr1 = DatetimeArray::new(
vec64![1000i64, 2000, 3000],
Some(mask1),
Some(TimeUnit::Seconds),
);
let arr2 = DatetimeArray::<i64>::from_slice(&[1500, 2500, 2500], Some(TimeUnit::Seconds));
let result = arr1.is_before(&arr2).unwrap();
assert_eq!(result.get(0), Some(true));
assert_eq!(result.get(1), None); assert_eq!(result.get(2), Some(false));
}
#[test]
fn test_diff_length_mismatch() {
let arr1 = DatetimeArray::<i64>::from_slice(&[1000, 2000], Some(TimeUnit::Seconds));
let arr2 = DatetimeArray::<i64>::from_slice(&[1500], Some(TimeUnit::Seconds));
let result = arr1.diff(&arr2, TimeUnit::Seconds);
assert!(result.is_err());
}
#[test]
fn test_component_extraction_with_nulls() {
use crate::Bitmask;
let mask = Bitmask::from_bools(&[true, false]);
let arr = DatetimeArray::new(
vec64![1_700_000_000i64, 1_700_086_400],
Some(mask),
Some(TimeUnit::Seconds),
);
let years = arr.year();
assert_eq!(years.get(0), Some(2023));
assert_eq!(years.get(1), None);
}
#[test]
fn test_leap_year_feb_29() {
let timestamp = 1_709_164_800i64;
let arr = DatetimeArray::<i64>::from_slice(&[timestamp], Some(TimeUnit::Seconds));
assert_eq!(arr.year().get(0), Some(2024));
assert_eq!(arr.month().get(0), Some(2));
assert_eq!(arr.day().get(0), Some(29));
assert_eq!(arr.is_leap_year().get(0), Some(true));
}
#[test]
fn test_add_months_leap_year_feb_29() {
let timestamp = 1_709_164_800i64; let arr = DatetimeArray::<i64>::from_slice(&[timestamp], Some(TimeUnit::Seconds));
let result = arr.add_months(1).unwrap();
assert_eq!(result.year().get(0), Some(2024));
assert_eq!(result.month().get(0), Some(3));
assert_eq!(result.day().get(0), Some(29));
}
#[test]
fn test_add_months_leap_year_to_non_leap() {
let timestamp = 1_709_164_800i64; let arr = DatetimeArray::<i64>::from_slice(&[timestamp], Some(TimeUnit::Seconds));
let result = arr.add_months(12).unwrap();
assert_eq!(result.year().get(0), Some(2025));
assert_eq!(result.month().get(0), Some(2));
assert_eq!(result.day().get(0), Some(28)); assert_eq!(result.is_leap_year().get(0), Some(false));
}
#[test]
fn test_add_years_leap_year() {
let timestamp = 1_709_164_800i64; let arr = DatetimeArray::<i64>::from_slice(&[timestamp], Some(TimeUnit::Seconds));
let result = arr.add_years(4).unwrap();
assert_eq!(result.year().get(0), Some(2028));
assert_eq!(result.month().get(0), Some(2));
assert_eq!(result.day().get(0), Some(29));
}
#[test]
fn test_add_years_leap_to_non_leap() {
let timestamp = 1_709_164_800i64; let arr = DatetimeArray::<i64>::from_slice(&[timestamp], Some(TimeUnit::Seconds));
let result = arr.add_years(1).unwrap();
assert_eq!(result.year().get(0), Some(2025));
assert_eq!(result.month().get(0), Some(2));
assert_eq!(result.day().get(0), Some(28)); }
#[test]
fn test_year_boundary_dec_31_to_jan_1() {
let timestamp = 1_704_067_199i64; let arr = DatetimeArray::<i64>::from_slice(&[timestamp], Some(TimeUnit::Seconds));
let result = arr.add_days(1).unwrap();
assert_eq!(result.year().get(0), Some(2024));
assert_eq!(result.month().get(0), Some(1));
assert_eq!(result.day().get(0), Some(1));
}
#[test]
fn test_month_boundary_jan_31_to_feb_1() {
let timestamp = 1_706_659_200i64; let arr = DatetimeArray::<i64>::from_slice(&[timestamp], Some(TimeUnit::Seconds));
let result = arr.add_days(1).unwrap();
assert_eq!(result.year().get(0), Some(2024));
assert_eq!(result.month().get(0), Some(2));
assert_eq!(result.day().get(0), Some(1));
}
#[test]
fn test_negative_months() {
let timestamp = 1_678_838_400i64; let arr = DatetimeArray::<i64>::from_slice(&[timestamp], Some(TimeUnit::Seconds));
let result = arr.add_months(-3).unwrap();
assert_eq!(result.year().get(0), Some(2022));
assert_eq!(result.month().get(0), Some(12));
assert_eq!(result.day().get(0), Some(15));
}
#[test]
fn test_negative_years() {
let timestamp = 1_700_000_000i64;
let arr = DatetimeArray::<i64>::from_slice(&[timestamp], Some(TimeUnit::Seconds));
let result = arr.add_years(-5).unwrap();
assert_eq!(result.year().get(0), Some(2018));
assert_eq!(result.month().get(0), Some(11));
assert_eq!(result.day().get(0), Some(14));
}
#[test]
fn test_century_leap_year_2000() {
let timestamp = 951_868_800i64; let arr = DatetimeArray::<i64>::from_slice(&[timestamp], Some(TimeUnit::Seconds));
let prev_day = arr.add_days(-1).unwrap();
assert_eq!(prev_day.year().get(0), Some(2000));
assert_eq!(prev_day.month().get(0), Some(2));
assert_eq!(prev_day.day().get(0), Some(29)); }
#[test]
fn test_century_non_leap_year_1900() {
let timestamp = -2_203_891_200i64; let arr = DatetimeArray::<i64>::from_slice(&[timestamp], Some(TimeUnit::Seconds));
let prev_day = arr.add_days(-1).unwrap();
assert_eq!(prev_day.year().get(0), Some(1900));
assert_eq!(prev_day.month().get(0), Some(2));
assert_eq!(prev_day.day().get(0), Some(28)); }
#[test]
fn test_extreme_timestamp_min() {
let timestamp = -62_135_596_800i64; let arr = DatetimeArray::<i64>::from_slice(&[timestamp], Some(TimeUnit::Seconds));
assert_eq!(arr.year().get(0), Some(1));
assert_eq!(arr.month().get(0), Some(1));
assert_eq!(arr.day().get(0), Some(1));
}
#[test]
fn test_extreme_timestamp_far_future() {
let timestamp = 32_503_680_000i64; let arr = DatetimeArray::<i64>::from_slice(&[timestamp], Some(TimeUnit::Seconds));
assert_eq!(arr.year().get(0), Some(3000));
assert_eq!(arr.month().get(0), Some(1));
assert_eq!(arr.day().get(0), Some(1));
}
#[test]
fn test_different_time_units_precision() {
let arr_sec = DatetimeArray::<i64>::from_slice(&[1_700_000_000], Some(TimeUnit::Seconds));
let arr_ms = arr_sec.cast_time_unit(TimeUnit::Milliseconds).unwrap();
let arr_us = arr_ms.cast_time_unit(TimeUnit::Microseconds).unwrap();
let arr_ns = arr_us.cast_time_unit(TimeUnit::Nanoseconds).unwrap();
let back_to_sec = arr_ns.cast_time_unit(TimeUnit::Seconds).unwrap();
assert_eq!(back_to_sec.value(0), Some(1_700_000_000));
}
#[test]
fn test_week_start_on_sunday() {
let timestamp = 1_699_747_200i64; let arr = DatetimeArray::<i64>::from_slice(&[timestamp], Some(TimeUnit::Seconds));
let result = arr.week();
assert_eq!(result.day().get(0), Some(12)); assert_eq!(result.hour().get(0), Some(0));
}
#[test]
fn test_week_start_on_saturday() {
let timestamp = 1_700_265_600i64; let arr = DatetimeArray::<i64>::from_slice(&[timestamp], Some(TimeUnit::Seconds));
let result = arr.week();
assert_eq!(result.day().get(0), Some(12)); }
#[test]
fn test_add_months_varying_lengths() {
let jan_31 = 1_675_123_200i64; let arr = DatetimeArray::<i64>::from_slice(&[jan_31], Some(TimeUnit::Seconds));
let feb = arr.add_months(1).unwrap();
assert_eq!(feb.month().get(0), Some(2));
assert_eq!(feb.day().get(0), Some(28));
let mar = feb.add_months(1).unwrap();
assert_eq!(mar.month().get(0), Some(3));
assert_eq!(mar.day().get(0), Some(28));
}
}