use chrono::{DateTime, Datelike, NaiveDate, Timelike, Utc};
use glaredb_error::{DbError, Result, not_implemented};
use crate::arrays::array::Array;
use crate::arrays::array::physical_type::{PhysicalI32, PhysicalI64};
use crate::arrays::datatype::{DataTypeId, TimeUnit};
use crate::arrays::executor::OutBuffer;
use crate::arrays::executor::scalar::UnaryExecutor;
use crate::arrays::scalar::decimal::{Decimal64Type, DecimalType};
use crate::util::iter::IntoExactSizeIterator;
pub const EPOCH_NAIVE_DATE: NaiveDate = match NaiveDate::from_ymd_opt(1970, 1, 1) {
Some(date) => date,
_ => unreachable!(),
};
pub const EPOCH_DAYS_FROM_CE: i32 = 719_163;
pub const SECONDS_IN_DAY: i64 = 86_400;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DatePart {
Century,
Day,
Decade,
DayOfWeek,
DayOfYear,
Epoch,
Hour,
IsoDayOfWeek,
IsoYear,
Julian,
Microseconds,
Millenium,
Milliseconds,
Minute,
Month,
Quarter,
Second,
Timezone,
TimezoneHour,
TimezoneMinute,
Week,
Year,
}
pub fn extract_date_part(
part: DatePart,
arr: &Array,
sel: impl IntoExactSizeIterator<Item = usize>,
out: &mut Array,
) -> Result<()> {
let datatype = arr.datatype();
match datatype.id {
DataTypeId::Date32 => match part {
DatePart::Microseconds => date32_extract_with_fn(arr, sel, extract_microseconds, out),
DatePart::Milliseconds => date32_extract_with_fn(arr, sel, extract_milliseconds, out),
DatePart::Second => date32_extract_with_fn(arr, sel, extract_seconds, out),
DatePart::Minute => date32_extract_with_fn(arr, sel, extract_minute, out),
DatePart::DayOfWeek => date32_extract_with_fn(arr, sel, extract_day_of_week, out),
DatePart::IsoDayOfWeek => {
date32_extract_with_fn(arr, sel, extract_iso_day_of_week, out)
}
DatePart::Day => date32_extract_with_fn(arr, sel, extract_day, out),
DatePart::Month => date32_extract_with_fn(arr, sel, extract_month, out),
DatePart::Quarter => date32_extract_with_fn(arr, sel, extract_quarter, out),
DatePart::Year => date32_extract_with_fn(arr, sel, extract_year, out),
other => not_implemented!("Extract {other:?} from {datatype}"),
},
DataTypeId::Date64 => match part {
DatePart::Microseconds => date64_extract_with_fn(arr, sel, extract_microseconds, out),
DatePart::Milliseconds => date64_extract_with_fn(arr, sel, extract_milliseconds, out),
DatePart::Second => date64_extract_with_fn(arr, sel, extract_seconds, out),
DatePart::Minute => date64_extract_with_fn(arr, sel, extract_minute, out),
DatePart::DayOfWeek => date64_extract_with_fn(arr, sel, extract_day_of_week, out),
DatePart::IsoDayOfWeek => {
date64_extract_with_fn(arr, sel, extract_iso_day_of_week, out)
}
DatePart::Day => date64_extract_with_fn(arr, sel, extract_day, out),
DatePart::Month => date64_extract_with_fn(arr, sel, extract_month, out),
DatePart::Quarter => date64_extract_with_fn(arr, sel, extract_quarter, out),
DatePart::Year => date64_extract_with_fn(arr, sel, extract_year, out),
other => not_implemented!("Extract {other:?} from {datatype}"),
},
DataTypeId::Timestamp => {
let m = datatype.try_get_timestamp_type_meta()?;
match part {
DatePart::Microseconds => {
timestamp_extract_with_fn(m.unit, arr, sel, extract_microseconds, out)
}
DatePart::Milliseconds => {
timestamp_extract_with_fn(m.unit, arr, sel, extract_milliseconds, out)
}
DatePart::Second => {
timestamp_extract_with_fn(m.unit, arr, sel, extract_seconds, out)
}
DatePart::Minute => {
timestamp_extract_with_fn(m.unit, arr, sel, extract_minute, out)
}
DatePart::DayOfWeek => {
timestamp_extract_with_fn(m.unit, arr, sel, extract_day_of_week, out)
}
DatePart::IsoDayOfWeek => {
timestamp_extract_with_fn(m.unit, arr, sel, extract_iso_day_of_week, out)
}
DatePart::Day => timestamp_extract_with_fn(m.unit, arr, sel, extract_day, out),
DatePart::Month => timestamp_extract_with_fn(m.unit, arr, sel, extract_month, out),
DatePart::Quarter => {
timestamp_extract_with_fn(m.unit, arr, sel, extract_quarter, out)
}
DatePart::Year => timestamp_extract_with_fn(m.unit, arr, sel, extract_year, out),
other => not_implemented!("Extract {other:?} from {datatype}"),
}
}
other => Err(DbError::new(format!(
"Unable to extract date part for array with data type {other}"
))),
}
}
fn timestamp_extract_with_fn<F>(
unit: TimeUnit,
arr: &Array,
sel: impl IntoExactSizeIterator<Item = usize>,
f: F,
out: &mut Array,
) -> Result<()>
where
F: Fn(DateTime<Utc>) -> i64,
{
match unit {
TimeUnit::Second => timestamp_extract_with_fn_and_datetime_builder(
arr,
sel,
f,
|val| DateTime::from_timestamp(val, 0).unwrap_or_default(),
out,
),
TimeUnit::Millisecond => timestamp_extract_with_fn_and_datetime_builder(
arr,
sel,
f,
|val| DateTime::from_timestamp_millis(val).unwrap_or_default(),
out,
),
TimeUnit::Microsecond => timestamp_extract_with_fn_and_datetime_builder(
arr,
sel,
f,
|val| DateTime::from_timestamp_micros(val).unwrap_or_default(),
out,
),
TimeUnit::Nanosecond => timestamp_extract_with_fn_and_datetime_builder(
arr,
sel,
f,
DateTime::from_timestamp_nanos,
out,
),
}
}
fn timestamp_extract_with_fn_and_datetime_builder<F, B>(
arr: &Array,
sel: impl IntoExactSizeIterator<Item = usize>,
f: F,
builder: B,
out: &mut Array,
) -> Result<()>
where
B: Fn(i64) -> DateTime<Utc>,
F: Fn(DateTime<Utc>) -> i64,
{
UnaryExecutor::execute::<PhysicalI64, PhysicalI64, _>(
arr,
sel,
OutBuffer::from_array(out)?,
|&val, buf| {
let date = builder(val);
buf.put(&f(date))
},
)
}
fn date32_extract_with_fn<F>(
arr: &Array,
sel: impl IntoExactSizeIterator<Item = usize>,
f: F,
out: &mut Array,
) -> Result<()>
where
F: Fn(DateTime<Utc>) -> i64,
{
UnaryExecutor::execute::<PhysicalI32, PhysicalI64, _>(
arr,
sel,
OutBuffer::from_array(out)?,
|&val, buf| {
let date = DateTime::from_timestamp(val as i64 * SECONDS_IN_DAY, 0).unwrap_or_default();
buf.put(&f(date))
},
)
}
fn date64_extract_with_fn<F>(
arr: &Array,
sel: impl IntoExactSizeIterator<Item = usize>,
f: F,
out: &mut Array,
) -> Result<()>
where
F: Fn(DateTime<Utc>) -> i64,
{
UnaryExecutor::execute::<PhysicalI64, PhysicalI64, _>(
arr,
sel,
OutBuffer::from_array(out)?,
|&val, buf| {
let date = DateTime::from_timestamp_millis(val).unwrap_or_default();
buf.put(&f(date))
},
)
}
const WHOLE_INT_SCALE: i64 = i64::pow(10, Decimal64Type::DEFAULT_SCALE as u32);
fn extract_quarter<T: Datelike + Timelike>(val: T) -> i64 {
((val.month0() / 3) + 1) as i64 * WHOLE_INT_SCALE
}
fn extract_year<T: Datelike + Timelike>(val: T) -> i64 {
(val.year() as i64) * WHOLE_INT_SCALE
}
fn extract_month<T: Datelike + Timelike>(val: T) -> i64 {
(val.month() as i64) * WHOLE_INT_SCALE
}
fn extract_day<T: Datelike + Timelike>(val: T) -> i64 {
(val.day() as i64) * WHOLE_INT_SCALE
}
fn extract_day_of_week<T: Datelike + Timelike>(val: T) -> i64 {
((val.weekday().number_from_sunday() as i64) - 1) * WHOLE_INT_SCALE
}
fn extract_iso_day_of_week<T: Datelike + Timelike>(val: T) -> i64 {
(val.weekday().number_from_monday() as i64) * WHOLE_INT_SCALE
}
fn extract_minute<T: Datelike + Timelike>(val: T) -> i64 {
(val.minute() as i64) * WHOLE_INT_SCALE
}
fn extract_seconds<T: Datelike + Timelike>(val: T) -> i64 {
(val.nanosecond() / 1_000_000) as i64
}
fn extract_milliseconds<T: Datelike + Timelike>(val: T) -> i64 {
(val.nanosecond() / 1_000) as i64
}
fn extract_microseconds<T: Datelike + Timelike>(val: T) -> i64 {
val.nanosecond() as i64
}