From 317160d3f48cd2985765ffba4ecdf4b29058bfca Mon Sep 17 00:00:00 2001 From: coldWater Date: Tue, 21 Jul 2026 09:51:00 +0800 Subject: [PATCH] refactor(query): move date helpers into timestamp functions --- src/query/expression/src/types/date.rs | 12 +- src/query/expression/src/types/timestamp.rs | 31 +- src/query/expression/src/types/variant.rs | 2 +- src/query/expression/src/utils/date_helper.rs | 2257 ----------------- src/query/expression/src/utils/display.rs | 4 +- src/query/expression/src/utils/mod.rs | 1 - src/query/formats/src/field_encoder/bytes.rs | 2 +- src/query/formats/src/field_encoder/string.rs | 4 +- src/query/formats/src/output_format/json.rs | 7 +- src/query/functions/benches/bench.rs | 4 +- .../scalars/timestamp/src/date_arithmetic.rs | 1872 ++++++++++++++ .../scalars/timestamp/src/date_conversion.rs | 215 ++ .../src/scalars/timestamp/src/date_extract.rs | 615 +++++ .../src/scalars/timestamp/src/date_format.rs | 313 +++ .../src/scalars/timestamp/src/date_round.rs | 398 +++ .../scalars/timestamp/src/date_time_slice.rs | 312 +++ .../src/scalars/timestamp/src/datetime.rs | 1742 +------------ .../src/scalars/timestamp/src/interval.rs | 28 +- .../src/scalars/timestamp/src/lib.rs | 8 + .../transforms/window/transform_window.rs | 4 +- .../transforms/transform_dictionary.rs | 2 +- .../tests/it/servers/http/json_block.rs | 2 +- .../src/system_tables/task_history.rs | 16 +- .../src/table_functions/task_history.rs | 33 +- 24 files changed, 3843 insertions(+), 4041 deletions(-) delete mode 100644 src/query/expression/src/utils/date_helper.rs create mode 100644 src/query/functions/src/scalars/timestamp/src/date_arithmetic.rs create mode 100644 src/query/functions/src/scalars/timestamp/src/date_conversion.rs create mode 100644 src/query/functions/src/scalars/timestamp/src/date_extract.rs create mode 100644 src/query/functions/src/scalars/timestamp/src/date_format.rs create mode 100644 src/query/functions/src/scalars/timestamp/src/date_round.rs create mode 100644 src/query/functions/src/scalars/timestamp/src/date_time_slice.rs diff --git a/src/query/expression/src/types/date.rs b/src/query/expression/src/types/date.rs index 560f671563c68..e798a00b66563 100644 --- a/src/query/expression/src/types/date.rs +++ b/src/query/expression/src/types/date.rs @@ -20,6 +20,7 @@ use databend_common_column::buffer::Buffer; use databend_common_exception::ErrorCode; use databend_common_io::cursor_ext::BufferReadDateTimeExt; use databend_common_io::cursor_ext::ReadBytesExt; +use jiff::SignedDuration; use jiff::civil::Date; use jiff::fmt::strtime; use jiff::tz::TimeZone; @@ -32,7 +33,6 @@ use super::SimpleValueType; use super::number::SimpleDomain; use crate::ColumnBuilder; use crate::ScalarRef; -use crate::date_helper::DateConverter; use crate::property::Domain; use crate::values::Column; use crate::values::Scalar; @@ -45,6 +45,11 @@ pub const DATE_MIN: i32 = -719162; /// 9999-12-31 pub const DATE_MAX: i32 = 2932896; +pub fn date_from_days(days: impl AsPrimitive) -> Date { + let duration = SignedDuration::from_hours(days.as_() * 24); + Date::constant(1970, 1, 1).checked_add(duration).unwrap() +} + /// Check if date is within range. /// /// If days is invalid convert to DATE_MIN. #[inline] @@ -161,7 +166,6 @@ pub fn string_to_date( } #[inline] -pub fn date_to_string(date: impl AsPrimitive, tz: &TimeZone) -> impl Display { - let res = date.as_().to_date(tz); - strtime::format(DATE_FORMAT, res).unwrap() +pub fn date_to_string(date: impl AsPrimitive) -> String { + strtime::format(DATE_FORMAT, date_from_days(date)).unwrap() } diff --git a/src/query/expression/src/types/timestamp.rs b/src/query/expression/src/types/timestamp.rs index bfc2d9cf4c989..4fcae60924452 100644 --- a/src/query/expression/src/types/timestamp.rs +++ b/src/query/expression/src/types/timestamp.rs @@ -21,9 +21,11 @@ use databend_common_exception::ErrorCode; use databend_common_io::cursor_ext::BufferReadDateTimeExt; use databend_common_io::cursor_ext::DateTimeResType; use databend_common_io::cursor_ext::ReadBytesExt; +use jiff::Timestamp; use jiff::Zoned; use jiff::fmt::strtime; use jiff::tz::TimeZone; +use num_traits::AsPrimitive; use super::ArgType; use super::DataType; @@ -33,7 +35,6 @@ use super::number::SimpleDomain; use crate::ColumnBuilder; use crate::ScalarRef; use crate::property::Domain; -use crate::utils::date_helper::DateConverter; use crate::values::Column; use crate::values::Scalar; @@ -46,6 +47,32 @@ pub const TIMESTAMP_MAX: i64 = 253402300799999999; pub const MICROS_PER_SEC: i64 = 1_000_000; pub const MICROS_PER_MILLI: i64 = 1_000; +// jiff's `Timestamp` only accepts UTC seconds in +// [-377705023201, 253402207200] so that any +/-25:59:59 offset still +// yields a valid civil datetime. Clamp after splitting into seconds +// and sub-second nanoseconds to avoid constructing out-of-range values. +const JIFF_TIMESTAMP_MIN_SEC: i64 = -377705023201; +const JIFF_TIMESTAMP_MAX_SEC: i64 = 253402207200; + +pub fn timestamp_from_micros(micros: impl AsPrimitive, tz: &TimeZone) -> Zoned { + // Can't use `tz.timestamp_nanos(micros.as_() * 1000)` directly, as it may overflow. + let micros = micros.as_(); + let (mut secs, mut nanos) = (micros / MICROS_PER_SEC, (micros % MICROS_PER_SEC) * 1_000); + if nanos < 0 { + secs -= 1; + nanos += 1_000_000_000; + } + if secs > JIFF_TIMESTAMP_MAX_SEC { + secs = JIFF_TIMESTAMP_MAX_SEC; + nanos = 0; + } else if secs < JIFF_TIMESTAMP_MIN_SEC { + secs = JIFF_TIMESTAMP_MIN_SEC; + nanos = 0; + } + let ts = Timestamp::new(secs, nanos as i32).unwrap(); + ts.to_zoned(tz.clone()) +} + pub const PRECISION_MICRO: u8 = 6; pub const PRECISION_MILLI: u8 = 3; pub const PRECISION_SEC: u8 = 0; @@ -194,6 +221,6 @@ pub fn string_to_timestamp( #[inline] pub fn timestamp_to_string(ts: i64, tz: &TimeZone) -> impl Display { - let zdt = ts.to_timestamp(tz); + let zdt = timestamp_from_micros(ts, tz); strtime::format(TIMESTAMP_FORMAT, &zdt).unwrap() } diff --git a/src/query/expression/src/types/variant.rs b/src/query/expression/src/types/variant.rs index bbd1c5abc213b..d0e1234b9aee7 100644 --- a/src/query/expression/src/types/variant.rs +++ b/src/query/expression/src/types/variant.rs @@ -325,7 +325,7 @@ pub fn cast_scalar_to_variant( ScalarRef::Decimal(v) => v.to_string(), ScalarRef::Boolean(v) => v.to_string(), ScalarRef::Timestamp(v) => timestamp_to_string(v, tz).to_string(), - ScalarRef::Date(v) => date_to_string(v, tz).to_string(), + ScalarRef::Date(v) => date_to_string(v), _ => unreachable!(), }; let mut val = vec![]; diff --git a/src/query/expression/src/utils/date_helper.rs b/src/query/expression/src/utils/date_helper.rs deleted file mode 100644 index 25fa1f83ae953..0000000000000 --- a/src/query/expression/src/utils/date_helper.rs +++ /dev/null @@ -1,2257 +0,0 @@ -// Copyright 2021 Datafuse Labs -// -// Licensed 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. - -use std::sync::LazyLock; - -use databend_common_column::types::timestamp_tz; -use databend_common_exception::Result; -use databend_common_timezone::DateTimeComponents; -use databend_common_timezone::fast_components_from_timestamp; -use databend_common_timezone::fast_utc_from_local; -use jiff::SignedDuration; -use jiff::SpanRelativeTo; -use jiff::Timestamp; -use jiff::ToSpan; -use jiff::Unit; -use jiff::Zoned; -use jiff::civil::Date; -use jiff::civil::DateTime; -use jiff::civil::Time; -use jiff::civil::Weekday; -use jiff::civil::date; -use jiff::civil::datetime; -use jiff::tz::TimeZone; -use num_traits::AsPrimitive; - -use crate::types::date::clamp_date; -use crate::types::timestamp::MICROS_PER_SEC; -use crate::types::timestamp::clamp_timestamp; - -// jiff's `Timestamp` only accepts UTC seconds in -// [-377705023201, 253402207200] so that any ±25:59:59 offset still -// yields a valid civil datetime. Clamp after splitting into seconds -// and sub-second nanoseconds to avoid constructing out-of-range values. -const JIFF_TIMESTAMP_MIN_SEC: i64 = -377705023201; -const JIFF_TIMESTAMP_MAX_SEC: i64 = 253402207200; - -pub trait DateConverter { - fn to_date(&self, tz: &TimeZone) -> Date; - fn to_timestamp(&self, tz: &TimeZone) -> Zoned; -} - -impl DateConverter for T -where T: AsPrimitive -{ - fn to_date(&self, _tz: &TimeZone) -> Date { - let dur = SignedDuration::from_hours(self.as_() * 24); - date(1970, 1, 1).checked_add(dur).unwrap() - } - - fn to_timestamp(&self, tz: &TimeZone) -> Zoned { - // Can't use `tz.timestamp_nanos(self.as_() * 1000)` directly, is may cause multiply with overflow. - let micros = self.as_(); - let (mut secs, mut nanos) = (micros / MICROS_PER_SEC, (micros % MICROS_PER_SEC) * 1_000); - if nanos < 0 { - secs -= 1; - nanos += 1_000_000_000; - } - if secs > JIFF_TIMESTAMP_MAX_SEC { - secs = JIFF_TIMESTAMP_MAX_SEC; - nanos = 0; - } else if secs < JIFF_TIMESTAMP_MIN_SEC { - secs = JIFF_TIMESTAMP_MIN_SEC; - nanos = 0; - } - let ts = Timestamp::new(secs, nanos as i32).unwrap(); - ts.to_zoned(tz.clone()) - } -} - -pub const MICROSECS_PER_DAY: i64 = 86_400_000_000; - -// Timestamp arithmetic factors. -pub const FACTOR_HOUR: i64 = 3600; -pub const FACTOR_MINUTE: i64 = 60; -pub const FACTOR_SECOND: i64 = 1; -const LAST_DAY_LUT: [i8; 13] = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; - -fn eval_years_base( - year: i16, - month: i8, - day: i8, - delta: i64, - _add_months: bool, -) -> std::result::Result { - let new_year = year as i64 + delta; - let mut new_day = day; - if std::intrinsics::unlikely(month == 2 && day == 29) { - new_day = last_day_of_year_month(new_year as i16, month); - } - match Date::new(new_year as i16, month, new_day) { - Ok(d) => Ok(d), - Err(e) => Err(format!("Invalid date: {}", e)), - } -} - -fn eval_months_base( - year: i16, - month: i8, - day: i8, - delta: i64, - add_months: bool, -) -> std::result::Result { - let total_months = (month as i64 + delta - 1) as i16; - let mut new_year = year + (total_months / 12); - let mut new_month0 = total_months % 12; - if new_month0 < 0 { - new_year -= 1; - new_month0 += 12; - } - - // Handle month last day overflow, "2020-2-29" + "1 year" should be "2021-2-28", or "1990-1-31" + "3 month" should be "1990-4-30". - // For ADD_MONTHS only, if the original day is the last day of the month, the result day of month will be the last day of the result month. - let new_month = (new_month0 + 1) as i8; - // Determine the correct day - let max_day = last_day_of_year_month(new_year, new_month); - let new_day = if add_months && day == last_day_of_year_month(year, month) { - max_day - } else { - day.min(max_day) - }; - - match Date::new(new_year, (new_month0 + 1) as i8, new_day) { - Ok(d) => Ok(d), - Err(e) => Err(format!("Invalid date: {}", e)), - } -} - -// Get the last day of the year month, could be 28(non leap Feb), 29(leap year Feb), 30 or 31 -fn last_day_of_year_month(year: i16, month: i8) -> i8 { - let is_leap_year = (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0); - if std::intrinsics::unlikely(month == 2 && is_leap_year) { - return 29; - } - LAST_DAY_LUT[month as usize] -} - -macro_rules! impl_interval_year_month { - ($name: ident, $op: expr) => { - #[derive(Clone)] - pub struct $name; - - impl $name { - pub fn eval_date( - date: i32, - tz: &TimeZone, - delta: impl AsPrimitive, - add_months: bool, - ) -> std::result::Result { - let date = date.to_date(tz); - let new_date = $op( - date.year(), - date.month(), - date.day(), - delta.as_(), - add_months, - )?; - - Ok(clamp_date( - new_date - .since((Unit::Day, Date::new(1970, 1, 1).unwrap())) - .unwrap() - .get_days() as i64, - )) - } - - pub fn eval_timestamp( - us: i64, - tz: &TimeZone, - delta: impl AsPrimitive, - add_months: bool, - ) -> std::result::Result { - let ts = us.to_timestamp(tz); - let original_offset = ts.offset().seconds(); - - if let Some(components) = fast_components_from_timestamp(us, tz) { - let new_date = $op( - components.year as i16, - components.month as i8, - components.day as i8, - delta.as_(), - add_months, - )?; - if let Some(mut new_ts) = fast_utc_from_local( - tz, - new_date.year() as i32, - new_date.month() as u8, - new_date.day() as u8, - components.hour, - components.minute, - components.second, - components.micro, - ) { - if let Some(new_components) = fast_components_from_timestamp(new_ts, tz) { - if new_components.offset_seconds != original_offset { - let shift_secs = - (new_components.offset_seconds - original_offset) as i64; - let shift_micros = shift_secs.saturating_mul(MICROS_PER_SEC); - new_ts = new_ts.checked_add(shift_micros).unwrap_or_else(|| { - if shift_micros.is_negative() { - i64::MIN - } else { - i64::MAX - } - }); - } - clamp_timestamp(&mut new_ts); - return Ok(new_ts); - } - } - } - - let new_date = $op(ts.year(), ts.month(), ts.day(), delta.as_(), add_months)?; - - let local = - new_date.at(ts.hour(), ts.minute(), ts.second(), ts.subsec_nanosecond()); - let mut zoned = match local.to_zoned(tz.clone()) { - Ok(z) => z, - Err(e) => match local.checked_add(SignedDuration::from_secs(3600)) { - Ok(res2) => res2 - .to_zoned(tz.clone()) - .map_err(|err| format!("{}", err))?, - Err(_) => return Err(format!("{}", e)), - }, - }; - if zoned.offset().seconds() != original_offset { - let shift = (zoned.offset().seconds() - original_offset) as i64; - if let Ok(adj_local) = local.checked_add(SignedDuration::from_secs(shift)) { - if let Ok(adj_zoned) = adj_local.to_zoned(tz.clone()) { - zoned = adj_zoned; - } - } - } - let mut ts = zoned.timestamp().as_microsecond(); - clamp_timestamp(&mut ts); - Ok(ts) - } - } - }; -} - -impl_interval_year_month!(EvalYearsImpl, eval_years_base); -impl_interval_year_month!(EvalMonthsImpl, eval_months_base); - -/// Compare two `DateTimeComponents` by their time-of-day portion only. -fn components_time_less_than(a: &DateTimeComponents, b: &DateTimeComponents) -> bool { - (a.hour, a.minute, a.second, a.micro) < (b.hour, b.minute, b.second, b.micro) -} - -fn date_from_components(c: &DateTimeComponents) -> Option { - Date::new(c.year as i16, c.month as i8, c.day as i8).ok() -} - -#[inline] -pub fn timestamp_tz_local_micros(value: timestamp_tz) -> Option { - let offset = value.micros_offset()?; - value.timestamp().checked_add(offset) -} - -#[inline] -pub fn timestamp_tz_components_via_lut(value: timestamp_tz) -> Option { - let local = timestamp_tz_local_micros(value)?; - fast_components_from_timestamp(local, &TimeZone::UTC) -} - -fn datetime_from_components(c: &DateTimeComponents) -> Option { - let date = date_from_components(c)?; - Some(date.at( - c.hour as i8, - c.minute as i8, - c.second as i8, - (c.micro * 1_000) as i32, - )) -} - -impl EvalYearsImpl { - pub fn eval_date_diff(date_start: i32, date_end: i32, tz: &TimeZone) -> i32 { - let date_start = date_start.to_date(tz); - let date_end = date_end.to_date(tz); - (date_end.year() - date_start.year()) as i32 - } - - pub fn eval_date_between(date_start: i32, date_end: i32, tz: &TimeZone) -> i32 { - if date_start == date_end { - return 0; - } - if date_start > date_end { - return -Self::eval_date_between(date_end, date_start, tz); - } - - let date_start = date_start.to_date(tz); - let date_end = date_end.to_date(tz); - - let mut years = date_end.year() - date_start.year(); - - // If the end month is less than the start month, - // or the months are equal but the end day is less than the start day, - // the last year is incomplete, minus 1 - if (date_end.month() < date_start.month()) - || (date_end.month() == date_start.month() && date_end.day() < date_start.day()) - { - years -= 1; - } - - years as i32 - } - - pub fn eval_timestamp_diff(date_start: i64, date_end: i64, tz: &TimeZone) -> i64 { - if let (Some(start), Some(end)) = ( - fast_components_from_timestamp(date_start, tz), - fast_components_from_timestamp(date_end, tz), - ) { - return (end.year as i64) - (start.year as i64); - } - let date_start = date_start.to_timestamp(tz); - let date_end = date_end.to_timestamp(tz); - date_end.year() as i64 - date_start.year() as i64 - } - - pub fn eval_timestamp_between(date_start: i64, date_end: i64, tz: &TimeZone) -> i64 { - if date_start == date_end { - return 0; - } - if date_start > date_end { - return -Self::eval_timestamp_between(date_end, date_start, tz); - } - if let (Some(start), Some(end)) = ( - fast_components_from_timestamp(date_start, tz), - fast_components_from_timestamp(date_end, tz), - ) { - let mut years = end.year - start.year; - let start_is_feb_29 = start.month == 2 && start.day == 29; - let end_is_feb_28 = end.month == 2 && end.day == 28; - let end_before_start_date = (end.month < start.month) - || (end.month == start.month && end.day < start.day) - || (end.month == start.month - && end.day == start.day - && components_time_less_than(&end, &start)); - if !(start_is_feb_29 && end_is_feb_28) && end_before_start_date { - years -= 1; - } - return years as i64; - } - let start = date_start.to_timestamp(tz); - let end = date_end.to_timestamp(tz); - - let mut years = end.year() - start.year(); - - // Handle special cases on February 29 in leap years: - // If the start date is February 29 and the end date is February 28, it is considered a full year (leap year to regular year). - // Otherwise, the end date, month day, must be >= the start date, month day, and the time must be reached - let start_month = start.month(); - let start_day = start.day(); - - let end_month = end.month(); - let end_day = end.day(); - - let start_is_feb_29 = start_month == 2 && start_day == 29; - let end_is_feb_28 = end_month == 2 && end_day == 28; - - let end_before_start_date = (end_month < start_month) - || (end_month == start_month && end_day < start_day) - || (end_month == start_month && end_day == start_day && end.time() < start.time()); - - if start_is_feb_29 && end_is_feb_28 { - } else if end_before_start_date { - years -= 1; - } - - years as i64 - } -} - -pub struct EvalISOYearsImpl; -impl EvalISOYearsImpl { - pub fn eval_date_diff(date_start: i32, date_end: i32, tz: &TimeZone) -> i32 { - let date_start = date_start.to_date(tz); - let date_end = date_end.to_date(tz); - date_end.iso_week_date().year() as i32 - date_start.iso_week_date().year() as i32 - } - - pub fn eval_date_between(date_start: i32, date_end: i32, tz: &TimeZone) -> i32 { - if date_start == date_end { - return 0; - } - if date_start > date_end { - return -Self::eval_date_between(date_end, date_start, tz); - } - let date_start = date_start.to_date(tz); - let date_end = date_end.to_date(tz); - let mut years = date_end.iso_week_date().year() - date_start.iso_week_date().year(); - if (date_end.month() < date_start.month()) - || (date_end.month() == date_start.month() && date_end.day() < date_start.day()) - { - years -= 1; - } - - years as i32 - } - - pub fn eval_timestamp_diff(date_start: i64, date_end: i64, tz: &TimeZone) -> i64 { - if let (Some(start), Some(end)) = ( - fast_components_from_timestamp(date_start, tz), - fast_components_from_timestamp(date_end, tz), - ) { - let (start_year, _) = start.iso_year_week(); - let (end_year, _) = end.iso_year_week(); - return (end_year - start_year) as i64; - } - let date_start = date_start.to_timestamp(tz); - let date_end = date_end.to_timestamp(tz); - date_end.date().iso_week_date().year() as i64 - date_start.iso_week_date().year() as i64 - } - - pub fn eval_timestamp_between(date_start: i64, date_end: i64, tz: &TimeZone) -> i64 { - if date_start == date_end { - return 0; - } - if date_start > date_end { - return -Self::eval_timestamp_between(date_end, date_start, tz); - } - if let (Some(start), Some(end)) = ( - fast_components_from_timestamp(date_start, tz), - fast_components_from_timestamp(date_end, tz), - ) { - let mut years = end.year - start.year; - let start_is_feb_29 = start.month == 2 && start.day == 29; - let end_is_feb_28 = end.month == 2 && end.day == 28; - let end_before_start_date = (end.month < start.month) - || (end.month == start.month && end.day < start.day) - || (end.month == start.month - && end.day == start.day - && components_time_less_than(&end, &start)); - if !(start_is_feb_29 && end_is_feb_28) && end_before_start_date { - years -= 1; - } - return years as i64; - } - - let start = date_start.to_timestamp(tz); - let end = date_end.to_timestamp(tz); - let mut years = - end.date().iso_week_date().year() as i64 - start.date().iso_week_date().year() as i64; - let start_month = start.month(); - let start_day = start.day(); - - let end_month = end.month(); - let end_day = end.day(); - - let start_is_feb_29 = start_month == 2 && start_day == 29; - let end_is_feb_28 = end_month == 2 && end_day == 28; - - let end_before_start_date = (end_month < start_month) - || (end_month == start_month && end_day < start_day) - || (end_month == start_month && end_day == start_day && end.time() < start.time()); - - if start_is_feb_29 && end_is_feb_28 { - } else if end_before_start_date { - years -= 1; - } - - years - } -} - -pub struct EvalYearWeeksImpl; -impl EvalYearWeeksImpl { - fn yearweek(date: Date) -> i32 { - let iso_week = date.iso_week_date(); - (iso_week.year() as i32 * 100) + iso_week.week() as i32 - } - - fn yearweek_from_components(components: &DateTimeComponents) -> i32 { - let (year, week) = components.iso_year_week(); - year * 100 + week as i32 - } - - pub fn eval_date_diff(date_start: i32, date_end: i32, tz: &TimeZone) -> i32 { - let date_start = date_start.to_date(tz); - let date_end = date_end.to_date(tz); - let end = Self::yearweek(date_end); - let start = Self::yearweek(date_start); - - end - start - } - - pub fn eval_timestamp_diff(date_start: i64, date_end: i64, tz: &TimeZone) -> i64 { - if let (Some(start), Some(end)) = ( - fast_components_from_timestamp(date_start, tz), - fast_components_from_timestamp(date_end, tz), - ) { - let start_yw = Self::yearweek_from_components(&start) as i64; - let end_yw = Self::yearweek_from_components(&end) as i64; - return end_yw - start_yw; - } - let date_start = date_start.to_timestamp(tz); - let date_end = date_end.to_timestamp(tz); - let end = Self::yearweek(date_end.date()) as i64; - let start = Self::yearweek(date_start.date()) as i64; - - end - start - } - - // In duckdb datesub(yearweek, ) is same as datesub(week, ) But we can contain these logic - // fn week_end(date: Date) -> Date { - // let weekday = date.weekday(); - // - // let days_to_sunday = 7 - weekday.to_monday_one_offset(); // monday=1, sunday=7 - // let dur = SignedDuration::from_hours(days_to_sunday as i64 * 24); - // date.checked_add(dur).unwrap() - // } - // pub fn eval_date_between(start: i32, end: i32, tz: &TimeZone) -> i32 { - // if start == end { - // return 0; - // } - // - // let (earlier, later, sign) = if start <= end { - // (start, end, 1) - // } else { - // (end, start, -1) - // }; - // - // let earlier = earlier.to_date(tz); - // let later = later.to_date(tz); - // - // let start_yw = Self::yearweek(earlier); - // let end_yw = Self::yearweek(later); - // - // let mut diff = end_yw - start_yw; - // - // If the end week is incomplete, subtract 1 - // if later < Self::week_end(later) { - // diff -= 1; - // } - // - // diff * sign - // } - // pub fn eval_timestamp_between(start: i64, end: i64, tz: &TimeZone) -> i64 { - // if start == end { - // return 0; - // } - // - // let (earlier, later, sign) = if start <= end { - // (start, end, 1) - // } else { - // (end, start, -1) - // }; - // - // let earlier = earlier.to_timestamp(tz); - // let later = later.to_timestamp(tz); - // - // let start_yw = Self::yearweek(earlier.date()); - // let end_yw = Self::yearweek(later.date()); - // - // let mut diff = end_yw - start_yw; - // - // let week_end = EvalYearWeeksImpl::week_end(later.date()); - // if later.datetime() < week_end.at(23, 59, 59, 999_999_999) { - // diff -= 1; - // } - // - // diff as i64 * sign - // } -} - -pub struct EvalQuartersImpl; - -impl EvalQuartersImpl { - pub fn eval_date_diff(date_start: i32, date_end: i32, tz: &TimeZone) -> i32 { - EvalQuartersImpl::eval_timestamp_diff( - date_start as i64 * MICROSECS_PER_DAY, - date_end as i64 * MICROSECS_PER_DAY, - tz, - ) as i32 - } - - pub fn eval_timestamp_diff(date_start: i64, date_end: i64, tz: &TimeZone) -> i64 { - if let (Some(start), Some(end)) = ( - fast_components_from_timestamp(date_start, tz), - fast_components_from_timestamp(date_end, tz), - ) { - let start_quarter = ((start.month as i64 - 1) / 3) + 1; - let end_quarter = ((end.month as i64 - 1) / 3) + 1; - return (end.year as i64 - start.year as i64) * 4 + end_quarter - start_quarter; - } - let date_start = date_start.to_timestamp(tz); - let date_end = date_end.to_timestamp(tz); - (date_end.year() - date_start.year()) as i64 * 4 + ToQuarter::to_number(&date_end) as i64 - - ToQuarter::to_number(&date_start) as i64 - } - - // Return date corresponding to quarter number (1~4) - // fn quarter(month: i8) -> i32 { - // ((month - 1) / 3 + 1) as i32 - // } - // - // - // fn quarter_start(year: i16, month: i8) -> (i16, i8) { - // let q = ((month - 1) / 3) + 1; - // let start_month = (q - 1) * 3 + 1; - // (year, start_month) - // } - // - // DuckDB directly calc month/3 - // pub fn eval_date_between(start: i32, end: i32, tz: &TimeZone) -> i32 { - // if start == end { - // return 0; - // } - // let (earlier, later, sign) = if start <= end { - // (start, end, 1) - // } else { - // (end, start, -1) - // }; - // - // let earlier = earlier.to_date(tz); - // let later = later.to_date(tz); - // - // let start_year = earlier.year(); - // let start_quarter = Self::quarter(earlier.month()); - // let end_year = later.year(); - // let end_quarter = Self::quarter(later.month()); - // - // let mut diff = - // (end_year - start_year) as i64 * 4 + (end_quarter as i64 - start_quarter as i64); - // - // let (last_quarter_start_year, last_quarter_start_month) = - // Self::quarter_start(end_year, later.month()); - // let last_quarter_start_date = date(last_quarter_start_year, last_quarter_start_month, 1); - // - // - // if later < last_quarter_start_date { - // diff -= 1; - // } - // - // (diff * sign) as i32 - // } - // pub fn eval_timestamp_between(start: i64, end: i64, tz: &TimeZone) -> i64 { - // if start == end { - // return 0; - // } - // - // let (earlier, later, sign) = if start <= end { - // (start, end, 1) - // } else { - // (end, start, -1) - // }; - // - // let earlier = earlier.to_timestamp(tz); - // let later = later.to_timestamp(tz); - // - // let start_year = earlier.year(); - // let start_quarter = Self::quarter(earlier.month()); - // let end_year = later.year(); - // let end_quarter = Self::quarter(later.month()); - // - // let mut diff = - // (end_year - start_year) as i64 * 4 + (end_quarter as i64 - start_quarter as i64); - // - // let (last_quarter_start_year, last_quarter_start_month) = - // Self::quarter_start(later.year(), later.month()); - // let last_quarter_start_date = date(last_quarter_start_year, last_quarter_start_month, 1); - // let last_quarter_start_datetime = last_quarter_start_date.to_datetime(earlier.time()); - // - // if later.datetime() < last_quarter_start_datetime { - // diff -= 1; - // } - // diff * sign - // } -} - -impl EvalMonthsImpl { - pub fn eval_date_diff(date_start: i32, date_end: i32, tz: &TimeZone) -> i32 { - let date_start = date_start.to_date(tz); - let date_end = date_end.to_date(tz); - (date_end.year() - date_start.year()) as i32 * 12 + date_end.month() as i32 - - date_start.month() as i32 - } - - pub fn eval_date_between(start: i32, end: i32, tz: &TimeZone) -> i32 { - if start == end { - return 0; - } - if start > end { - return -Self::eval_date_between(end, start, tz); - } - - let start = start.to_date(tz); - let end = end.to_date(tz); - - let year_diff = end.year() - start.year(); - let month_diff = end.month() as i32 - start.month() as i32; - let mut months = year_diff as i32 * 12 + month_diff; - - if end.day() < start.day() { - months -= 1; - } - - months - } - - pub fn eval_timestamp_diff(date_start: i64, date_end: i64, tz: &TimeZone) -> i64 { - EvalMonthsImpl::eval_date_diff( - (date_start / MICROSECS_PER_DAY) as i32, - (date_end / MICROSECS_PER_DAY) as i32, - tz, - ) as i64 - } - - pub fn eval_timestamp_between(start: i64, end: i64, tz: &TimeZone) -> i64 { - if start == end { - return 0; - } - if start > end { - return -Self::eval_timestamp_between(end, start, tz); - } - if let (Some(start_c), Some(end_c)) = ( - fast_components_from_timestamp(start, tz), - fast_components_from_timestamp(end, tz), - ) { - let year_diff = end_c.year - start_c.year; - let month_diff = end_c.month as i32 - start_c.month as i32; - let mut months = year_diff as i64 * 12 + month_diff as i64; - if (end_c.day < start_c.day) - || (end_c.day == start_c.day && components_time_less_than(&end_c, &start_c)) - { - months -= 1; - } - return months; - } - - let start = start.to_timestamp(tz); - let end = end.to_timestamp(tz); - let year_diff = end.year() - start.year(); - let month_diff = end.month() as i64 - start.month() as i64; - let mut months = year_diff as i64 * 12 + month_diff; - - // Determine the time sequence. If the end time is less than the start time, it is incomplete - if (end.day() < start.day()) || (end.day() == start.day() && end.time() < start.time()) { - months -= 1; - } - - months - } - - // current we don't consider tz here - pub fn months_between_ts(ts_a: i64, ts_b: i64) -> f64 { - EvalMonthsImpl::months_between( - (ts_a / 86_400_000_000) as i32, - (ts_b / 86_400_000_000) as i32, - ) - } - - pub fn months_between(date_a: i32, date_b: i32) -> f64 { - let date_a = Date::new(1970, 1, 1) - .unwrap() - .checked_add(SignedDuration::from_hours(date_a as i64 * 24)) - .unwrap(); - let date_b = Date::new(1970, 1, 1) - .unwrap() - .checked_add(SignedDuration::from_hours(date_b as i64 * 24)) - .unwrap(); - - let year_diff = (date_a.year() - date_b.year()) as i64; - let month_diff = date_a.month() as i64 - date_b.month() as i64; - - // Calculate total months difference - let total_months_diff = year_diff * 12 + month_diff; - - // Determine if special case for fractional part applies - let is_same_day_of_month = date_a.day() == date_b.day(); - - let are_both_end_of_month = - date_a.last_of_month() == date_a && date_b.last_of_month() == date_b; - let day_fraction = if is_same_day_of_month || are_both_end_of_month { - 0.0 - } else { - let day_diff = date_a.day() as i32 - date_b.day() as i32; - day_diff as f64 / 31.0 // Using 31-day month for fractional part - }; - - // Total difference including fractional part - total_months_diff as f64 + day_fraction - } -} - -pub struct EvalWeeksImpl; - -impl EvalWeeksImpl { - pub fn eval_date_diff(date_start: i32, date_end: i32) -> i32 { - // 1970-01-01 is ThursDay - let date_start = date_start / 7 + (date_start % 7 >= 4) as i32; - let date_end = date_end / 7 + (date_end % 7 >= 4) as i32; - date_end - date_start - } - - pub fn eval_timestamp_diff(date_start: i64, date_end: i64) -> i64 { - EvalWeeksImpl::eval_date_diff( - (date_start / MICROSECS_PER_DAY) as i32, - (date_end / MICROSECS_PER_DAY) as i32, - ) as i64 - } - - fn calculate_weeks_between_years( - start_year: i32, - end_year: i32, - start_week: u32, - end_week: u32, - ) -> i32 { - let mut weeks = 0; - let mut current_year = start_year + 1; - - fn iso_weeks(year: i32) -> i32 { - // Get the first day of the year - let first_day = date(year as i16, 1, 1); - - // Determine the weekday of the first day - let weekday = first_day.weekday(); - - // Check if the year starts on a Thursday. - if weekday == Weekday::Thursday { - return 53; - } - - // Check if the year starts on a Wednesday and is a leap year. - if weekday == Weekday::Wednesday - && (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)) - { - return 53; - } - 52 - } - while current_year < end_year { - weeks += iso_weeks(current_year); - current_year += 1; - } - - // add start_year weeks and end_year weeks - weeks += iso_weeks(start_year) - start_week as i32 + end_week as i32; - weeks - } - - pub fn eval_date_between(start: i32, end: i32, tz: &TimeZone) -> i32 { - if start == end { - return 0; - } - if start > end { - return -Self::eval_date_between(end, start, tz); - } - - let earlier = start.to_date(tz); - let later = end.to_date(tz); - let mut weeks = Self::calculate_weeks_between_years( - earlier.year() as i32, - later.year() as i32, - earlier.iso_week_date().week() as u32, - later.iso_week_date().week() as u32, - ); - // Judge whether it is complete after the last week - let end_weekday = later.weekday(); - let days_since_monday = end_weekday.to_monday_one_offset() - 1; - let dur = SignedDuration::from_hours(days_since_monday as i64 * 24); - let monday_of_end_week = later.checked_sub(dur).unwrap(); - - if later < monday_of_end_week { - weeks -= 1; - } - - weeks - } - - pub fn eval_timestamp_between(start: i64, end: i64, tz: &TimeZone) -> i64 { - if start == end { - return 0; - } - if start > end { - return -Self::eval_timestamp_between(end, start, tz); - } - if let (Some(start_c), Some(end_c)) = ( - fast_components_from_timestamp(start, tz), - fast_components_from_timestamp(end, tz), - ) { - if let (Some(start_date), Some(end_date)) = - (date_from_components(&start_c), date_from_components(&end_c)) - { - let mut weeks = Self::calculate_weeks_between_years( - start_date.year() as i32, - end_date.year() as i32, - start_date.iso_week_date().week() as u32, - end_date.iso_week_date().week() as u32, - ) as i64; - let days_since_monday = end_c.weekday.to_monday_one_offset() - 1; - let dur = SignedDuration::from_hours(days_since_monday as i64 * 24); - let monday_of_end_week = end_date.checked_sub(dur).unwrap(); - let monday_dt = monday_of_end_week.at(0, 0, 0, 0); - if let Some(end_dt) = datetime_from_components(&end_c) { - if end_dt < monday_dt { - weeks -= 1; - } - } - return weeks; - } - } - - let earlier = start.to_timestamp(tz); - let later = end.to_timestamp(tz); - - let mut weeks = Self::calculate_weeks_between_years( - earlier.year() as i32, - later.year() as i32, - earlier.date().iso_week_date().week() as u32, - later.date().iso_week_date().week() as u32, - ) as i64; - // Judge whether it is complete after the last week - let end_date = later.date(); - let end_weekday = end_date.weekday(); - let days_since_monday = end_weekday.to_monday_one_offset() - 1; - let dur = SignedDuration::from_hours(days_since_monday as i64 * 24); - let monday_of_end_week = end_date.checked_sub(dur).unwrap(); - let monday_of_end_week_datetime = monday_of_end_week.at(0, 0, 0, 0); - - if later.datetime() < monday_of_end_week_datetime { - weeks -= 1; - } - weeks - } -} - -pub struct EvalDaysImpl; - -impl EvalDaysImpl { - pub fn eval_date(date: i32, delta: impl AsPrimitive) -> i32 { - clamp_date((date as i64).wrapping_add(delta.as_())) - } - - pub fn eval_date_diff(date_start: i32, date_end: i32) -> i32 { - date_end - date_start - } - - pub fn eval_timestamp(date: i64, delta: impl AsPrimitive) -> i64 { - let mut value = date.wrapping_add(delta.as_().wrapping_mul(MICROSECS_PER_DAY)); - clamp_timestamp(&mut value); - value - } - - pub fn eval_timestamp_diff(date_start: i64, date_end: i64) -> i64 { - EvalDaysImpl::eval_date_diff( - (date_start / MICROSECS_PER_DAY) as i32, - (date_end / MICROSECS_PER_DAY) as i32, - ) as i64 - } - - pub fn eval_timestamp_between(start: i64, end: i64, tz: &TimeZone) -> i64 { - if start == end { - return 0; - } - if start > end { - return -Self::eval_timestamp_between(end, start, tz); - } - - let start = start.to_timestamp(tz); - let end = end.to_timestamp(tz); - let mut full_days = (end.date() - start.date()) - .to_duration(SpanRelativeTo::days_are_24_hours()) - .unwrap() - .as_hours() - / 24; - let end_time = end.time(); - let start_time = start.time(); - if end_time < start_time { - full_days -= 1; - } - full_days - } -} - -pub struct EvalTimesImpl; - -impl EvalTimesImpl { - pub fn eval_date(date: i32, delta: impl AsPrimitive, factor: i64) -> i32 { - clamp_date( - (date as i64 * MICROSECS_PER_DAY) - .wrapping_add(delta.as_().wrapping_mul(factor * MICROS_PER_SEC)), - ) - } - - pub fn eval_timestamp(us: i64, delta: impl AsPrimitive, factor: i64) -> i64 { - let mut ts = us.wrapping_add(delta.as_().wrapping_mul(factor * MICROS_PER_SEC)); - clamp_timestamp(&mut ts); - ts - } - - pub fn eval_timestamp_diff(date_start: i64, date_end: i64, factor: i64) -> i64 { - let date_start = date_start / (MICROS_PER_SEC * factor); - let date_end = date_end / (MICROS_PER_SEC * factor); - date_end - date_start - } - - pub fn eval_timestamp_between(unit: &str, start: i64, end: i64) -> i64 { - if start == end { - return 0; - } - if start > end { - return -Self::eval_timestamp_between(unit, end, start); - } - - let duration = SignedDuration::from_micros(end - start); - - match unit { - "hours" => duration.as_hours(), - "minutes" => duration.as_mins(), - "seconds" => duration.as_secs(), - _ => unreachable!("Unsupported unit: {}", unit), - } - } -} - -#[inline] -pub fn today_date(now: &Zoned, tz: &TimeZone) -> i32 { - let now = now.with_time_zone(tz.clone()); - now.date() - .since((Unit::Day, Date::new(1970, 1, 1).unwrap())) - .unwrap() - .get_days() -} - -// Summer Time in 1990 began at 2 a.m. (Beijing time) on Sunday, April 15th and ended at 2 a.m. (Beijing Daylight Saving Time) on Sunday, September 16th. -// During this period, the summer working hours will be implemented, namely from April 15th to September 16th. -// The working hours of all departments of The State Council are from 8 a.m. to 12 p.m. and from 1:30 p.m. to 5:30 p.m. The winter working hours will be implemented after September 17th. -pub fn calc_date_to_timestamp(val: i32, tz: &TimeZone) -> std::result::Result { - let ts = (val as i64) * 24 * 3600 * MICROS_PER_SEC; - let local_date = val.to_date(tz); - let year = i32::from(local_date.year()); - let month = local_date.month() as u8; - let day = local_date.day() as u8; - - if let Some(micros) = fast_utc_from_local(tz, year, month, day, 0, 0, 0, 0) { - return Ok(micros); - } - - let midnight = local_date.to_datetime(Time::midnight()); - match midnight.to_zoned(tz.clone()) { - Ok(zoned) => Ok(zoned.timestamp().as_microsecond()), - Err(_err) => { - for minutes in 1..=1440 { - let delta = SignedDuration::from_secs((minutes * 60) as i64); - if let Ok(adj) = midnight.checked_add(delta) { - if let Ok(zoned) = adj.to_zoned(tz.clone()) { - return Ok(zoned.timestamp().as_microsecond()); - } - } else { - break; - } - } - - // The timezone database might not have explicit rules for extremely - // old/new dates, so fall back to the legacy behavior that applies the - // canonical offset we use for 1970-01-01. - let tz_offset_micros = tz - .to_timestamp(date(1970, 1, 1).at(0, 0, 0, 0)) - .unwrap() - .as_microsecond(); - Ok(ts + tz_offset_micros) - } - } -} - -pub trait ToNumber { - fn to_number(dt: &Zoned) -> N; - - fn from_components(_components: &DateTimeComponents) -> Option { - None - } -} - -pub trait DateToNumber: ToNumber { - fn to_number_from_date(date: &Date) -> N; -} - -pub struct ToNumberImpl; - -impl ToNumberImpl { - pub fn eval_timestamp, R>(us: i64, tz: &TimeZone) -> R { - if let Some(components) = fast_components_from_timestamp(us, tz) { - if let Some(value) = T::from_components(&components) { - return value; - } - } - let dt = us.to_timestamp(tz); - T::to_number(&dt) - } - - pub fn eval_date, R>(date: i32, tz: &TimeZone) -> Result { - Ok(T::to_number_from_date(&date.to_date(tz))) - } -} - -pub struct ToYYYYMM; -pub struct ToYYYYWW; -pub struct ToYYYYMMDD; -pub struct ToYYYYMMDDHH; -pub struct ToYYYYMMDDHHMMSS; -pub struct ToYear; -pub struct ToTimezoneHour; -pub struct ToTimezoneMinute; -pub struct ToMillennium; -pub struct ToISOYear; -pub struct ToQuarter; -pub struct ToMonth; -pub struct ToDayOfYear; -pub struct ToDayOfMonth; -pub struct ToDayOfWeek; -pub struct DayOfWeek; -pub struct ToHour; -pub struct ToMinute; -pub struct ToSecond; -pub struct ToUnixTimestamp; - -pub struct ToWeekOfYear; - -impl ToNumber for ToYYYYMM { - fn to_number(dt: &Zoned) -> u32 { - dt.year() as u32 * 100 + dt.month() as u32 - } - - fn from_components(components: &DateTimeComponents) -> Option { - Some(components.year as u32 * 100 + components.month as u32) - } -} - -impl DateToNumber for ToYYYYMM { - fn to_number_from_date(date: &Date) -> u32 { - date.year() as u32 * 100 + date.month() as u32 - } -} - -impl ToNumber for ToMillennium { - fn to_number(dt: &Zoned) -> u16 { - (dt.year() as u16).div_ceil(1000) - } - - fn from_components(components: &DateTimeComponents) -> Option { - Some((components.year as u16).div_ceil(1000)) - } -} - -impl DateToNumber for ToMillennium { - fn to_number_from_date(date: &Date) -> u16 { - (date.year() as u16).div_ceil(1000) - } -} - -impl ToNumber for ToWeekOfYear { - fn to_number(dt: &Zoned) -> u32 { - dt.date().iso_week_date().week() as u32 - } - - fn from_components(components: &DateTimeComponents) -> Option { - Some(components.iso_year_week().1) - } -} - -impl DateToNumber for ToWeekOfYear { - fn to_number_from_date(date: &Date) -> u32 { - date.iso_week_date().week() as u32 - } -} - -impl ToNumber for ToYYYYMMDD { - fn to_number(dt: &Zoned) -> u32 { - dt.year() as u32 * 10_000 + dt.month() as u32 * 100 + dt.day() as u32 - } - - fn from_components(components: &DateTimeComponents) -> Option { - Some( - components.year as u32 * 10_000 + components.month as u32 * 100 + components.day as u32, - ) - } -} - -impl DateToNumber for ToYYYYMMDD { - fn to_number_from_date(date: &Date) -> u32 { - date.year() as u32 * 10_000 + date.month() as u32 * 100 + date.day() as u32 - } -} - -impl ToNumber for ToYYYYMMDDHH { - fn to_number(dt: &Zoned) -> u64 { - dt.year() as u64 * 1_000_000 - + dt.month() as u64 * 10_000 - + dt.day() as u64 * 100 - + dt.hour() as u64 - } - - fn from_components(components: &DateTimeComponents) -> Option { - Some( - components.year as u64 * 1_000_000 - + components.month as u64 * 10_000 - + components.day as u64 * 100 - + components.hour as u64, - ) - } -} - -impl ToNumber for ToYYYYMMDDHHMMSS { - fn to_number(dt: &Zoned) -> u64 { - dt.year() as u64 * 10_000_000_000 - + dt.month() as u64 * 100_000_000 - + dt.day() as u64 * 1_000_000 - + dt.hour() as u64 * 10_000 - + dt.minute() as u64 * 100 - + dt.second() as u64 - } - - fn from_components(components: &DateTimeComponents) -> Option { - Some( - components.year as u64 * 10_000_000_000 - + components.month as u64 * 100_000_000 - + components.day as u64 * 1_000_000 - + components.hour as u64 * 10_000 - + components.minute as u64 * 100 - + components.second as u64, - ) - } -} - -impl ToNumber for ToYear { - fn to_number(dt: &Zoned) -> u16 { - dt.year() as u16 - } - - fn from_components(components: &DateTimeComponents) -> Option { - Some(components.year as u16) - } -} - -impl DateToNumber for ToYear { - fn to_number_from_date(date: &Date) -> u16 { - date.year() as u16 - } -} - -impl ToNumber for ToTimezoneHour { - fn to_number(dt: &Zoned) -> i16 { - dt.offset().seconds().div_ceil(3600) as i16 - } - - fn from_components(components: &DateTimeComponents) -> Option { - Some(components.offset_seconds.div_ceil(3600) as i16) - } -} - -impl ToNumber for ToTimezoneMinute { - fn to_number(dt: &Zoned) -> i16 { - (dt.offset().seconds() % 3600).div_ceil(60) as i16 - } - - fn from_components(components: &DateTimeComponents) -> Option { - Some((components.offset_seconds % 3600).div_ceil(60) as i16) - } -} - -impl ToNumber for ToISOYear { - fn to_number(dt: &Zoned) -> u16 { - dt.date().iso_week_date().year() as _ - } - - fn from_components(components: &DateTimeComponents) -> Option { - Some(components.iso_year_week().0 as u16) - } -} - -impl DateToNumber for ToISOYear { - fn to_number_from_date(date: &Date) -> u16 { - date.iso_week_date().year() as u16 - } -} - -impl ToNumber for ToYYYYWW { - fn to_number(dt: &Zoned) -> u32 { - let week_date = dt.date().iso_week_date(); - let year = week_date.year() as u32 * 100; - year + dt.date().iso_week_date().week() as u32 - } - - fn from_components(components: &DateTimeComponents) -> Option { - let (iso_year, iso_week) = components.iso_year_week(); - Some(iso_year as u32 * 100 + iso_week) - } -} - -impl DateToNumber for ToYYYYWW { - fn to_number_from_date(date: &Date) -> u32 { - let week_date = date.iso_week_date(); - week_date.year() as u32 * 100 + week_date.week() as u32 - } -} - -impl ToNumber for ToQuarter { - fn to_number(dt: &Zoned) -> u8 { - // begin with 0 - ((dt.month() - 1) / 3 + 1) as u8 - } - - fn from_components(components: &DateTimeComponents) -> Option { - Some((components.month - 1) / 3 + 1) - } -} - -impl DateToNumber for ToQuarter { - fn to_number_from_date(date: &Date) -> u8 { - (date.month() as u8 - 1) / 3 + 1 - } -} - -impl ToNumber for ToMonth { - fn to_number(dt: &Zoned) -> u8 { - dt.month() as u8 - } - - fn from_components(components: &DateTimeComponents) -> Option { - Some(components.month) - } -} - -impl DateToNumber for ToMonth { - fn to_number_from_date(date: &Date) -> u8 { - date.month() as u8 - } -} - -impl ToNumber for ToDayOfYear { - fn to_number(dt: &Zoned) -> u16 { - dt.day_of_year() as u16 - } - - fn from_components(components: &DateTimeComponents) -> Option { - Some(components.day_of_year) - } -} - -impl DateToNumber for ToDayOfYear { - fn to_number_from_date(date: &Date) -> u16 { - date.day_of_year() as u16 - } -} - -impl ToNumber for ToDayOfMonth { - fn to_number(dt: &Zoned) -> u8 { - dt.day() as u8 - } - - fn from_components(components: &DateTimeComponents) -> Option { - Some(components.day) - } -} - -impl DateToNumber for ToDayOfMonth { - fn to_number_from_date(date: &Date) -> u8 { - date.day() as u8 - } -} - -impl ToNumber for ToDayOfWeek { - fn to_number(dt: &Zoned) -> u8 { - dt.weekday().to_monday_one_offset() as u8 - } - - fn from_components(components: &DateTimeComponents) -> Option { - Some(components.weekday.to_monday_one_offset() as u8) - } -} - -impl DateToNumber for ToDayOfWeek { - fn to_number_from_date(date: &Date) -> u8 { - date.weekday().to_monday_one_offset() as u8 - } -} - -impl ToNumber for DayOfWeek { - fn to_number(dt: &Zoned) -> u8 { - dt.weekday().to_sunday_zero_offset() as u8 - } - - fn from_components(components: &DateTimeComponents) -> Option { - Some(components.weekday.to_sunday_zero_offset() as u8) - } -} - -impl DateToNumber for DayOfWeek { - fn to_number_from_date(date: &Date) -> u8 { - date.weekday().to_sunday_zero_offset() as u8 - } -} - -impl ToNumber for ToUnixTimestamp { - fn to_number(dt: &Zoned) -> i64 { - dt.with_time_zone(TimeZone::UTC).timestamp().as_second() - } - - fn from_components(components: &DateTimeComponents) -> Option { - Some(components.unix_seconds) - } -} - -#[derive(Clone, Copy)] -pub enum Round { - Second, - Minute, - FiveMinutes, - TenMinutes, - FifteenMinutes, - TimeSlot, - Hour, - Day, -} - -pub fn round_timestamp(ts: i64, tz: &TimeZone, round: Round) -> i64 { - let dtz = ts.to_timestamp(tz); - let res = match round { - Round::Second => tz - .to_zoned(datetime( - dtz.year(), - dtz.month(), - dtz.day(), - dtz.hour(), - dtz.minute(), - dtz.second(), - 0, - )) - .unwrap(), - Round::Minute => tz - .to_zoned(datetime( - dtz.year(), - dtz.month(), - dtz.day(), - dtz.hour(), - dtz.minute(), - 0, - 0, - )) - .unwrap(), - Round::FiveMinutes => tz - .to_zoned(datetime( - dtz.year(), - dtz.month(), - dtz.day(), - dtz.hour(), - dtz.minute() / 5 * 5, - 0, - 0, - )) - .unwrap(), - Round::TenMinutes => tz - .to_zoned(datetime( - dtz.year(), - dtz.month(), - dtz.day(), - dtz.hour(), - dtz.minute() / 10 * 10, - 0, - 0, - )) - .unwrap(), - Round::FifteenMinutes => tz - .to_zoned(datetime( - dtz.year(), - dtz.month(), - dtz.day(), - dtz.hour(), - dtz.minute() / 15 * 15, - 0, - 0, - )) - .unwrap(), - Round::TimeSlot => tz - .to_zoned(datetime( - dtz.year(), - dtz.month(), - dtz.day(), - dtz.hour(), - dtz.minute() / 30 * 30, - 0, - 0, - )) - .unwrap(), - Round::Hour => tz - .to_zoned(datetime( - dtz.year(), - dtz.month(), - dtz.day(), - dtz.hour(), - 0, - 0, - 0, - )) - .unwrap(), - Round::Day => tz - .to_zoned(datetime(dtz.year(), dtz.month(), dtz.day(), 0, 0, 0, 0)) - .unwrap(), - }; - res.timestamp().as_microsecond() -} - -#[derive(Debug, Clone, Copy)] -pub enum TimePart { - Year, - Quarter, - Month, - Week, - Day, - Hour, - Minute, - Second, - None, -} - -impl TimePart { - pub fn from(s: &str) -> Self { - match s.to_ascii_uppercase().as_str() { - "YEAR" => Self::Year, - "QUARTER" => Self::Quarter, - "MONTH" => Self::Month, - "WEEK" => Self::Week, - "DAY" => Self::Day, - "HOUR" => Self::Hour, - "MINUTE" => Self::Minute, - "SECOND" => Self::Second, - _ => Self::None, - } - } - - pub fn date_part(&self) -> bool { - matches!( - self, - Self::Year | Self::Quarter | Self::Month | Self::Week | Self::Day - ) - } -} - -#[derive(Debug, Clone, Copy)] -pub enum StartOrEnd { - Start, - End, - None, -} - -impl StartOrEnd { - pub fn from(s: &str) -> Self { - match s.to_ascii_uppercase().as_str() { - "START" => Self::Start, - "END" => Self::End, - _ => Self::None, - } - } -} - -/// Floor division for i64 (uses div_euclid which floors toward -inf for positive divisor). -fn floordiv(a: i64, b: i64) -> i64 { - a.div_euclid(b) -} - -/// Epoch reference for alignment -fn epoch_date() -> Date { - Date::new(1970, 1, 1).unwrap() -} - -/// Add months to a date safely (handles negative months and day overflow) -fn add_months_to_date(date: Date, months: i64) -> Date { - // Represent months from year 0 as i64 to avoid overflow; year is i32 so convert - let year = date.year() as i64; - let month0 = (date.month() - 1) as i64; // 0..11 - let total_month0 = year * 12 + month0 + months; - let new_year = (total_month0 / 12) as i32; - let new_month0 = total_month0.rem_euclid(12) as u32; // 0..11 - let new_month = new_month0 + 1; // 1..12 - - // day adjust: cap by last day of new month - let new_day = std::cmp::min(date.day() as u32, last_day_of_month(new_year, new_month)) as i8; - Date::new(new_year as i16, new_month as i8, new_day).unwrap() -} - -fn last_day_of_month(year: i32, month: u32) -> u32 { - // get first day of next month then -1 day - let (ny, nm) = if month == 12 { - (year + 1, 1) - } else { - (year, month + 1) - }; - let first_of_next = Date::new(ny as i16, nm as i8, 1).unwrap(); - - (first_of_next - 1.day()).day() as u32 -} - -pub fn time_slice_timestamp( - ts: i64, - slice_length: u64, - part: TimePart, - start_or_end: StartOrEnd, - week_start: Weekday, - tz: &TimeZone, -) -> i64 { - let slice_length = slice_length as i64; - - let ts = ts.to_timestamp(tz); - let dt = ts.datetime(); - - let start = match part { - TimePart::Year | TimePart::Quarter | TimePart::Month | TimePart::Week | TimePart::Day => { - let date = convert_start_date(dt.date(), part, week_start, slice_length); - DateTime::from(date) - } - TimePart::Hour | TimePart::Minute | TimePart::Second => { - let unit_seconds = match part { - TimePart::Hour => 3600, - TimePart::Minute => 60, - TimePart::Second => 1, - _ => unreachable!(), - }; - let total_unit_seconds = unit_seconds * slice_length; - let secs = ts.timestamp().as_second(); - let slice_index = floordiv(secs, total_unit_seconds); - let start_secs = slice_index * total_unit_seconds; - Timestamp::new(start_secs, 0) - .unwrap() - .to_zoned(tz.clone()) - .datetime() - } - TimePart::None => unreachable!(), - }; - - let result = match start_or_end { - StartOrEnd::Start => start, - StartOrEnd::End => add_units_to_datetime(start, slice_length, part), - _ => unreachable!(), - }; - - result - .to_zoned(tz.clone()) - .unwrap() - .timestamp() - .as_microsecond() -} - -fn add_units_to_datetime(start: DateTime, slice_length: i64, part: TimePart) -> DateTime { - match part { - TimePart::Year | TimePart::Quarter | TimePart::Month | TimePart::Week | TimePart::Day => { - let new_date = add_units_to_date(start.date(), slice_length, part); - DateTime::from(new_date) - } - TimePart::Hour => start + (slice_length * 3600).seconds(), - TimePart::Minute => start + (slice_length * 60).seconds(), - TimePart::Second => start + slice_length.seconds(), - TimePart::None => unreachable!(), - } -} - -fn convert_start_date(date: Date, part: TimePart, week_start: Weekday, slice_length: i64) -> Date { - match part { - TimePart::Year => { - // Years: convert year to offset from 1970, floor to slice, align to Jan 1 - let year_offset = (date.year() as i64) - 1970; - let slice_index = floordiv(year_offset, slice_length); - let start_year = 1970 + slice_index * slice_length; - // START aligned to first day of that year - Date::new(start_year as i16, 1, 1).unwrap() - } - TimePart::Quarter | TimePart::Month => { - // Months/Quarters: compute months since 1970-01 (0-based), slice by slice_months - let unit_months = match part { - TimePart::Quarter => 3, - TimePart::Month => 1, - _ => unreachable!(), - }; - // months_since_epoch: months since 1970-01 (1970-01 = 0) - let months_since_epoch = (date.year() as i64 - 1970) * 12 + ((date.month() - 1) as i64); - let slice_months = slice_length * unit_months; - let slice_index = floordiv(months_since_epoch, slice_months); - let start_months_total = slice_index * slice_months; - let start_year = (1970 + start_months_total / 12) as i32; - let start_month0 = (start_months_total.rem_euclid(12)) as u32; - let start_month = start_month0 + 1; - // START aligned to first day of that month - Date::new(start_year as i16, start_month as i8, 1).unwrap() - } - TimePart::Week => { - // Weeks: compute epoch's week start per week_start, then floor slices by weeks - let epoch = epoch_date(); - let epoch_wd = epoch.weekday(); - // convert weekday to Monday=0 offset - let ep = epoch_wd.to_monday_zero_offset() as i32; - let ws = week_start.to_monday_zero_offset() as i32; - // delta = days from epoch back to the week's start - let delta = (ep - ws).rem_euclid(7) as i64; - let start_of_epoch_week = epoch - delta.days(); - // days from start_of_epoch_week to input date - let days_since_start = (date - start_of_epoch_week).get_days(); - let slice_days = slice_length * 7; - let slice_index = floordiv(days_since_start as i64, slice_days); - start_of_epoch_week + (slice_index * slice_days).days() - } - TimePart::Day => { - // Days: floor by days since epoch - let days_since_epoch = (date - epoch_date()).get_days(); - let slice_index = floordiv(days_since_epoch as i64, slice_length); - epoch_date() + (slice_index * slice_length).days() - } - _ => unreachable!(), - } -} - -pub fn time_slice_date( - date: i32, - slice_length: u64, - part: TimePart, - start_or_end: StartOrEnd, - week_start: Weekday, -) -> i32 { - let dur = SignedDuration::from_hours((date * 24) as i64); - let date = jiff::civil::date(1970, 1, 1).checked_add(dur).unwrap(); - let slice_length = slice_length as i64; - - let start = convert_start_date(date, part, week_start, slice_length); - - // Return Start or End (End = Start + slice_length * unit) - let result = match start_or_end { - StartOrEnd::Start => start, - StartOrEnd::End => add_units_to_date(start, slice_length, part), - _ => unreachable!(), - }; - - result - .since((Unit::Day, Date::new(1970, 1, 1).unwrap())) - .unwrap() - .get_days() -} - -fn add_units_to_date(start: Date, slice_length: i64, part: TimePart) -> Date { - match part { - TimePart::Year => add_months_to_date(start, slice_length * 12), - TimePart::Quarter => add_months_to_date(start, slice_length * 3), - TimePart::Month => add_months_to_date(start, slice_length), - TimePart::Week => start + (slice_length * 7).days(), - TimePart::Day => start + slice_length.days(), - _ => unreachable!(), - } -} - -pub struct DateRounder; - -impl DateRounder { - pub fn eval_timestamp>(us: i64, tz: &TimeZone) -> i32 { - let dt = us.to_timestamp(tz); - T::to_number(&dt) - } - - pub fn eval_date>(date: i32, tz: &TimeZone) -> Result { - Ok(T::to_number_from_date(&date.to_date(tz))) - } -} - -#[inline] -fn date_to_inner_number(date: &Date) -> i32 { - date.since((Unit::Day, Date::new(1970, 1, 1).unwrap())) - .unwrap() - .get_days() -} - -/// Convert `jiff::Zoned` to `i32` in `Scalar::Date(i32)` for `DateType`. -/// -/// It's the days since 1970-01-01. -#[inline] -fn datetime_to_date_inner_number(date: &Zoned) -> i32 { - date.date() - .since((Unit::Day, Date::new(1970, 1, 1).unwrap())) - .unwrap() - .get_days() -} - -pub struct ToLastMonday; -pub struct ToLastSunday; -pub struct ToStartOfMonth; -pub struct ToStartOfQuarter; -pub struct ToStartOfYear; -pub struct ToStartOfISOYear; - -pub struct ToLastOfYear; -pub struct ToLastOfWeek; -pub struct ToLastOfMonth; -pub struct ToLastOfQuarter; -pub struct ToPreviousMonday; -pub struct ToPreviousTuesday; -pub struct ToPreviousWednesday; -pub struct ToPreviousThursday; -pub struct ToPreviousFriday; -pub struct ToPreviousSaturday; -pub struct ToPreviousSunday; -pub struct ToNextMonday; -pub struct ToNextTuesday; -pub struct ToNextWednesday; -pub struct ToNextThursday; -pub struct ToNextFriday; -pub struct ToNextSaturday; -pub struct ToNextSunday; - -impl ToNumber for ToLastMonday { - fn to_number(dt: &Zoned) -> i32 { - // datetime_to_date_inner_number just calc naive_date, so weekday also need only calc naive_date - datetime_to_date_inner_number(dt) - dt.date().weekday().to_monday_zero_offset() as i32 - } -} - -impl DateToNumber for ToLastMonday { - fn to_number_from_date(date: &Date) -> i32 { - date_to_inner_number(date) - date.weekday().to_monday_zero_offset() as i32 - } -} - -impl ToNumber for ToLastSunday { - fn to_number(dt: &Zoned) -> i32 { - // datetime_to_date_inner_number just calc naive_date, so weekday also need only calc naive_date - datetime_to_date_inner_number(dt) - dt.date().weekday().to_sunday_zero_offset() as i32 - } -} - -impl DateToNumber for ToLastSunday { - fn to_number_from_date(date: &Date) -> i32 { - date_to_inner_number(date) - date.weekday().to_sunday_zero_offset() as i32 - } -} - -impl ToNumber for ToStartOfMonth { - fn to_number(dt: &Zoned) -> i32 { - datetime_to_date_inner_number(&dt.first_of_month().unwrap()) - } -} - -impl DateToNumber for ToStartOfMonth { - fn to_number_from_date(date: &Date) -> i32 { - date_to_inner_number(&date.first_of_month()) - } -} - -impl ToNumber for ToStartOfQuarter { - fn to_number(dt: &Zoned) -> i32 { - let new_month = (dt.month() - 1) / 3 * 3 + 1; - let new_day = date(dt.year(), new_month, 1) - .at(0, 0, 0, 0) - .to_zoned(dt.time_zone().clone()) - .unwrap(); - datetime_to_date_inner_number(&new_day) - } -} - -impl DateToNumber for ToStartOfQuarter { - fn to_number_from_date(input: &Date) -> i32 { - let new_month = (input.month() - 1) / 3 * 3 + 1; - date_to_inner_number(&date(input.year(), new_month, 1)) - } -} - -impl ToNumber for ToStartOfYear { - fn to_number(dt: &Zoned) -> i32 { - datetime_to_date_inner_number(&dt.first_of_year().unwrap()) - } -} - -impl DateToNumber for ToStartOfYear { - fn to_number_from_date(date: &Date) -> i32 { - date_to_inner_number(&date.first_of_year()) - } -} - -impl ToNumber for ToStartOfISOYear { - fn to_number(dt: &Zoned) -> i32 { - let iso_year = dt.date().iso_week_date().year(); - for i in 1..=7 { - let new_dt = date(iso_year, 1, i) - .at(0, 0, 0, 0) - .to_zoned(dt.time_zone().clone()) - .unwrap(); - if new_dt.date().iso_week_date().weekday() == Weekday::Monday { - return datetime_to_date_inner_number(&new_dt); - } - } - // Never return 0 - 0 - } -} - -impl DateToNumber for ToStartOfISOYear { - fn to_number_from_date(input: &Date) -> i32 { - let iso_year = input.iso_week_date().year(); - for i in 1..=7 { - let new_date = date(iso_year, 1, i); - if new_date.iso_week_date().weekday() == Weekday::Monday { - return date_to_inner_number(&new_date); - } - } - 0 - } -} - -impl ToNumber for ToLastOfWeek { - fn to_number(dt: &Zoned) -> i32 { - datetime_to_date_inner_number(dt) - dt.date().weekday().to_monday_zero_offset() as i32 + 6 - } -} - -impl DateToNumber for ToLastOfWeek { - fn to_number_from_date(date: &Date) -> i32 { - date_to_inner_number(date) - date.weekday().to_monday_zero_offset() as i32 + 6 - } -} - -impl ToNumber for ToLastOfMonth { - fn to_number(dt: &Zoned) -> i32 { - let day = last_day_of_year_month(dt.year(), dt.month()); - let dt = date(dt.year(), dt.month(), day) - .at(dt.hour(), dt.minute(), dt.second(), dt.subsec_nanosecond()) - .to_zoned(dt.time_zone().clone()) - .unwrap(); - datetime_to_date_inner_number(&dt) - } -} - -impl DateToNumber for ToLastOfMonth { - fn to_number_from_date(input: &Date) -> i32 { - let day = last_day_of_year_month(input.year(), input.month()); - date_to_inner_number(&date(input.year(), input.month(), day)) - } -} - -impl ToNumber for ToLastOfQuarter { - fn to_number(dt: &Zoned) -> i32 { - let new_month = (dt.month() - 1) / 3 * 3 + 3; - let day = last_day_of_year_month(dt.year(), new_month); - let dt = date(dt.year(), new_month, day) - .at(dt.hour(), dt.minute(), dt.second(), dt.subsec_nanosecond()) - .to_zoned(dt.time_zone().clone()) - .unwrap(); - datetime_to_date_inner_number(&dt) - } -} - -impl DateToNumber for ToLastOfQuarter { - fn to_number_from_date(input: &Date) -> i32 { - let new_month = (input.month() - 1) / 3 * 3 + 3; - let day = last_day_of_year_month(input.year(), new_month); - date_to_inner_number(&date(input.year(), new_month, day)) - } -} - -impl ToNumber for ToLastOfYear { - fn to_number(dt: &Zoned) -> i32 { - let day = last_day_of_year_month(dt.year(), 12); - let dt = date(dt.year(), 12, day) - .at(dt.hour(), dt.minute(), dt.second(), dt.subsec_nanosecond()) - .to_zoned(dt.time_zone().clone()) - .unwrap(); - datetime_to_date_inner_number(&dt) - } -} - -impl DateToNumber for ToLastOfYear { - fn to_number_from_date(input: &Date) -> i32 { - let day = last_day_of_year_month(input.year(), 12); - date_to_inner_number(&date(input.year(), 12, day)) - } -} - -macro_rules! impl_date_round_to_weekday { - ($type:ident, $weekday:ident, $is_previous:literal) => { - impl DateToNumber for $type { - fn to_number_from_date(date: &Date) -> i32 { - previous_or_next_date_day(date, Weekday::$weekday, $is_previous) - } - } - }; -} - -impl_date_round_to_weekday!(ToPreviousMonday, Monday, true); -impl_date_round_to_weekday!(ToPreviousTuesday, Tuesday, true); -impl_date_round_to_weekday!(ToPreviousWednesday, Wednesday, true); -impl_date_round_to_weekday!(ToPreviousThursday, Thursday, true); -impl_date_round_to_weekday!(ToPreviousFriday, Friday, true); -impl_date_round_to_weekday!(ToPreviousSaturday, Saturday, true); -impl_date_round_to_weekday!(ToPreviousSunday, Sunday, true); -impl_date_round_to_weekday!(ToNextMonday, Monday, false); -impl_date_round_to_weekday!(ToNextTuesday, Tuesday, false); -impl_date_round_to_weekday!(ToNextWednesday, Wednesday, false); -impl_date_round_to_weekday!(ToNextThursday, Thursday, false); -impl_date_round_to_weekday!(ToNextFriday, Friday, false); -impl_date_round_to_weekday!(ToNextSaturday, Saturday, false); -impl_date_round_to_weekday!(ToNextSunday, Sunday, false); - -fn previous_or_next_date_day(date: &Date, target: Weekday, is_previous: bool) -> i32 { - let dir = if is_previous { -1 } else { 1 }; - let mut days_diff = (dir - * (target.to_monday_zero_offset() as i32 - date.weekday().to_monday_zero_offset() as i32) - + 7) - % 7; - - days_diff = if days_diff == 0 { 7 } else { days_diff }; - - clamp_date(date_to_inner_number(date) as i64 + (dir * days_diff) as i64) -} - -impl ToNumber for ToPreviousMonday { - fn to_number(dt: &Zoned) -> i32 { - previous_or_next_day(dt, Weekday::Monday, true) - } -} - -impl ToNumber for ToPreviousTuesday { - fn to_number(dt: &Zoned) -> i32 { - previous_or_next_day(dt, Weekday::Tuesday, true) - } -} - -impl ToNumber for ToPreviousWednesday { - fn to_number(dt: &Zoned) -> i32 { - previous_or_next_day(dt, Weekday::Wednesday, true) - } -} - -impl ToNumber for ToPreviousThursday { - fn to_number(dt: &Zoned) -> i32 { - previous_or_next_day(dt, Weekday::Thursday, true) - } -} - -impl ToNumber for ToPreviousFriday { - fn to_number(dt: &Zoned) -> i32 { - previous_or_next_day(dt, Weekday::Friday, true) - } -} - -impl ToNumber for ToPreviousSaturday { - fn to_number(dt: &Zoned) -> i32 { - previous_or_next_day(dt, Weekday::Saturday, true) - } -} - -impl ToNumber for ToPreviousSunday { - fn to_number(dt: &Zoned) -> i32 { - previous_or_next_day(dt, Weekday::Sunday, true) - } -} - -impl ToNumber for ToNextMonday { - fn to_number(dt: &Zoned) -> i32 { - previous_or_next_day(dt, Weekday::Monday, false) - } -} - -impl ToNumber for ToNextTuesday { - fn to_number(dt: &Zoned) -> i32 { - previous_or_next_day(dt, Weekday::Tuesday, false) - } -} - -impl ToNumber for ToNextWednesday { - fn to_number(dt: &Zoned) -> i32 { - previous_or_next_day(dt, Weekday::Wednesday, false) - } -} - -impl ToNumber for ToNextThursday { - fn to_number(dt: &Zoned) -> i32 { - previous_or_next_day(dt, Weekday::Thursday, false) - } -} - -impl ToNumber for ToNextFriday { - fn to_number(dt: &Zoned) -> i32 { - previous_or_next_day(dt, Weekday::Friday, false) - } -} - -impl ToNumber for ToNextSaturday { - fn to_number(dt: &Zoned) -> i32 { - previous_or_next_day(dt, Weekday::Saturday, false) - } -} - -impl ToNumber for ToNextSunday { - fn to_number(dt: &Zoned) -> i32 { - previous_or_next_day(dt, Weekday::Sunday, false) - } -} - -pub fn previous_or_next_day(dt: &Zoned, target: Weekday, is_previous: bool) -> i32 { - let dir = if is_previous { -1 } else { 1 }; - let mut days_diff = (dir - * (target.to_monday_zero_offset() as i32 - - dt.date().weekday().to_monday_zero_offset() as i32) - + 7) - % 7; - - days_diff = if days_diff == 0 { 7 } else { days_diff }; - - datetime_to_date_inner_number(dt) + dir * days_diff -} - -/// PostgreSQL to strftime format specifier mappings -/// -/// The vector contains tuples of (postgres_format, strftime_format): -/// - For case-insensitive PostgreSQL formats (e.g., "YYYY"), any case variation will match -/// - For case-sensitive strftime formats (prefixed with '%'), exact case matching is required -/// -/// Note: The sort order (by descending key length) is critical for correct pattern matching -static PG_STRFTIME_MAPPINGS: LazyLock> = LazyLock::new(|| { - let mut mappings = vec![ - // ============================================== - // Case-insensitive PostgreSQL format specifiers - // (will match regardless of letter case) - // ============================================== - // Date components - ("YYYY", "%Y"), // 4-digit year - ("YY", "%y"), // 2-digit year - ("MMMM", "%B"), // Full month name - ("MON", "%b"), // Abbreviated month name (special word boundary handling) - ("MM", "%m"), // Month number (01-12) - ("DD", "%d"), // Day of month (01-31) - ("DY", "%a"), // Abbreviated weekday name - // Time components - ("HH24", "%H"), // 24-hour format (00-23) - ("HH12", "%I"), // 12-hour format (01-12) - ("AM", "%p"), // AM/PM indicator (matches both AM/PM) - ("PM", "%p"), // AM/PM indicator (matches both AM/PM) - ("MI", "%M"), // Minutes (00-59) - ("SS", "%S"), // Seconds (00-59) - ("FF", "%f"), // Fractional seconds - // Special cases - ("UUUU", "%G"), // ISO week-numbering year - ("TZHTZM", "%z"), // Timezone as ±HHMM - ("TZH:TZM", "%z"), // Timezone as ±HH:MM - ("TZH", "%:::z"), // Timezone hour only - // ============================================== - // Case-sensitive strftime format specifiers - // (must match exactly including case) - // ============================================== - ("%Y", "%Y"), // Year aliases - ("%y", "%y"), - ("%B", "%B"), // Month aliases - ("%b", "%b"), - ("%m", "%m"), - ("%d", "%d"), // Day aliases - ("%a", "%a"), // Weekday alias - ("%H", "%H"), // Hour aliases - ("%I", "%I"), - ("%p", "%p"), // AM/PM indicator - ("%M", "%M"), // Minute alias - ("%S", "%S"), // Second alias - ("%f", "%f"), // Fractional second alias - ("%G", "%G"), // ISO year alias - ("%z", "%z"), // Timezone aliases - ("%:::z", "%:::z"), // Timezone hour alias - ]; - - // Critical: Sort by descending key length to ensure longest possible matches are found first - // This prevents shorter patterns from incorrectly matching parts of longer patterns - mappings.sort_by(|a, b| b.0.len().cmp(&a.0.len())); - mappings -}); - -static PG_KEY_LENGTHS: LazyLock> = - LazyLock::new(|| PG_STRFTIME_MAPPINGS.iter().map(|(k, _)| k.len()).collect()); - -fn starts_with_ignore_case(text: &str, prefix: &str) -> bool { - if text.len() < prefix.len() { - return false; - } - text.chars() - .zip(prefix.chars()) - .all(|(c1, c2)| c1.to_lowercase().eq(c2.to_lowercase())) -} - -fn is_word_char(c: char) -> bool { - c.is_ascii_alphanumeric() || c == '_' -} - -#[inline] -pub fn pg_format_to_strftime(pg_format_string: &str) -> String { - let mut result = String::with_capacity(pg_format_string.len() + 16); - let mut current_byte_idx = 0; - let format_len = pg_format_string.len(); - - while current_byte_idx < format_len { - let remaining_slice = &pg_format_string[current_byte_idx..]; - let mut matched = false; - let first_char = remaining_slice.chars().next().unwrap_or('\0'); - - for ((key, value), &key_len) in PG_STRFTIME_MAPPINGS.iter().zip(PG_KEY_LENGTHS.iter()) { - if !key.is_empty() && !first_char.eq_ignore_ascii_case(&key.chars().next().unwrap()) { - continue; - } - - let is_case_sensitive_key = key.starts_with('%'); - let is_current_match = if is_case_sensitive_key { - remaining_slice.starts_with(key) - } else { - starts_with_ignore_case(remaining_slice, key) - }; - - if is_current_match { - let mut is_valid_match = true; - if !is_case_sensitive_key && key.eq_ignore_ascii_case("MON") { - let next_byte_idx = current_byte_idx + key_len; - - if current_byte_idx > 0 { - if let Some(prev_char) = - pg_format_string[..current_byte_idx].chars().next_back() - { - if is_word_char(prev_char) { - is_valid_match = false; - } - } - } - - if is_valid_match && next_byte_idx < format_len { - if let Some(next_char) = pg_format_string[next_byte_idx..].chars().next() { - if is_word_char(next_char) { - is_valid_match = false; - } - } - } - } - - if is_valid_match { - result.push_str(value); - current_byte_idx += key_len; - matched = true; - break; - } - } - } - - if !matched { - let c = first_char; - result.push(c); - current_byte_idx += c.len_utf8(); - } - } - - result -} diff --git a/src/query/expression/src/utils/display.rs b/src/query/expression/src/utils/display.rs index 2418cb54150a5..1adcbb6ef425f 100755 --- a/src/query/expression/src/utils/display.rs +++ b/src/query/expression/src/utils/display.rs @@ -296,7 +296,7 @@ impl Display for ScalarRef<'_> { ScalarRef::String(s) => write!(f, "{}", QuotedString(s, '\'')), ScalarRef::Timestamp(t) => write!(f, "'{}'", timestamp_to_string(*t, &TimeZone::UTC)), ScalarRef::TimestampTz(t) => write!(f, "'{}'", t), - ScalarRef::Date(d) => write!(f, "'{}'", date_to_string(*d as i64, &TimeZone::UTC)), + ScalarRef::Date(d) => write!(f, "'{}'", date_to_string(*d as i64)), ScalarRef::Interval(interval) => write!(f, "'{}'", interval_to_string(interval)), ScalarRef::Array(col) => write!(f, "[{}]", col.iter().join(", ")), ScalarRef::Map(col) => { @@ -368,7 +368,7 @@ pub fn scalar_ref_to_string(value: &ScalarRef) -> String { match value { ScalarRef::String(s) => s.to_string(), ScalarRef::Timestamp(t) => format!("{}", timestamp_to_string(*t, &TimeZone::UTC)), - ScalarRef::Date(d) => format!("{}", date_to_string(*d as i64, &TimeZone::UTC)), + ScalarRef::Date(d) => format!("{}", date_to_string(*d as i64)), ScalarRef::Interval(interval) => format!("{}", interval_to_string(interval)), ScalarRef::Bitmap(bits) => { let rb = deserialize_bitmap(bits).unwrap(); diff --git a/src/query/expression/src/utils/mod.rs b/src/query/expression/src/utils/mod.rs index 03a21207bfd2e..67b3c48a47fba 100644 --- a/src/query/expression/src/utils/mod.rs +++ b/src/query/expression/src/utils/mod.rs @@ -19,7 +19,6 @@ pub mod bitmap; pub mod block_debug; pub mod block_thresholds; mod column_from; -pub mod date_helper; pub mod display; pub mod filter_helper; pub mod serialize; diff --git a/src/query/formats/src/field_encoder/bytes.rs b/src/query/formats/src/field_encoder/bytes.rs index ff67324c30de8..c7d77c75ad73b 100644 --- a/src/query/formats/src/field_encoder/bytes.rs +++ b/src/query/formats/src/field_encoder/bytes.rs @@ -308,7 +308,7 @@ setting binary_output_format to 'UTF-8-LOSSY'." in_nested: bool, ) { let v = unsafe { column.get_unchecked(row_index) }; - let s = date_to_string(*v as i64, &self.common_settings.settings.jiff_timezone).to_string(); + let s = date_to_string(*v as i64); self.write_string_inner(s.as_bytes(), out_buf, in_nested); } diff --git a/src/query/formats/src/field_encoder/string.rs b/src/query/formats/src/field_encoder/string.rs index 4afa252e0702f..8d944aa308a6a 100644 --- a/src/query/formats/src/field_encoder/string.rs +++ b/src/query/formats/src/field_encoder/string.rs @@ -227,9 +227,7 @@ impl FieldEncoderToString { #[inline] fn date_text(&self, value: i32) -> String { match self.settings.http_json_result_mode { - HttpHandlerDataFormat::Display => { - date_to_string(value as i64, &self.settings.jiff_timezone).to_string() - } + HttpHandlerDataFormat::Display => date_to_string(value as i64), HttpHandlerDataFormat::Driver => value.to_string(), } } diff --git a/src/query/formats/src/output_format/json.rs b/src/query/formats/src/output_format/json.rs index cca787a468344..34a5c38a6ac28 100644 --- a/src/query/formats/src/output_format/json.rs +++ b/src/query/formats/src/output_format/json.rs @@ -15,10 +15,11 @@ use databend_common_expression::DataBlock; use databend_common_expression::ScalarRef; use databend_common_expression::TableSchemaRef; -use databend_common_expression::date_helper::DateConverter; use databend_common_expression::types::VectorScalarRef; +use databend_common_expression::types::date::date_from_days; use databend_common_expression::types::interval::interval_to_string; use databend_common_expression::types::number::NumberScalar; +use databend_common_expression::types::timestamp::timestamp_from_micros; use databend_common_io::deserialize_bitmap; use databend_common_io::prelude::OutputFormatSettings; use geozero::ToJson; @@ -94,14 +95,14 @@ fn scalar_to_json( }), ScalarRef::Decimal(x) => Ok(serde_json::to_value(x.to_string()).unwrap()), ScalarRef::Date(v) => { - let dt = DateConverter::to_date(&v, &format.jiff_timezone); + let dt = date_from_days(v); Ok(serde_json::to_value(strtime::format("%Y-%m-%d", dt).unwrap()).unwrap()) } ScalarRef::Interval(v) => { Ok(serde_json::to_value(interval_to_string(&v).to_string()).unwrap()) } ScalarRef::Timestamp(v) => { - let dt = DateConverter::to_timestamp(&v, &format.jiff_timezone); + let dt = timestamp_from_micros(v, &format.jiff_timezone); Ok(serde_json::to_value(strtime::format("%Y-%m-%d %H:%M:%S", &dt).unwrap()).unwrap()) } ScalarRef::TimestampTz(v) => Ok(serde_json::to_value(v.to_string()).unwrap()), diff --git a/src/query/functions/benches/bench.rs b/src/query/functions/benches/bench.rs index 37109aa2b7097..647208de53f99 100644 --- a/src/query/functions/benches/bench.rs +++ b/src/query/functions/benches/bench.rs @@ -318,12 +318,12 @@ mod datetime_fast_path { use databend_common_expression::Evaluator; use databend_common_expression::Expr; use databend_common_expression::FunctionContext; - use databend_common_expression::date_helper::DateConverter; use databend_common_expression::type_check; use databend_common_expression::types::DataType; use databend_common_expression::types::string::StringColumn; use databend_common_expression::types::string::StringColumnBuilder; use databend_common_expression::types::timestamp::microseconds_to_days; + use databend_common_expression::types::timestamp::timestamp_from_micros; use databend_common_expression::types::timestamp::timestamp_to_string; use databend_common_expression_test_support as parser; use databend_common_functions::BUILTIN_FUNCTIONS; @@ -360,7 +360,7 @@ mod datetime_fast_path { let formatted = timestamp_to_string(micros, &tz_sh).to_string(); string_builder.put_and_commit(formatted); - let zoned = micros.to_timestamp(&tz_sh); + let zoned = timestamp_from_micros(micros, &tz_sh); let offset_secs = zoned.offset().seconds(); let offset_hours = offset_secs / 3600; let offset_minutes = (offset_secs.abs() % 3600) / 60; diff --git a/src/query/functions/src/scalars/timestamp/src/date_arithmetic.rs b/src/query/functions/src/scalars/timestamp/src/date_arithmetic.rs new file mode 100644 index 0000000000000..0d87a2438e319 --- /dev/null +++ b/src/query/functions/src/scalars/timestamp/src/date_arithmetic.rs @@ -0,0 +1,1872 @@ +// Copyright 2021 Datafuse Labs +// +// Licensed 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. + +use databend_common_column::types::months_days_micros; +use databend_common_column::types::timestamp_tz; +use databend_common_expression::FunctionDomain; +use databend_common_expression::FunctionRegistry; +use databend_common_expression::types::DateType; +use databend_common_expression::types::F64; +use databend_common_expression::types::Float64Type; +use databend_common_expression::types::Int32Type; +use databend_common_expression::types::IntervalType; +use databend_common_expression::types::TimestampType; +use databend_common_expression::types::date::DATE_MAX; +use databend_common_expression::types::date::DATE_MIN; +use databend_common_expression::types::date::clamp_date; +use databend_common_expression::types::date::date_from_days; +use databend_common_expression::types::number::Int64Type; +use databend_common_expression::types::number::SimpleDomain; +use databend_common_expression::types::timestamp::MICROS_PER_SEC; +use databend_common_expression::types::timestamp::TIMESTAMP_MAX; +use databend_common_expression::types::timestamp::TIMESTAMP_MIN; +use databend_common_expression::types::timestamp::clamp_timestamp; +use databend_common_expression::types::timestamp::timestamp_from_micros; +use databend_common_expression::vectorize_2_arg; +use databend_common_expression::vectorize_with_builder_2_arg; +use databend_common_timezone::DateTimeComponents; +use databend_common_timezone::fast_components_from_timestamp; +use databend_common_timezone::fast_utc_from_local; +use jiff::SignedDuration; +use jiff::SpanRelativeTo; +use jiff::Unit; +use jiff::civil::Date; +use jiff::civil::DateTime; +use jiff::civil::Weekday; +use jiff::civil::date; +use jiff::tz::TimeZone; +use num_traits::AsPrimitive; + +use crate::date_extract::ToNumber; +use crate::date_extract::ToQuarter; + +const MICROSECS_PER_DAY: i64 = 86_400_000_000; + +// Timestamp arithmetic factors. +const FACTOR_HOUR: i64 = 3600; +const FACTOR_MINUTE: i64 = 60; +const FACTOR_SECOND: i64 = 1; +const LAST_DAY_LUT: [i8; 13] = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; + +fn eval_years_base( + year: i16, + month: i8, + day: i8, + delta: i64, + _add_months: bool, +) -> std::result::Result { + let new_year = year as i64 + delta; + let mut new_day = day; + if std::intrinsics::unlikely(month == 2 && day == 29) { + new_day = last_day_of_year_month(new_year as i16, month); + } + match Date::new(new_year as i16, month, new_day) { + Ok(d) => Ok(d), + Err(e) => Err(format!("Invalid date: {}", e)), + } +} + +fn eval_months_base( + year: i16, + month: i8, + day: i8, + delta: i64, + add_months: bool, +) -> std::result::Result { + let total_months = (month as i64 + delta - 1) as i16; + let mut new_year = year + (total_months / 12); + let mut new_month0 = total_months % 12; + if new_month0 < 0 { + new_year -= 1; + new_month0 += 12; + } + + // Handle month last day overflow, "2020-2-29" + "1 year" should be "2021-2-28", or "1990-1-31" + "3 month" should be "1990-4-30". + // For ADD_MONTHS only, if the original day is the last day of the month, the result day of month will be the last day of the result month. + let new_month = (new_month0 + 1) as i8; + // Determine the correct day + let max_day = last_day_of_year_month(new_year, new_month); + let new_day = if add_months && day == last_day_of_year_month(year, month) { + max_day + } else { + day.min(max_day) + }; + + match Date::new(new_year, (new_month0 + 1) as i8, new_day) { + Ok(d) => Ok(d), + Err(e) => Err(format!("Invalid date: {}", e)), + } +} + +// Get the last day of the year month, could be 28(non leap Feb), 29(leap year Feb), 30 or 31 +pub(super) fn last_day_of_year_month(year: i16, month: i8) -> i8 { + let is_leap_year = (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0); + if std::intrinsics::unlikely(month == 2 && is_leap_year) { + return 29; + } + LAST_DAY_LUT[month as usize] +} + +macro_rules! impl_interval_year_month { + ($vis:vis $name:ident, $op:expr) => { + #[derive(Clone)] + $vis struct $name; + + impl $name { + $vis fn eval_date( + date: i32, + delta: impl AsPrimitive, + add_months: bool, + ) -> std::result::Result { + let date = date_from_days(date); + let new_date = $op( + date.year(), + date.month(), + date.day(), + delta.as_(), + add_months, + )?; + + Ok(clamp_date( + new_date + .since((Unit::Day, Date::new(1970, 1, 1).unwrap())) + .unwrap() + .get_days() as i64, + )) + } + + $vis fn eval_timestamp( + us: i64, + tz: &TimeZone, + delta: impl AsPrimitive, + add_months: bool, + ) -> std::result::Result { + let ts = timestamp_from_micros(us, tz); + let original_offset = ts.offset().seconds(); + + if let Some(components) = fast_components_from_timestamp(us, tz) { + let new_date = $op( + components.year as i16, + components.month as i8, + components.day as i8, + delta.as_(), + add_months, + )?; + if let Some(mut new_ts) = fast_utc_from_local( + tz, + new_date.year() as i32, + new_date.month() as u8, + new_date.day() as u8, + components.hour, + components.minute, + components.second, + components.micro, + ) { + if let Some(new_components) = fast_components_from_timestamp(new_ts, tz) { + if new_components.offset_seconds != original_offset { + let shift_secs = + (new_components.offset_seconds - original_offset) as i64; + let shift_micros = shift_secs.saturating_mul(MICROS_PER_SEC); + new_ts = new_ts.checked_add(shift_micros).unwrap_or_else(|| { + if shift_micros.is_negative() { + i64::MIN + } else { + i64::MAX + } + }); + } + clamp_timestamp(&mut new_ts); + return Ok(new_ts); + } + } + } + + let new_date = $op(ts.year(), ts.month(), ts.day(), delta.as_(), add_months)?; + + let local = + new_date.at(ts.hour(), ts.minute(), ts.second(), ts.subsec_nanosecond()); + let mut zoned = match local.to_zoned(tz.clone()) { + Ok(z) => z, + Err(e) => match local.checked_add(SignedDuration::from_secs(3600)) { + Ok(res2) => res2 + .to_zoned(tz.clone()) + .map_err(|err| format!("{}", err))?, + Err(_) => return Err(format!("{}", e)), + }, + }; + if zoned.offset().seconds() != original_offset { + let shift = (zoned.offset().seconds() - original_offset) as i64; + if let Ok(adj_local) = local.checked_add(SignedDuration::from_secs(shift)) { + if let Ok(adj_zoned) = adj_local.to_zoned(tz.clone()) { + zoned = adj_zoned; + } + } + } + let mut ts = zoned.timestamp().as_microsecond(); + clamp_timestamp(&mut ts); + Ok(ts) + } + } + }; +} + +impl_interval_year_month!(EvalYearsImpl, eval_years_base); +impl_interval_year_month!(pub EvalMonthsImpl, eval_months_base); + +/// Compare two `DateTimeComponents` by their time-of-day portion only. +fn components_time_less_than(a: &DateTimeComponents, b: &DateTimeComponents) -> bool { + (a.hour, a.minute, a.second, a.micro) < (b.hour, b.minute, b.second, b.micro) +} + +fn date_from_components(c: &DateTimeComponents) -> Option { + Date::new(c.year as i16, c.month as i8, c.day as i8).ok() +} + +#[inline] +pub(super) fn timestamp_tz_components_via_lut(value: timestamp_tz) -> Option { + let offset = value.micros_offset()?; + let local = value.timestamp().checked_add(offset)?; + fast_components_from_timestamp(local, &TimeZone::UTC) +} + +fn datetime_from_components(c: &DateTimeComponents) -> Option { + let date = date_from_components(c)?; + Some(date.at( + c.hour as i8, + c.minute as i8, + c.second as i8, + (c.micro * 1_000) as i32, + )) +} + +impl EvalYearsImpl { + fn eval_date_diff(date_start: i32, date_end: i32) -> i32 { + let date_start = date_from_days(date_start); + let date_end = date_from_days(date_end); + (date_end.year() - date_start.year()) as i32 + } + + fn eval_date_between(date_start: i32, date_end: i32) -> i32 { + if date_start == date_end { + return 0; + } + if date_start > date_end { + return -Self::eval_date_between(date_end, date_start); + } + + let date_start = date_from_days(date_start); + let date_end = date_from_days(date_end); + + let mut years = date_end.year() - date_start.year(); + + // If the end month is less than the start month, + // or the months are equal but the end day is less than the start day, + // the last year is incomplete, minus 1 + if (date_end.month() < date_start.month()) + || (date_end.month() == date_start.month() && date_end.day() < date_start.day()) + { + years -= 1; + } + + years as i32 + } + + fn eval_timestamp_diff(date_start: i64, date_end: i64, tz: &TimeZone) -> i64 { + if let (Some(start), Some(end)) = ( + fast_components_from_timestamp(date_start, tz), + fast_components_from_timestamp(date_end, tz), + ) { + return (end.year as i64) - (start.year as i64); + } + let date_start = timestamp_from_micros(date_start, tz); + let date_end = timestamp_from_micros(date_end, tz); + date_end.year() as i64 - date_start.year() as i64 + } + + fn eval_timestamp_between(date_start: i64, date_end: i64, tz: &TimeZone) -> i64 { + if date_start == date_end { + return 0; + } + if date_start > date_end { + return -Self::eval_timestamp_between(date_end, date_start, tz); + } + if let (Some(start), Some(end)) = ( + fast_components_from_timestamp(date_start, tz), + fast_components_from_timestamp(date_end, tz), + ) { + let mut years = end.year - start.year; + let start_is_feb_29 = start.month == 2 && start.day == 29; + let end_is_feb_28 = end.month == 2 && end.day == 28; + let end_before_start_date = (end.month < start.month) + || (end.month == start.month && end.day < start.day) + || (end.month == start.month + && end.day == start.day + && components_time_less_than(&end, &start)); + if !(start_is_feb_29 && end_is_feb_28) && end_before_start_date { + years -= 1; + } + return years as i64; + } + let start = timestamp_from_micros(date_start, tz); + let end = timestamp_from_micros(date_end, tz); + + let mut years = end.year() - start.year(); + + // Handle special cases on February 29 in leap years: + // If the start date is February 29 and the end date is February 28, it is considered a full year (leap year to regular year). + // Otherwise, the end date, month day, must be >= the start date, month day, and the time must be reached + let start_month = start.month(); + let start_day = start.day(); + + let end_month = end.month(); + let end_day = end.day(); + + let start_is_feb_29 = start_month == 2 && start_day == 29; + let end_is_feb_28 = end_month == 2 && end_day == 28; + + let end_before_start_date = (end_month < start_month) + || (end_month == start_month && end_day < start_day) + || (end_month == start_month && end_day == start_day && end.time() < start.time()); + + if start_is_feb_29 && end_is_feb_28 { + } else if end_before_start_date { + years -= 1; + } + + years as i64 + } +} + +struct EvalISOYearsImpl; +impl EvalISOYearsImpl { + fn eval_date_diff(date_start: i32, date_end: i32) -> i32 { + let date_start = date_from_days(date_start); + let date_end = date_from_days(date_end); + date_end.iso_week_date().year() as i32 - date_start.iso_week_date().year() as i32 + } + + fn eval_date_between(date_start: i32, date_end: i32) -> i32 { + if date_start == date_end { + return 0; + } + if date_start > date_end { + return -Self::eval_date_between(date_end, date_start); + } + let date_start = date_from_days(date_start); + let date_end = date_from_days(date_end); + let mut years = date_end.iso_week_date().year() - date_start.iso_week_date().year(); + if (date_end.month() < date_start.month()) + || (date_end.month() == date_start.month() && date_end.day() < date_start.day()) + { + years -= 1; + } + + years as i32 + } + + fn eval_timestamp_diff(date_start: i64, date_end: i64, tz: &TimeZone) -> i64 { + if let (Some(start), Some(end)) = ( + fast_components_from_timestamp(date_start, tz), + fast_components_from_timestamp(date_end, tz), + ) { + let (start_year, _) = start.iso_year_week(); + let (end_year, _) = end.iso_year_week(); + return (end_year - start_year) as i64; + } + let date_start = timestamp_from_micros(date_start, tz); + let date_end = timestamp_from_micros(date_end, tz); + date_end.date().iso_week_date().year() as i64 - date_start.iso_week_date().year() as i64 + } + + fn eval_timestamp_between(date_start: i64, date_end: i64, tz: &TimeZone) -> i64 { + if date_start == date_end { + return 0; + } + if date_start > date_end { + return -Self::eval_timestamp_between(date_end, date_start, tz); + } + if let (Some(start), Some(end)) = ( + fast_components_from_timestamp(date_start, tz), + fast_components_from_timestamp(date_end, tz), + ) { + let mut years = end.year - start.year; + let start_is_feb_29 = start.month == 2 && start.day == 29; + let end_is_feb_28 = end.month == 2 && end.day == 28; + let end_before_start_date = (end.month < start.month) + || (end.month == start.month && end.day < start.day) + || (end.month == start.month + && end.day == start.day + && components_time_less_than(&end, &start)); + if !(start_is_feb_29 && end_is_feb_28) && end_before_start_date { + years -= 1; + } + return years as i64; + } + + let start = timestamp_from_micros(date_start, tz); + let end = timestamp_from_micros(date_end, tz); + let mut years = + end.date().iso_week_date().year() as i64 - start.date().iso_week_date().year() as i64; + let start_month = start.month(); + let start_day = start.day(); + + let end_month = end.month(); + let end_day = end.day(); + + let start_is_feb_29 = start_month == 2 && start_day == 29; + let end_is_feb_28 = end_month == 2 && end_day == 28; + + let end_before_start_date = (end_month < start_month) + || (end_month == start_month && end_day < start_day) + || (end_month == start_month && end_day == start_day && end.time() < start.time()); + + if start_is_feb_29 && end_is_feb_28 { + } else if end_before_start_date { + years -= 1; + } + + years + } +} + +struct EvalYearWeeksImpl; +impl EvalYearWeeksImpl { + fn yearweek(date: Date) -> i32 { + let iso_week = date.iso_week_date(); + (iso_week.year() as i32 * 100) + iso_week.week() as i32 + } + + fn yearweek_from_components(components: &DateTimeComponents) -> i32 { + let (year, week) = components.iso_year_week(); + year * 100 + week as i32 + } + + fn eval_date_diff(date_start: i32, date_end: i32) -> i32 { + let date_start = date_from_days(date_start); + let date_end = date_from_days(date_end); + let end = Self::yearweek(date_end); + let start = Self::yearweek(date_start); + + end - start + } + + fn eval_timestamp_diff(date_start: i64, date_end: i64, tz: &TimeZone) -> i64 { + if let (Some(start), Some(end)) = ( + fast_components_from_timestamp(date_start, tz), + fast_components_from_timestamp(date_end, tz), + ) { + let start_yw = Self::yearweek_from_components(&start) as i64; + let end_yw = Self::yearweek_from_components(&end) as i64; + return end_yw - start_yw; + } + let date_start = timestamp_from_micros(date_start, tz); + let date_end = timestamp_from_micros(date_end, tz); + let end = Self::yearweek(date_end.date()) as i64; + let start = Self::yearweek(date_start.date()) as i64; + + end - start + } + + // In duckdb datesub(yearweek, ) is same as datesub(week, ) But we can contain these logic + // fn week_end(date: Date) -> Date { + // let weekday = date.weekday(); + // + // let days_to_sunday = 7 - weekday.to_monday_one_offset(); // monday=1, sunday=7 + // let dur = SignedDuration::from_hours(days_to_sunday as i64 * 24); + // date.checked_add(dur).unwrap() + // } + // pub fn eval_date_between(start: i32, end: i32, tz: &TimeZone) -> i32 { + // if start == end { + // return 0; + // } + // + // let (earlier, later, sign) = if start <= end { + // (start, end, 1) + // } else { + // (end, start, -1) + // }; + // + // let earlier = date_from_days(earlier); + // let later = date_from_days(later); + // + // let start_yw = Self::yearweek(earlier); + // let end_yw = Self::yearweek(later); + // + // let mut diff = end_yw - start_yw; + // + // If the end week is incomplete, subtract 1 + // if later < Self::week_end(later) { + // diff -= 1; + // } + // + // diff * sign + // } + // pub fn eval_timestamp_between(start: i64, end: i64, tz: &TimeZone) -> i64 { + // if start == end { + // return 0; + // } + // + // let (earlier, later, sign) = if start <= end { + // (start, end, 1) + // } else { + // (end, start, -1) + // }; + // + // let earlier = timestamp_from_micros(earlier, tz); + // let later = timestamp_from_micros(later, tz); + // + // let start_yw = Self::yearweek(earlier.date()); + // let end_yw = Self::yearweek(later.date()); + // + // let mut diff = end_yw - start_yw; + // + // let week_end = EvalYearWeeksImpl::week_end(later.date()); + // if later.datetime() < week_end.at(23, 59, 59, 999_999_999) { + // diff -= 1; + // } + // + // diff as i64 * sign + // } +} + +struct EvalQuartersImpl; + +impl EvalQuartersImpl { + fn eval_date_diff(date_start: i32, date_end: i32) -> i32 { + let date_start = date_from_days(date_start); + let date_end = date_from_days(date_end); + let start_quarter = (date_start.month() as i32 - 1) / 3 + 1; + let end_quarter = (date_end.month() as i32 - 1) / 3 + 1; + (date_end.year() - date_start.year()) as i32 * 4 + end_quarter - start_quarter + } + + fn eval_timestamp_diff(date_start: i64, date_end: i64, tz: &TimeZone) -> i64 { + if let (Some(start), Some(end)) = ( + fast_components_from_timestamp(date_start, tz), + fast_components_from_timestamp(date_end, tz), + ) { + let start_quarter = ((start.month as i64 - 1) / 3) + 1; + let end_quarter = ((end.month as i64 - 1) / 3) + 1; + return (end.year as i64 - start.year as i64) * 4 + end_quarter - start_quarter; + } + let date_start = timestamp_from_micros(date_start, tz); + let date_end = timestamp_from_micros(date_end, tz); + (date_end.year() - date_start.year()) as i64 * 4 + ToQuarter::to_number(&date_end) as i64 + - ToQuarter::to_number(&date_start) as i64 + } + + // Return date corresponding to quarter number (1~4) + // fn quarter(month: i8) -> i32 { + // ((month - 1) / 3 + 1) as i32 + // } + // + // + // fn quarter_start(year: i16, month: i8) -> (i16, i8) { + // let q = ((month - 1) / 3) + 1; + // let start_month = (q - 1) * 3 + 1; + // (year, start_month) + // } + // + // DuckDB directly calc month/3 + // pub fn eval_date_between(start: i32, end: i32, tz: &TimeZone) -> i32 { + // if start == end { + // return 0; + // } + // let (earlier, later, sign) = if start <= end { + // (start, end, 1) + // } else { + // (end, start, -1) + // }; + // + // let earlier = date_from_days(earlier); + // let later = date_from_days(later); + // + // let start_year = earlier.year(); + // let start_quarter = Self::quarter(earlier.month()); + // let end_year = later.year(); + // let end_quarter = Self::quarter(later.month()); + // + // let mut diff = + // (end_year - start_year) as i64 * 4 + (end_quarter as i64 - start_quarter as i64); + // + // let (last_quarter_start_year, last_quarter_start_month) = + // Self::quarter_start(end_year, later.month()); + // let last_quarter_start_date = date(last_quarter_start_year, last_quarter_start_month, 1); + // + // + // if later < last_quarter_start_date { + // diff -= 1; + // } + // + // (diff * sign) as i32 + // } + // pub fn eval_timestamp_between(start: i64, end: i64, tz: &TimeZone) -> i64 { + // if start == end { + // return 0; + // } + // + // let (earlier, later, sign) = if start <= end { + // (start, end, 1) + // } else { + // (end, start, -1) + // }; + // + // let earlier = timestamp_from_micros(earlier, tz); + // let later = timestamp_from_micros(later, tz); + // + // let start_year = earlier.year(); + // let start_quarter = Self::quarter(earlier.month()); + // let end_year = later.year(); + // let end_quarter = Self::quarter(later.month()); + // + // let mut diff = + // (end_year - start_year) as i64 * 4 + (end_quarter as i64 - start_quarter as i64); + // + // let (last_quarter_start_year, last_quarter_start_month) = + // Self::quarter_start(later.year(), later.month()); + // let last_quarter_start_date = date(last_quarter_start_year, last_quarter_start_month, 1); + // let last_quarter_start_datetime = last_quarter_start_date.to_datetime(earlier.time()); + // + // if later.datetime() < last_quarter_start_datetime { + // diff -= 1; + // } + // diff * sign + // } +} + +impl EvalMonthsImpl { + fn eval_date_diff(date_start: i32, date_end: i32) -> i32 { + let date_start = date_from_days(date_start); + let date_end = date_from_days(date_end); + (date_end.year() - date_start.year()) as i32 * 12 + date_end.month() as i32 + - date_start.month() as i32 + } + + fn eval_date_between(start: i32, end: i32) -> i32 { + if start == end { + return 0; + } + if start > end { + return -Self::eval_date_between(end, start); + } + + let start = date_from_days(start); + let end = date_from_days(end); + + let year_diff = end.year() - start.year(); + let month_diff = end.month() as i32 - start.month() as i32; + let mut months = year_diff as i32 * 12 + month_diff; + + if end.day() < start.day() { + months -= 1; + } + + months + } + + fn eval_timestamp_diff(date_start: i64, date_end: i64) -> i64 { + EvalMonthsImpl::eval_date_diff( + (date_start / MICROSECS_PER_DAY) as i32, + (date_end / MICROSECS_PER_DAY) as i32, + ) as i64 + } + + fn eval_timestamp_between(start: i64, end: i64, tz: &TimeZone) -> i64 { + if start == end { + return 0; + } + if start > end { + return -Self::eval_timestamp_between(end, start, tz); + } + if let (Some(start_c), Some(end_c)) = ( + fast_components_from_timestamp(start, tz), + fast_components_from_timestamp(end, tz), + ) { + let year_diff = end_c.year - start_c.year; + let month_diff = end_c.month as i32 - start_c.month as i32; + let mut months = year_diff as i64 * 12 + month_diff as i64; + if (end_c.day < start_c.day) + || (end_c.day == start_c.day && components_time_less_than(&end_c, &start_c)) + { + months -= 1; + } + return months; + } + + let start = timestamp_from_micros(start, tz); + let end = timestamp_from_micros(end, tz); + let year_diff = end.year() - start.year(); + let month_diff = end.month() as i64 - start.month() as i64; + let mut months = year_diff as i64 * 12 + month_diff; + + // Determine the time sequence. If the end time is less than the start time, it is incomplete + if (end.day() < start.day()) || (end.day() == start.day() && end.time() < start.time()) { + months -= 1; + } + + months + } + + // current we don't consider tz here + fn months_between_ts(ts_a: i64, ts_b: i64) -> f64 { + EvalMonthsImpl::months_between( + (ts_a / 86_400_000_000) as i32, + (ts_b / 86_400_000_000) as i32, + ) + } + + fn months_between(date_a: i32, date_b: i32) -> f64 { + let date_a = Date::new(1970, 1, 1) + .unwrap() + .checked_add(SignedDuration::from_hours(date_a as i64 * 24)) + .unwrap(); + let date_b = Date::new(1970, 1, 1) + .unwrap() + .checked_add(SignedDuration::from_hours(date_b as i64 * 24)) + .unwrap(); + + let year_diff = (date_a.year() - date_b.year()) as i64; + let month_diff = date_a.month() as i64 - date_b.month() as i64; + + // Calculate total months difference + let total_months_diff = year_diff * 12 + month_diff; + + // Determine if special case for fractional part applies + let is_same_day_of_month = date_a.day() == date_b.day(); + + let are_both_end_of_month = + date_a.last_of_month() == date_a && date_b.last_of_month() == date_b; + let day_fraction = if is_same_day_of_month || are_both_end_of_month { + 0.0 + } else { + let day_diff = date_a.day() as i32 - date_b.day() as i32; + day_diff as f64 / 31.0 // Using 31-day month for fractional part + }; + + // Total difference including fractional part + total_months_diff as f64 + day_fraction + } +} + +struct EvalWeeksImpl; + +impl EvalWeeksImpl { + fn eval_date_diff(date_start: i32, date_end: i32) -> i32 { + // 1970-01-01 is ThursDay + let date_start = date_start / 7 + (date_start % 7 >= 4) as i32; + let date_end = date_end / 7 + (date_end % 7 >= 4) as i32; + date_end - date_start + } + + fn eval_timestamp_diff(date_start: i64, date_end: i64) -> i64 { + EvalWeeksImpl::eval_date_diff( + (date_start / MICROSECS_PER_DAY) as i32, + (date_end / MICROSECS_PER_DAY) as i32, + ) as i64 + } + + fn calculate_weeks_between_years( + start_year: i32, + end_year: i32, + start_week: u32, + end_week: u32, + ) -> i32 { + let mut weeks = 0; + let mut current_year = start_year + 1; + + fn iso_weeks(year: i32) -> i32 { + // Get the first day of the year + let first_day = date(year as i16, 1, 1); + + // Determine the weekday of the first day + let weekday = first_day.weekday(); + + // Check if the year starts on a Thursday. + if weekday == Weekday::Thursday { + return 53; + } + + // Check if the year starts on a Wednesday and is a leap year. + if weekday == Weekday::Wednesday + && (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)) + { + return 53; + } + 52 + } + while current_year < end_year { + weeks += iso_weeks(current_year); + current_year += 1; + } + + // add start_year weeks and end_year weeks + weeks += iso_weeks(start_year) - start_week as i32 + end_week as i32; + weeks + } + + fn eval_date_between(start: i32, end: i32) -> i32 { + if start == end { + return 0; + } + if start > end { + return -Self::eval_date_between(end, start); + } + + let earlier = date_from_days(start); + let later = date_from_days(end); + let mut weeks = Self::calculate_weeks_between_years( + earlier.year() as i32, + later.year() as i32, + earlier.iso_week_date().week() as u32, + later.iso_week_date().week() as u32, + ); + // Judge whether it is complete after the last week + let end_weekday = later.weekday(); + let days_since_monday = end_weekday.to_monday_one_offset() - 1; + let dur = SignedDuration::from_hours(days_since_monday as i64 * 24); + let monday_of_end_week = later.checked_sub(dur).unwrap(); + + if later < monday_of_end_week { + weeks -= 1; + } + + weeks + } + + fn eval_timestamp_between(start: i64, end: i64, tz: &TimeZone) -> i64 { + if start == end { + return 0; + } + if start > end { + return -Self::eval_timestamp_between(end, start, tz); + } + if let (Some(start_c), Some(end_c)) = ( + fast_components_from_timestamp(start, tz), + fast_components_from_timestamp(end, tz), + ) { + if let (Some(start_date), Some(end_date)) = + (date_from_components(&start_c), date_from_components(&end_c)) + { + let mut weeks = Self::calculate_weeks_between_years( + start_date.year() as i32, + end_date.year() as i32, + start_date.iso_week_date().week() as u32, + end_date.iso_week_date().week() as u32, + ) as i64; + let days_since_monday = end_c.weekday.to_monday_one_offset() - 1; + let dur = SignedDuration::from_hours(days_since_monday as i64 * 24); + let monday_of_end_week = end_date.checked_sub(dur).unwrap(); + let monday_dt = monday_of_end_week.at(0, 0, 0, 0); + if let Some(end_dt) = datetime_from_components(&end_c) { + if end_dt < monday_dt { + weeks -= 1; + } + } + return weeks; + } + } + + let earlier = timestamp_from_micros(start, tz); + let later = timestamp_from_micros(end, tz); + + let mut weeks = Self::calculate_weeks_between_years( + earlier.year() as i32, + later.year() as i32, + earlier.date().iso_week_date().week() as u32, + later.date().iso_week_date().week() as u32, + ) as i64; + // Judge whether it is complete after the last week + let end_date = later.date(); + let end_weekday = end_date.weekday(); + let days_since_monday = end_weekday.to_monday_one_offset() - 1; + let dur = SignedDuration::from_hours(days_since_monday as i64 * 24); + let monday_of_end_week = end_date.checked_sub(dur).unwrap(); + let monday_of_end_week_datetime = monday_of_end_week.at(0, 0, 0, 0); + + if later.datetime() < monday_of_end_week_datetime { + weeks -= 1; + } + weeks + } +} + +pub(super) struct EvalDaysImpl; + +impl EvalDaysImpl { + pub(super) fn eval_date(date: i32, delta: impl AsPrimitive) -> i32 { + clamp_date((date as i64).wrapping_add(delta.as_())) + } + + pub(super) fn eval_date_diff(date_start: i32, date_end: i32) -> i32 { + date_end - date_start + } + + pub(super) fn eval_timestamp(date: i64, delta: impl AsPrimitive) -> i64 { + let mut value = date.wrapping_add(delta.as_().wrapping_mul(MICROSECS_PER_DAY)); + clamp_timestamp(&mut value); + value + } + + pub(super) fn eval_timestamp_diff(date_start: i64, date_end: i64) -> i64 { + EvalDaysImpl::eval_date_diff( + (date_start / MICROSECS_PER_DAY) as i32, + (date_end / MICROSECS_PER_DAY) as i32, + ) as i64 + } + + pub(super) fn eval_timestamp_between(start: i64, end: i64, tz: &TimeZone) -> i64 { + if start == end { + return 0; + } + if start > end { + return -Self::eval_timestamp_between(end, start, tz); + } + + let start = timestamp_from_micros(start, tz); + let end = timestamp_from_micros(end, tz); + let mut full_days = (end.date() - start.date()) + .to_duration(SpanRelativeTo::days_are_24_hours()) + .unwrap() + .as_hours() + / 24; + let end_time = end.time(); + let start_time = start.time(); + if end_time < start_time { + full_days -= 1; + } + full_days + } +} + +struct EvalTimesImpl; + +impl EvalTimesImpl { + fn eval_timestamp(us: i64, delta: impl AsPrimitive, factor: i64) -> i64 { + let mut ts = us.wrapping_add(delta.as_().wrapping_mul(factor * MICROS_PER_SEC)); + clamp_timestamp(&mut ts); + ts + } + + fn eval_timestamp_diff(date_start: i64, date_end: i64, factor: i64) -> i64 { + let date_start = date_start / (MICROS_PER_SEC * factor); + let date_end = date_end / (MICROS_PER_SEC * factor); + date_end - date_start + } + + fn eval_timestamp_between(unit: &str, start: i64, end: i64) -> i64 { + if start == end { + return 0; + } + if start > end { + return -Self::eval_timestamp_between(unit, end, start); + } + + let duration = SignedDuration::from_micros(end - start); + + match unit { + "hours" => duration.as_hours(), + "minutes" => duration.as_mins(), + "seconds" => duration.as_secs(), + _ => unreachable!("Unsupported unit: {}", unit), + } + } +} + +pub(super) fn register(registry: &mut FunctionRegistry) { + register_add_functions(registry); + register_sub_functions(registry); + register_diff_functions(registry); + register_between_functions(registry); +} + +fn register_year_arith_function( + registry: &mut FunctionRegistry, + name: &'static str, + delta_sign: i64, +) { + registry.register_passthrough_nullable_2_arg::( + name, + |_, _, _| FunctionDomain::MayThrow, + vectorize_with_builder_2_arg::( + move |date, delta, builder, ctx| match EvalYearsImpl::eval_date( + date, + delta * delta_sign, + false, + ) { + Ok(t) => builder.push(t), + Err(e) => { + ctx.set_error(builder.len(), e); + builder.push(0); + } + }, + ), + ); + registry.register_passthrough_nullable_2_arg::( + name, + |_, _, _| FunctionDomain::MayThrow, + vectorize_with_builder_2_arg::( + move |ts, delta, builder, ctx| match EvalYearsImpl::eval_timestamp( + ts, + &ctx.func_ctx.tz, + delta * delta_sign, + false, + ) { + Ok(t) => builder.push(t), + Err(e) => { + ctx.set_error(builder.len(), e); + builder.push(0); + } + }, + ), + ); +} + +fn register_month_based_arith_function( + registry: &mut FunctionRegistry, + name: &'static str, + month_multiplier: i64, + keep_end_of_month: bool, +) { + registry.register_passthrough_nullable_2_arg::( + name, + |_, _, _| FunctionDomain::MayThrow, + vectorize_with_builder_2_arg::( + move |date, delta, builder, ctx| match EvalMonthsImpl::eval_date( + date, + delta * month_multiplier, + keep_end_of_month, + ) { + Ok(t) => builder.push(t), + Err(e) => { + ctx.set_error(builder.len(), e); + builder.push(0); + } + }, + ), + ); + registry.register_passthrough_nullable_2_arg::( + name, + |_, _, _| FunctionDomain::MayThrow, + vectorize_with_builder_2_arg::( + move |ts, delta, builder, ctx| match EvalMonthsImpl::eval_timestamp( + ts, + &ctx.func_ctx.tz, + delta * month_multiplier, + keep_end_of_month, + ) { + Ok(t) => builder.push(t), + Err(e) => { + ctx.set_error(builder.len(), e); + builder.push(0); + } + }, + ), + ); +} + +fn register_day_based_arith_function( + registry: &mut FunctionRegistry, + name: &'static str, + day_multiplier: i64, +) { + registry.register_2_arg::( + name, + |_, _, _| FunctionDomain::Full, + move |date, delta, _| EvalDaysImpl::eval_date(date, delta * day_multiplier), + ); + + registry.register_2_arg::( + name, + |_, _, _| FunctionDomain::Full, + move |ts, delta, _| EvalDaysImpl::eval_timestamp(ts, delta * day_multiplier), + ); +} + +fn register_time_arith_function( + registry: &mut FunctionRegistry, + name: &'static str, + delta_sign: i64, + factor: i64, +) { + registry.register_2_arg::( + name, + |_, _, _| FunctionDomain::Full, + move |date, delta, _| { + let val = (date as i64) * 24 * 3600 * MICROS_PER_SEC; + EvalTimesImpl::eval_timestamp(val, delta * delta_sign, factor) + }, + ); + + registry.register_2_arg::( + name, + |_, _, _| FunctionDomain::Full, + move |ts, delta, _| EvalTimesImpl::eval_timestamp(ts, delta * delta_sign, factor), + ); +} + +fn register_add_functions(registry: &mut FunctionRegistry) { + register_year_arith_function(registry, "add_years", 1); + register_month_based_arith_function(registry, "add_quarters", 3, false); + register_month_based_arith_function(registry, "date_add_months", 1, false); + // For both ADD_MONTHS and DATEADD, if the result month has fewer days than the original day, the result day of the month is the last day of the result month. + // For ADD_MONTHS only, if the original day is the last day of the month, the result day of month will be the last day of the result month. + register_month_based_arith_function(registry, "add_months", 1, true); + register_day_based_arith_function(registry, "add_days", 1); + register_day_based_arith_function(registry, "add_weeks", 7); + register_time_arith_function(registry, "add_hours", 1, FACTOR_HOUR); + register_time_arith_function(registry, "add_minutes", 1, FACTOR_MINUTE); + register_time_arith_function(registry, "add_seconds", 1, FACTOR_SECOND); +} + +fn register_sub_functions(registry: &mut FunctionRegistry) { + register_year_arith_function(registry, "subtract_years", -1); + register_month_based_arith_function(registry, "subtract_quarters", -3, false); + register_month_based_arith_function(registry, "date_subtract_months", -1, false); + register_month_based_arith_function(registry, "subtract_months", -1, true); + register_day_based_arith_function(registry, "subtract_days", -1); + register_day_based_arith_function(registry, "subtract_weeks", -7); + register_time_arith_function(registry, "subtract_hours", -1, FACTOR_HOUR); + register_time_arith_function(registry, "subtract_minutes", -1, FACTOR_MINUTE); + register_time_arith_function(registry, "subtract_seconds", -1, FACTOR_SECOND); +} + +fn register_diff_functions(registry: &mut FunctionRegistry) { + registry.register_passthrough_nullable_2_arg::( + "diff_years", + |_, _, _| FunctionDomain::MayThrow, + vectorize_with_builder_2_arg::( + |date_end, date_start, builder, _| { + let diff_years = EvalYearsImpl::eval_date_diff(date_start, date_end); + builder.push(diff_years as i64); + }, + ), + ); + + registry.register_passthrough_nullable_2_arg::( + "diff_years", + |_, _, _| FunctionDomain::MayThrow, + vectorize_with_builder_2_arg::( + |date_end, date_start, builder, ctx| { + let diff_years = + EvalYearsImpl::eval_timestamp_diff(date_start, date_end, &ctx.func_ctx.tz); + builder.push(diff_years); + }, + ), + ); + + registry.register_passthrough_nullable_2_arg::( + "diff_quarters", + |_, _, _| FunctionDomain::MayThrow, + vectorize_with_builder_2_arg::( + |date_end, date_start, builder, _| { + let diff_years = EvalQuartersImpl::eval_date_diff(date_start, date_end); + builder.push(diff_years as i64); + }, + ), + ); + + registry.register_passthrough_nullable_2_arg::( + "diff_quarters", + |_, _, _| FunctionDomain::MayThrow, + vectorize_with_builder_2_arg::( + |date_end, date_start, builder, ctx| { + let diff_years = + EvalQuartersImpl::eval_timestamp_diff(date_start, date_end, &ctx.func_ctx.tz); + builder.push(diff_years); + }, + ), + ); + + registry.register_passthrough_nullable_2_arg::( + "diff_months", + |_, _, _| FunctionDomain::MayThrow, + vectorize_with_builder_2_arg::( + |date_end, date_start, builder, _| { + let diff_months = EvalMonthsImpl::eval_date_diff(date_start, date_end); + builder.push(diff_months as i64); + }, + ), + ); + + registry.register_passthrough_nullable_2_arg::( + "diff_months", + |_, _, _| FunctionDomain::MayThrow, + vectorize_with_builder_2_arg::( + |date_end, date_start, builder, _| { + let diff_months = EvalMonthsImpl::eval_timestamp_diff(date_start, date_end); + builder.push(diff_months); + }, + ), + ); + + registry.register_passthrough_nullable_2_arg::( + "diff_weeks", + |_, _, _| FunctionDomain::MayThrow, + vectorize_with_builder_2_arg::( + |date_end, date_start, builder, _| { + let diff_years = EvalWeeksImpl::eval_date_diff(date_start, date_end); + builder.push(diff_years as i64); + }, + ), + ); + + registry.register_passthrough_nullable_2_arg::( + "diff_weeks", + |_, _, _| FunctionDomain::MayThrow, + vectorize_with_builder_2_arg::( + |date_end, date_start, builder, _| { + let diff_years = EvalWeeksImpl::eval_timestamp_diff(date_start, date_end); + builder.push(diff_years); + }, + ), + ); + + registry.register_passthrough_nullable_2_arg::( + "diff_days", + |_, _, _| FunctionDomain::MayThrow, + vectorize_with_builder_2_arg::( + |date_end, date_start, builder, _| { + let diff_days = EvalDaysImpl::eval_date_diff(date_start, date_end); + builder.push(diff_days as i64); + }, + ), + ); + + registry.register_passthrough_nullable_2_arg::( + "diff_days", + |_, _, _| FunctionDomain::MayThrow, + vectorize_with_builder_2_arg::( + |date_end, date_start, builder, _| { + let diff_days = EvalDaysImpl::eval_timestamp_diff(date_start, date_end); + builder.push(diff_days); + }, + ), + ); + + registry.register_passthrough_nullable_2_arg::( + "diff_hours", + |_, _, _| FunctionDomain::MayThrow, + vectorize_with_builder_2_arg::( + |date_end, date_start, builder, _| { + let diff_hours = + EvalTimesImpl::eval_timestamp_diff(date_start, date_end, FACTOR_HOUR); + builder.push(diff_hours); + }, + ), + ); + + registry.register_passthrough_nullable_2_arg::( + "diff_minutes", + |_, _, _| FunctionDomain::MayThrow, + vectorize_with_builder_2_arg::( + |date_end, date_start, builder, _| { + let diff_minutes = + EvalTimesImpl::eval_timestamp_diff(date_start, date_end, FACTOR_MINUTE); + builder.push(diff_minutes); + }, + ), + ); + + registry.register_passthrough_nullable_2_arg::( + "diff_seconds", + |_, _, _| FunctionDomain::MayThrow, + vectorize_with_builder_2_arg::( + |date_end, date_start, builder, _| { + let diff_seconds = + EvalTimesImpl::eval_timestamp_diff(date_start, date_end, FACTOR_SECOND); + builder.push(diff_seconds); + }, + ), + ); + + registry.register_passthrough_nullable_2_arg::( + "diff_microseconds", + |_, _, _| FunctionDomain::MayThrow, + vectorize_with_builder_2_arg::( + |date_end, date_start, builder, _| { + let diff_microseconds = + EvalDaysImpl::eval_date_diff(date_start, date_end) as i64 * MICROSECS_PER_DAY; + builder.push(diff_microseconds); + }, + ), + ); + + registry.register_passthrough_nullable_2_arg::( + "diff_microseconds", + |_, _, _| FunctionDomain::MayThrow, + vectorize_with_builder_2_arg::( + |date_end, date_start, builder, _| { + builder.push(date_end - date_start); + }, + ), + ); + + registry.register_passthrough_nullable_2_arg::( + "diff_yearweeks", + |_, _, _| FunctionDomain::MayThrow, + vectorize_with_builder_2_arg::( + |date_end, date_start, builder, _| { + let diff = EvalYearWeeksImpl::eval_date_diff(date_start, date_end); + builder.push(diff as i64); + }, + ), + ); + + registry.register_passthrough_nullable_2_arg::( + "diff_yearweeks", + |_, _, _| FunctionDomain::MayThrow, + vectorize_with_builder_2_arg::( + |date_end, date_start, builder, ctx| { + let diff = + EvalYearWeeksImpl::eval_timestamp_diff(date_start, date_end, &ctx.func_ctx.tz); + builder.push(diff); + }, + ), + ); + + registry.register_passthrough_nullable_2_arg::( + "diff_isoyears", + |_, _, _| FunctionDomain::MayThrow, + vectorize_with_builder_2_arg::( + |date_end, date_start, builder, _| { + let diff = EvalISOYearsImpl::eval_date_diff(date_start, date_end); + builder.push(diff as i64); + }, + ), + ); + + registry.register_passthrough_nullable_2_arg::( + "diff_isoyears", + |_, _, _| FunctionDomain::MayThrow, + vectorize_with_builder_2_arg::( + |date_end, date_start, builder, ctx| { + let diff = + EvalISOYearsImpl::eval_timestamp_diff(date_start, date_end, &ctx.func_ctx.tz); + builder.push(diff); + }, + ), + ); + + registry.register_passthrough_nullable_2_arg::( + "diff_millenniums", + |_, _, _| FunctionDomain::MayThrow, + vectorize_with_builder_2_arg::( + |date_end, date_start, builder, _| { + let diff_years = EvalYearsImpl::eval_date_diff(date_start, date_end); + builder.push((diff_years / 1000) as i64); + }, + ), + ); + + registry.register_passthrough_nullable_2_arg::( + "diff_millenniums", + |_, _, _| FunctionDomain::MayThrow, + vectorize_with_builder_2_arg::( + |date_end, date_start, builder, ctx| { + let diff_years = + EvalYearsImpl::eval_timestamp_diff(date_start, date_end, &ctx.func_ctx.tz); + + builder.push(diff_years / 1000); + }, + ), + ); + registry.register_aliases("diff_seconds", &["diff_epochs"]); + registry.register_aliases("diff_days", &["diff_dows", "diff_isodows", "diff_doys"]); + + registry.register_2_arg::( + "minus", + |_, lhs, rhs| { + (|| { + let lm = lhs.max; + let ln = lhs.min; + let rm: i32 = num_traits::cast::cast(rhs.max)?; + let rn: i32 = num_traits::cast::cast(rhs.min)?; + + Some(FunctionDomain::Domain(SimpleDomain:: { + min: ln.checked_sub(rm)?, + max: lm.checked_sub(rn)?, + })) + })() + .unwrap_or(FunctionDomain::Full) + }, + |a, b, _| a - b, + ); + + registry.register_2_arg::( + "timestamp_diff", + |_, _, _| FunctionDomain::MayThrow, + |a, b, _| months_days_micros::new(0, 0, a - b), + ); + + registry.register_2_arg::( + "minus", + |_, lhs, rhs| { + (|| { + let lm = lhs.max; + let ln = lhs.min; + let rm = rhs.max; + let rn = rhs.min; + + Some(FunctionDomain::Domain(SimpleDomain:: { + min: ln.checked_sub(rm)?, + max: lm.checked_sub(rn)?, + })) + })() + .unwrap_or(FunctionDomain::Full) + }, + |a, b, _| a - b, + ); + + registry.register_passthrough_nullable_2_arg::( + "months_between", + |_, lhs, rhs| { + let lm = lhs.max; + let ln = lhs.min; + let rm = rhs.max; + let rn = rhs.min; + + let min = EvalMonthsImpl::months_between(ln, rm); + let max = EvalMonthsImpl::months_between(lm, rn); + FunctionDomain::Domain(SimpleDomain:: { + min: min.into(), + max: max.into(), + }) + }, + vectorize_2_arg::(|a, b, _ctx| { + EvalMonthsImpl::months_between(a, b).into() + }), + ); + + registry + .register_passthrough_nullable_2_arg::( + "months_between", + |_, lhs, rhs| { + let lm = lhs.max; + let ln = lhs.min; + let rm = rhs.max; + let rn = rhs.min; + + FunctionDomain::Domain(SimpleDomain:: { + min: EvalMonthsImpl::months_between_ts(ln, rm).into(), + max: EvalMonthsImpl::months_between_ts(lm, rn).into(), + }) + }, + vectorize_2_arg::(|a, b, _ctx| { + EvalMonthsImpl::months_between_ts(a, b).into() + }), + ); +} + +fn register_between_functions(registry: &mut FunctionRegistry) { + registry.register_passthrough_nullable_2_arg::( + "between_years", + |_, _, _| FunctionDomain::MayThrow, + vectorize_with_builder_2_arg::( + |date_end, date_start, builder, _| { + let between_years = EvalYearsImpl::eval_date_between(date_start, date_end); + builder.push(between_years as i64); + }, + ), + ); + + registry.register_passthrough_nullable_2_arg::( + "between_years", + |_, _, _| FunctionDomain::MayThrow, + vectorize_with_builder_2_arg::( + |date_end, date_start, builder, ctx| { + let between_years = + EvalYearsImpl::eval_timestamp_between(date_start, date_end, &ctx.func_ctx.tz); + builder.push(between_years); + }, + ), + ); + + registry.register_passthrough_nullable_2_arg::( + "between_quarters", + |_, _, _| FunctionDomain::MayThrow, + vectorize_with_builder_2_arg::( + |date_end, date_start, builder, _| { + let between_quarters = EvalMonthsImpl::eval_date_between(date_start, date_end) / 3; + builder.push(between_quarters as i64); + }, + ), + ); + + registry.register_passthrough_nullable_2_arg::( + "between_quarters", + |_, _, _| FunctionDomain::MayThrow, + vectorize_with_builder_2_arg::( + |date_end, date_start, builder, ctx| { + let between_quarters = + EvalMonthsImpl::eval_timestamp_between(date_start, date_end, &ctx.func_ctx.tz) + / 3; + builder.push(between_quarters); + }, + ), + ); + + registry.register_passthrough_nullable_2_arg::( + "between_months", + |_, _, _| FunctionDomain::MayThrow, + vectorize_with_builder_2_arg::( + |date_end, date_start, builder, _| { + let between_months = EvalMonthsImpl::eval_date_between(date_start, date_end); + builder.push(between_months as i64); + }, + ), + ); + + registry.register_passthrough_nullable_2_arg::( + "between_months", + |_, _, _| FunctionDomain::MayThrow, + vectorize_with_builder_2_arg::( + |date_end, date_start, builder, ctx| { + let between_months = + EvalMonthsImpl::eval_timestamp_between(date_start, date_end, &ctx.func_ctx.tz); + builder.push(between_months); + }, + ), + ); + + registry.register_passthrough_nullable_2_arg::( + "between_weeks", + |_, _, _| FunctionDomain::MayThrow, + vectorize_with_builder_2_arg::( + |date_end, date_start, builder, _| { + let between_weeks = EvalWeeksImpl::eval_date_between(date_start, date_end); + builder.push(between_weeks as i64); + }, + ), + ); + + registry.register_passthrough_nullable_2_arg::( + "between_weeks", + |_, _, _| FunctionDomain::MayThrow, + vectorize_with_builder_2_arg::( + |date_end, date_start, builder, ctx| { + let between_weeks = + EvalWeeksImpl::eval_timestamp_between(date_start, date_end, &ctx.func_ctx.tz); + builder.push(between_weeks); + }, + ), + ); + + registry.register_passthrough_nullable_2_arg::( + "between_days", + |_, _, _| FunctionDomain::MayThrow, + vectorize_with_builder_2_arg::( + |date_end, date_start, builder, _| { + // day is date type unit + let between_days = EvalDaysImpl::eval_date_diff(date_start, date_end); + builder.push(between_days as i64); + }, + ), + ); + + registry.register_passthrough_nullable_2_arg::( + "between_days", + |_, _, _| FunctionDomain::MayThrow, + vectorize_with_builder_2_arg::( + |date_end, date_start, builder, ctx| { + let between_days = + EvalDaysImpl::eval_timestamp_between(date_start, date_end, &ctx.func_ctx.tz); + builder.push(between_days); + }, + ), + ); + + registry.register_passthrough_nullable_2_arg::( + "between_hours", + |_, _, _| FunctionDomain::MayThrow, + vectorize_with_builder_2_arg::( + |date_end, date_start, builder, _| { + let between_hours = EvalDaysImpl::eval_date_diff(date_start, date_end) as i64 * 24; + builder.push(between_hours); + }, + ), + ); + + registry.register_passthrough_nullable_2_arg::( + "between_hours", + |_, _, _| FunctionDomain::MayThrow, + vectorize_with_builder_2_arg::( + |date_end, date_start, builder, _| { + let between_hours = + EvalTimesImpl::eval_timestamp_between("hours", date_start, date_end); + builder.push(between_hours); + }, + ), + ); + + registry.register_passthrough_nullable_2_arg::( + "between_minutes", + |_, _, _| FunctionDomain::MayThrow, + vectorize_with_builder_2_arg::( + |date_end, date_start, builder, _| { + let between_minutes = + EvalDaysImpl::eval_date_diff(date_start, date_end) as i64 * 24 * 60; + builder.push(between_minutes); + }, + ), + ); + + registry.register_passthrough_nullable_2_arg::( + "between_minutes", + |_, _, _| FunctionDomain::MayThrow, + vectorize_with_builder_2_arg::( + |date_end, date_start, builder, _| { + let between_minutes = + EvalTimesImpl::eval_timestamp_between("minutes", date_start, date_end); + builder.push(between_minutes); + }, + ), + ); + + registry.register_passthrough_nullable_2_arg::( + "between_seconds", + |_, _, _| FunctionDomain::MayThrow, + vectorize_with_builder_2_arg::( + |date_end, date_start, builder, _| { + let between_seconds = + EvalDaysImpl::eval_date_diff(date_start, date_end) as i64 * 24 * 3600; + builder.push(between_seconds); + }, + ), + ); + + registry.register_passthrough_nullable_2_arg::( + "between_seconds", + |_, _, _| FunctionDomain::MayThrow, + vectorize_with_builder_2_arg::( + |date_end, date_start, builder, _| { + let between_seconds = + EvalTimesImpl::eval_timestamp_between("seconds", date_start, date_end); + builder.push(between_seconds); + }, + ), + ); + + registry.register_passthrough_nullable_2_arg::( + "between_microseconds", + |_, _, _| FunctionDomain::MayThrow, + vectorize_with_builder_2_arg::( + |date_end, date_start, builder, _| { + let between_microseconds = + EvalDaysImpl::eval_date_diff(date_start, date_end) as i64 * MICROSECS_PER_DAY; + builder.push(between_microseconds); + }, + ), + ); + + registry.register_passthrough_nullable_2_arg::( + "between_microseconds", + |_, _, _| FunctionDomain::MayThrow, + vectorize_with_builder_2_arg::( + |date_end, date_start, builder, _| { + builder.push(date_end - date_start); + }, + ), + ); + + registry.register_passthrough_nullable_2_arg::( + "between_isoyears", + |_, _, _| FunctionDomain::MayThrow, + vectorize_with_builder_2_arg::( + |date_end, date_start, builder, _| { + let between_isoyears = EvalISOYearsImpl::eval_date_between(date_start, date_end); + builder.push(between_isoyears as i64); + }, + ), + ); + + registry.register_passthrough_nullable_2_arg::( + "between_isoyears", + |_, _, _| FunctionDomain::MayThrow, + vectorize_with_builder_2_arg::( + |date_end, date_start, builder, ctx| { + let between_isoyears = EvalISOYearsImpl::eval_timestamp_between( + date_start, + date_end, + &ctx.func_ctx.tz, + ); + builder.push(between_isoyears); + }, + ), + ); + + registry.register_passthrough_nullable_2_arg::( + "between_millenniums", + |_, _, _| FunctionDomain::MayThrow, + vectorize_with_builder_2_arg::( + |date_end, date_start, builder, _| { + let between_millenniums = EvalYearsImpl::eval_date_between(date_start, date_end); + builder.push((between_millenniums / 1000) as i64); + }, + ), + ); + + registry.register_passthrough_nullable_2_arg::( + "between_millenniums", + |_, _, _| FunctionDomain::MayThrow, + vectorize_with_builder_2_arg::( + |date_end, date_start, builder, ctx| { + let between_millenniums = + EvalYearsImpl::eval_timestamp_between(date_start, date_end, &ctx.func_ctx.tz); + + builder.push(between_millenniums / 1000); + }, + ), + ); + registry.register_aliases("between_seconds", &["between_epochs"]); + registry.register_aliases("between_weeks", &["between_yearweeks"]); + registry.register_aliases("between_days", &[ + "between_dows", + "between_isodows", + "between_doys", + ]); +} + +// Compute a correct date domain from a raw arithmetic range. +// `clamp_date` maps out-of-range values to DATE_MIN (non-monotonic), so naively +// clamping endpoints can produce a reversed domain. This function accounts for +// partial overlap with the valid date range. +fn clamp_date_domain(raw_min: i64, raw_max: i64) -> SimpleDomain { + if raw_min >= DATE_MIN as i64 && raw_max <= DATE_MAX as i64 { + SimpleDomain { + min: raw_min as i32, + max: raw_max as i32, + } + } else if raw_min > DATE_MAX as i64 || raw_max < DATE_MIN as i64 { + SimpleDomain { + min: DATE_MIN, + max: DATE_MIN, + } + } else { + SimpleDomain { + min: DATE_MIN, + max: raw_max.min(DATE_MAX as i64) as i32, + } + } +} + +fn clamp_timestamp_domain(raw_min: i64, raw_max: i64) -> SimpleDomain { + if raw_min >= TIMESTAMP_MIN && raw_max <= TIMESTAMP_MAX { + SimpleDomain { + min: raw_min, + max: raw_max, + } + } else if raw_min > TIMESTAMP_MAX || raw_max < TIMESTAMP_MIN { + SimpleDomain { + min: TIMESTAMP_MIN, + max: TIMESTAMP_MIN, + } + } else { + SimpleDomain { + min: TIMESTAMP_MIN, + max: raw_max.min(TIMESTAMP_MAX), + } + } +} + +pub(super) fn register_timestamp_add_sub(registry: &mut FunctionRegistry) { + registry.register_passthrough_nullable_2_arg::( + "plus", + |_, lhs, rhs| { + (|| { + let lm: i64 = num_traits::cast::cast(lhs.max)?; + let ln: i64 = num_traits::cast::cast(lhs.min)?; + let rm = rhs.max; + let rn = rhs.min; + + let raw_min = ln.saturating_add(rn); + let raw_max = lm.saturating_add(rm); + Some(FunctionDomain::Domain(clamp_date_domain(raw_min, raw_max))) + })() + .unwrap_or(FunctionDomain::MayThrow) + }, + vectorize_with_builder_2_arg::(|a, b, output, _| { + output.push(clamp_date((a as i64).saturating_add(b))) + }), + ); + + registry.register_passthrough_nullable_2_arg::( + "plus", + |_, lhs, rhs| { + { + let lm = lhs.max; + let ln = lhs.min; + let rm = rhs.max; + let rn = rhs.min; + let raw_min = ln.saturating_add(rn); + let raw_max = lm.saturating_add(rm); + Some(FunctionDomain::Domain(clamp_timestamp_domain( + raw_min, raw_max, + ))) + } + .unwrap_or(FunctionDomain::MayThrow) + }, + vectorize_with_builder_2_arg::( + |a, b, output, _| { + let mut sum = a.saturating_add(b); + clamp_timestamp(&mut sum); + output.push(sum); + }, + ), + ); + + registry.register_passthrough_nullable_2_arg::( + "minus", + |_, lhs, rhs| { + (|| { + let lm: i64 = num_traits::cast::cast(lhs.max)?; + let ln: i64 = num_traits::cast::cast(lhs.min)?; + let rm = rhs.max; + let rn = rhs.min; + + let raw_min = ln.saturating_sub(rm); + let raw_max = lm.saturating_sub(rn); + Some(FunctionDomain::Domain(clamp_date_domain(raw_min, raw_max))) + })() + .unwrap_or(FunctionDomain::MayThrow) + }, + vectorize_with_builder_2_arg::(|a, b, output, _| { + output.push(clamp_date((a as i64).saturating_sub(b))); + }), + ); + + registry.register_passthrough_nullable_2_arg::( + "minus", + |_, lhs, rhs| { + { + let lm = lhs.max; + let ln = lhs.min; + let rm = rhs.max; + let rn = rhs.min; + let raw_min = ln.saturating_sub(rm); + let raw_max = lm.saturating_sub(rn); + Some(FunctionDomain::Domain(clamp_timestamp_domain( + raw_min, raw_max, + ))) + } + .unwrap_or(FunctionDomain::MayThrow) + }, + vectorize_with_builder_2_arg::( + |a, b, output, _| { + let mut minus = a.saturating_sub(b); + clamp_timestamp(&mut minus); + output.push(minus); + }, + ), + ); +} diff --git a/src/query/functions/src/scalars/timestamp/src/date_conversion.rs b/src/query/functions/src/scalars/timestamp/src/date_conversion.rs new file mode 100644 index 0000000000000..3d1ae0eea9e1c --- /dev/null +++ b/src/query/functions/src/scalars/timestamp/src/date_conversion.rs @@ -0,0 +1,215 @@ +// Copyright 2021 Datafuse Labs +// +// Licensed 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. + +use databend_common_expression::FunctionContext; +use databend_common_expression::FunctionDomain; +use databend_common_expression::FunctionProperty; +use databend_common_expression::FunctionRegistry; +use databend_common_expression::Value; +use databend_common_expression::types::DataType; +use databend_common_expression::types::DateType; +use databend_common_expression::types::StringType; +use databend_common_expression::types::TimestampType; +use databend_common_expression::types::date::date_from_days; +use databend_common_expression::types::number::Int64Type; +use databend_common_expression::types::timestamp::MICROS_PER_SEC; +use databend_common_expression::vectorize_with_builder_1_arg; +use databend_common_timezone::fast_utc_from_local; +use jiff::SignedDuration; +use jiff::Unit; +use jiff::Zoned; +use jiff::civil::Date; +use jiff::civil::Time; +use jiff::civil::date; +use jiff::tz::TimeZone; + +#[inline] +pub(super) fn today_date(now: &Zoned, tz: &TimeZone) -> i32 { + let now = now.with_time_zone(tz.clone()); + now.date() + .since((Unit::Day, Date::new(1970, 1, 1).unwrap())) + .unwrap() + .get_days() +} + +// Summer Time in 1990 began at 2 a.m. (Beijing time) on Sunday, April 15th and ended at 2 a.m. (Beijing Daylight Saving Time) on Sunday, September 16th. +// During this period, the summer working hours will be implemented, namely from April 15th to September 16th. +// The working hours of all departments of The State Council are from 8 a.m. to 12 p.m. and from 1:30 p.m. to 5:30 p.m. The winter working hours will be implemented after September 17th. +pub fn calc_date_to_timestamp(val: i32, tz: &TimeZone) -> std::result::Result { + let ts = (val as i64) * 24 * 3600 * MICROS_PER_SEC; + let local_date = date_from_days(val); + let year = i32::from(local_date.year()); + let month = local_date.month() as u8; + let day = local_date.day() as u8; + + if let Some(micros) = fast_utc_from_local(tz, year, month, day, 0, 0, 0, 0) { + return Ok(micros); + } + + let midnight = local_date.to_datetime(Time::midnight()); + match midnight.to_zoned(tz.clone()) { + Ok(zoned) => Ok(zoned.timestamp().as_microsecond()), + Err(_err) => { + for minutes in 1..=1440 { + let delta = SignedDuration::from_secs((minutes * 60) as i64); + if let Ok(adj) = midnight.checked_add(delta) { + if let Ok(zoned) = adj.to_zoned(tz.clone()) { + return Ok(zoned.timestamp().as_microsecond()); + } + } else { + break; + } + } + + // The timezone database might not have explicit rules for extremely + // old/new dates, so fall back to the legacy behavior that applies the + // canonical offset we use for 1970-01-01. + let tz_offset_micros = tz + .to_timestamp(date(1970, 1, 1).at(0, 0, 0, 0)) + .unwrap() + .as_microsecond(); + Ok(ts + tz_offset_micros) + } + } +} + +fn normalize_time_precision(raw: i64) -> Result { + if (0..=9).contains(&raw) { + Ok(raw as u8) + } else { + Err(format!( + "Invalid fractional seconds precision `{raw}` for `current_time` (expect 0-9)" + )) + } +} + +fn current_time_string(func_ctx: &FunctionContext, precision: Option) -> String { + let datetime = func_ctx.now.with_time_zone(func_ctx.tz.clone()).datetime(); + let nanos = datetime.subsec_nanosecond() as u32; + let mut value = format!( + "{:02}:{:02}:{:02}", + datetime.hour(), + datetime.minute(), + datetime.second() + ); + + let precision = precision.unwrap_or(9).min(9); + if precision > 0 { + let divisor = 10_u32.pow(9 - precision as u32); + let truncated = nanos / divisor; + let frac = format!("{:0width$}", truncated, width = precision as usize); + value.push('.'); + value.push_str(&frac); + } + + value +} + +pub(super) fn register_real_time_functions(registry: &mut FunctionRegistry) { + registry.register_aliases("now", &["current_timestamp"]); + registry.register_aliases("today", &["current_date"]); + + registry.properties.insert( + "now".to_string(), + FunctionProperty::default().non_deterministic(), + ); + registry.properties.insert( + "current_time".to_string(), + FunctionProperty::default().non_deterministic(), + ); + registry.properties.insert( + "today".to_string(), + FunctionProperty::default().non_deterministic(), + ); + registry.properties.insert( + "yesterday".to_string(), + FunctionProperty::default().non_deterministic(), + ); + registry.properties.insert( + "tomorrow".to_string(), + FunctionProperty::default().non_deterministic(), + ); + + for name in &[ + "to_timestamp", + "to_timestamp_tz", + "to_date", + "to_yyyymm", + "to_yyyymmdd", + ] { + registry + .properties + .insert(name.to_string(), FunctionProperty::default().monotonicity()); + } + + registry.properties.insert( + "to_string".to_string(), + FunctionProperty::default() + .monotonicity_type(DataType::Timestamp) + .monotonicity_type(DataType::Timestamp.wrap_nullable()), + ); + + registry.properties.insert( + "to_string".to_string(), + FunctionProperty::default() + .monotonicity_type(DataType::Date) + .monotonicity_type(DataType::Date.wrap_nullable()), + ); + + registry.register_0_arg_core::( + "now", + |_| FunctionDomain::Full, + |ctx| Value::Scalar(ctx.func_ctx.now.timestamp().as_microsecond()), + ); + + registry.register_0_arg_core::( + "current_time", + |_| FunctionDomain::Full, + |ctx| Value::Scalar(current_time_string(ctx.func_ctx, None)), + ); + + registry.register_passthrough_nullable_1_arg::( + "current_time", + |_, _| FunctionDomain::MayThrow, + vectorize_with_builder_1_arg::(|precision, output, ctx| { + match normalize_time_precision(precision) { + Ok(valid_precision) => { + output.put_and_commit(current_time_string(ctx.func_ctx, Some(valid_precision))); + } + Err(err) => { + ctx.set_error(output.len(), err); + output.commit_row(); + } + } + }), + ); + + registry.register_0_arg_core::( + "today", + |_| FunctionDomain::Full, + |ctx| Value::Scalar(today_date(&ctx.func_ctx.now, &ctx.func_ctx.tz)), + ); + + registry.register_0_arg_core::( + "yesterday", + |_| FunctionDomain::Full, + |ctx| Value::Scalar(today_date(&ctx.func_ctx.now, &ctx.func_ctx.tz) - 1), + ); + + registry.register_0_arg_core::( + "tomorrow", + |_| FunctionDomain::Full, + |ctx| Value::Scalar(today_date(&ctx.func_ctx.now, &ctx.func_ctx.tz) + 1), + ); +} diff --git a/src/query/functions/src/scalars/timestamp/src/date_extract.rs b/src/query/functions/src/scalars/timestamp/src/date_extract.rs new file mode 100644 index 0000000000000..d2a5367522845 --- /dev/null +++ b/src/query/functions/src/scalars/timestamp/src/date_extract.rs @@ -0,0 +1,615 @@ +// Copyright 2021 Datafuse Labs +// +// Licensed 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. + +use databend_common_expression::FunctionDomain; +use databend_common_expression::FunctionRegistry; +use databend_common_expression::Value; +use databend_common_expression::types::Bitmap; +use databend_common_expression::types::DateType; +use databend_common_expression::types::F64; +use databend_common_expression::types::Float64Type; +use databend_common_expression::types::NumberType; +use databend_common_expression::types::TimestampType; +use databend_common_expression::types::date::date_from_days; +use databend_common_expression::types::nullable::NullableColumn; +use databend_common_expression::types::nullable::NullableDomain; +use databend_common_expression::types::number::Int64Type; +use databend_common_expression::types::number::SimpleDomain; +use databend_common_expression::types::number::UInt8Type; +use databend_common_expression::types::number::UInt16Type; +use databend_common_expression::types::number::UInt32Type; +use databend_common_expression::types::number::UInt64Type; +use databend_common_expression::types::timestamp::MICROS_PER_SEC; +use databend_common_expression::types::timestamp::timestamp_from_micros; +use databend_common_expression::vectorize_1_arg; +use databend_common_timezone::DateTimeComponents; +use databend_common_timezone::fast_components_from_timestamp; +use jiff::Zoned; +use jiff::civil::Date; +use jiff::tz::TimeZone; + +pub(super) trait ToNumber { + type Output; + + fn to_number(dt: &Zoned) -> Self::Output; + + fn from_components(components: &DateTimeComponents) -> Self::Output; +} + +trait DateToNumber: ToNumber { + fn to_number_from_date(date: &Date) -> Self::Output; +} + +struct ToNumberImpl; + +impl ToNumberImpl { + fn eval_timestamp(us: i64, tz: &TimeZone) -> T::Output { + if let Some(components) = fast_components_from_timestamp(us, tz) { + return T::from_components(&components); + } + let dt = timestamp_from_micros(us, tz); + T::to_number(&dt) + } + + fn eval_date(days: i32) -> T::Output { + T::to_number_from_date(&date_from_days(days as i64)) + } +} + +struct ToYYYYMM; +struct ToYYYYWW; +struct ToYYYYMMDD; +struct ToYYYYMMDDHH; +struct ToYYYYMMDDHHMMSS; +struct ToYear; +struct ToMillennium; +struct ToISOYear; +pub(super) struct ToQuarter; +struct ToMonth; +struct ToDayOfYear; +struct ToDayOfMonth; +struct ToDayOfWeek; +struct DayOfWeek; + +struct ToWeekOfYear; + +impl ToNumber for ToYYYYMM { + type Output = u32; + + fn to_number(dt: &Zoned) -> Self::Output { + dt.year() as u32 * 100 + dt.month() as u32 + } + + fn from_components(components: &DateTimeComponents) -> Self::Output { + components.year as u32 * 100 + components.month as u32 + } +} + +impl DateToNumber for ToYYYYMM { + fn to_number_from_date(date: &Date) -> Self::Output { + date.year() as u32 * 100 + date.month() as u32 + } +} + +impl ToNumber for ToMillennium { + type Output = u16; + + fn to_number(dt: &Zoned) -> Self::Output { + (dt.year() as u16).div_ceil(1000) + } + + fn from_components(components: &DateTimeComponents) -> Self::Output { + (components.year as u16).div_ceil(1000) + } +} + +impl DateToNumber for ToMillennium { + fn to_number_from_date(date: &Date) -> Self::Output { + (date.year() as u16).div_ceil(1000) + } +} + +impl ToNumber for ToWeekOfYear { + type Output = u32; + + fn to_number(dt: &Zoned) -> Self::Output { + dt.date().iso_week_date().week() as u32 + } + + fn from_components(components: &DateTimeComponents) -> Self::Output { + components.iso_year_week().1 + } +} + +impl DateToNumber for ToWeekOfYear { + fn to_number_from_date(date: &Date) -> Self::Output { + date.iso_week_date().week() as u32 + } +} + +impl ToNumber for ToYYYYMMDD { + type Output = u32; + + fn to_number(dt: &Zoned) -> Self::Output { + dt.year() as u32 * 10_000 + dt.month() as u32 * 100 + dt.day() as u32 + } + + fn from_components(components: &DateTimeComponents) -> Self::Output { + components.year as u32 * 10_000 + components.month as u32 * 100 + components.day as u32 + } +} + +impl DateToNumber for ToYYYYMMDD { + fn to_number_from_date(date: &Date) -> Self::Output { + date.year() as u32 * 10_000 + date.month() as u32 * 100 + date.day() as u32 + } +} + +impl ToNumber for ToYYYYMMDDHH { + type Output = u64; + + fn to_number(dt: &Zoned) -> Self::Output { + dt.year() as u64 * 1_000_000 + + dt.month() as u64 * 10_000 + + dt.day() as u64 * 100 + + dt.hour() as u64 + } + + fn from_components(components: &DateTimeComponents) -> Self::Output { + components.year as u64 * 1_000_000 + + components.month as u64 * 10_000 + + components.day as u64 * 100 + + components.hour as u64 + } +} + +impl ToNumber for ToYYYYMMDDHHMMSS { + type Output = u64; + + fn to_number(dt: &Zoned) -> Self::Output { + dt.year() as u64 * 10_000_000_000 + + dt.month() as u64 * 100_000_000 + + dt.day() as u64 * 1_000_000 + + dt.hour() as u64 * 10_000 + + dt.minute() as u64 * 100 + + dt.second() as u64 + } + + fn from_components(components: &DateTimeComponents) -> Self::Output { + components.year as u64 * 10_000_000_000 + + components.month as u64 * 100_000_000 + + components.day as u64 * 1_000_000 + + components.hour as u64 * 10_000 + + components.minute as u64 * 100 + + components.second as u64 + } +} + +impl ToNumber for ToYear { + type Output = u16; + + fn to_number(dt: &Zoned) -> Self::Output { + dt.year() as u16 + } + + fn from_components(components: &DateTimeComponents) -> Self::Output { + components.year as u16 + } +} + +impl DateToNumber for ToYear { + fn to_number_from_date(date: &Date) -> Self::Output { + date.year() as u16 + } +} + +impl ToNumber for ToISOYear { + type Output = u16; + + fn to_number(dt: &Zoned) -> Self::Output { + dt.date().iso_week_date().year() as _ + } + + fn from_components(components: &DateTimeComponents) -> Self::Output { + components.iso_year_week().0 as u16 + } +} + +impl DateToNumber for ToISOYear { + fn to_number_from_date(date: &Date) -> Self::Output { + date.iso_week_date().year() as u16 + } +} + +impl ToNumber for ToYYYYWW { + type Output = u32; + + fn to_number(dt: &Zoned) -> Self::Output { + let week_date = dt.date().iso_week_date(); + let year = week_date.year() as u32 * 100; + year + dt.date().iso_week_date().week() as u32 + } + + fn from_components(components: &DateTimeComponents) -> Self::Output { + let (iso_year, iso_week) = components.iso_year_week(); + iso_year as u32 * 100 + iso_week + } +} + +impl DateToNumber for ToYYYYWW { + fn to_number_from_date(date: &Date) -> Self::Output { + let week_date = date.iso_week_date(); + week_date.year() as u32 * 100 + week_date.week() as u32 + } +} + +impl ToNumber for ToQuarter { + type Output = u8; + + fn to_number(dt: &Zoned) -> Self::Output { + // begin with 0 + ((dt.month() - 1) / 3 + 1) as u8 + } + + fn from_components(components: &DateTimeComponents) -> Self::Output { + (components.month - 1) / 3 + 1 + } +} + +impl DateToNumber for ToQuarter { + fn to_number_from_date(date: &Date) -> Self::Output { + (date.month() as u8 - 1) / 3 + 1 + } +} + +impl ToNumber for ToMonth { + type Output = u8; + + fn to_number(dt: &Zoned) -> Self::Output { + dt.month() as u8 + } + + fn from_components(components: &DateTimeComponents) -> Self::Output { + components.month + } +} + +impl DateToNumber for ToMonth { + fn to_number_from_date(date: &Date) -> Self::Output { + date.month() as u8 + } +} + +impl ToNumber for ToDayOfYear { + type Output = u16; + + fn to_number(dt: &Zoned) -> Self::Output { + dt.day_of_year() as u16 + } + + fn from_components(components: &DateTimeComponents) -> Self::Output { + components.day_of_year + } +} + +impl DateToNumber for ToDayOfYear { + fn to_number_from_date(date: &Date) -> Self::Output { + date.day_of_year() as u16 + } +} + +impl ToNumber for ToDayOfMonth { + type Output = u8; + + fn to_number(dt: &Zoned) -> Self::Output { + dt.day() as u8 + } + + fn from_components(components: &DateTimeComponents) -> Self::Output { + components.day + } +} + +impl DateToNumber for ToDayOfMonth { + fn to_number_from_date(date: &Date) -> Self::Output { + date.day() as u8 + } +} + +impl ToNumber for ToDayOfWeek { + type Output = u8; + + fn to_number(dt: &Zoned) -> Self::Output { + dt.weekday().to_monday_one_offset() as u8 + } + + fn from_components(components: &DateTimeComponents) -> Self::Output { + components.weekday.to_monday_one_offset() as u8 + } +} + +impl DateToNumber for ToDayOfWeek { + fn to_number_from_date(date: &Date) -> Self::Output { + date.weekday().to_monday_one_offset() as u8 + } +} + +impl ToNumber for DayOfWeek { + type Output = u8; + + fn to_number(dt: &Zoned) -> Self::Output { + dt.weekday().to_sunday_zero_offset() as u8 + } + + fn from_components(components: &DateTimeComponents) -> Self::Output { + components.weekday.to_sunday_zero_offset() as u8 + } +} + +impl DateToNumber for DayOfWeek { + fn to_number_from_date(date: &Date) -> Self::Output { + date.weekday().to_sunday_zero_offset() as u8 + } +} + +pub(super) fn register_cast(registry: &mut FunctionRegistry) { + registry.register_1_arg::, _>( + "to_int64", + |_, domain| FunctionDomain::Domain(domain.overflow_cast().0), + |val, _| val as i64, + ); + + registry.register_passthrough_nullable_1_arg::, _>( + "to_int64", + |_, domain| FunctionDomain::Domain(*domain), + |val, _| match val { + Value::Scalar(scalar) => Value::Scalar(scalar), + Value::Column(col) => Value::Column(col), + }, + ); + + registry.register_combine_nullable_1_arg::, _, _>( + "try_to_int64", + |_, domain| { + FunctionDomain::Domain(NullableDomain { + has_null: false, + value: Some(Box::new(domain.overflow_cast().0)), + }) + }, + |val, _| match val { + Value::Scalar(scalar) => Value::Scalar(Some(scalar as i64)), + Value::Column(col) => Value::Column(NullableColumn::new_unchecked( + col.iter().map(|val| *val as i64).collect(), + Bitmap::new_constant(true, col.len()), + )), + }, + ); + + registry.register_combine_nullable_1_arg::, _, _>( + "try_to_int64", + |_, domain| { + FunctionDomain::Domain(NullableDomain { + has_null: false, + value: Some(Box::new(*domain)), + }) + }, + |val, _| match val { + Value::Scalar(scalar) => Value::Scalar(Some(scalar)), + Value::Column(col) => { + let validity = Bitmap::new_constant(true, col.len()); + Value::Column(NullableColumn::new_unchecked(col, validity)) + } + }, + ); +} + +pub(super) fn register(registry: &mut FunctionRegistry) { + // date + registry.register_passthrough_nullable_1_arg::( + "to_yyyymm", + |_, _| FunctionDomain::Full, + vectorize_1_arg::(|val, _| ToNumberImpl::eval_date::(val)), + ); + registry.register_passthrough_nullable_1_arg::( + "to_yyyymmdd", + |_, _| FunctionDomain::Full, + vectorize_1_arg::(|val, _| { + ToNumberImpl::eval_date::(val) + }), + ); + registry.register_passthrough_nullable_1_arg::( + "to_year", + |_, _| FunctionDomain::Full, + vectorize_1_arg::(|val, _| ToNumberImpl::eval_date::(val)), + ); + + registry.register_passthrough_nullable_1_arg::( + "to_iso_year", + |_, _| FunctionDomain::Full, + vectorize_1_arg::(|val, _| ToNumberImpl::eval_date::(val)), + ); + + registry.register_passthrough_nullable_1_arg::( + "to_quarter", + |_, _| FunctionDomain::Full, + vectorize_1_arg::(|val, _| ToNumberImpl::eval_date::(val)), + ); + registry.register_passthrough_nullable_1_arg::( + "to_month", + |_, _| FunctionDomain::Full, + vectorize_1_arg::(|val, _| ToNumberImpl::eval_date::(val)), + ); + registry.register_passthrough_nullable_1_arg::( + "to_day_of_year", + |_, _| FunctionDomain::Full, + vectorize_1_arg::(|val, _| { + ToNumberImpl::eval_date::(val) + }), + ); + registry.register_passthrough_nullable_1_arg::( + "to_day_of_month", + |_, _| FunctionDomain::Full, + vectorize_1_arg::(|val, _| { + ToNumberImpl::eval_date::(val) + }), + ); + registry.register_passthrough_nullable_1_arg::( + "to_day_of_week", + |_, _| FunctionDomain::Full, + vectorize_1_arg::(|val, _| { + ToNumberImpl::eval_date::(val) + }), + ); + registry.register_passthrough_nullable_1_arg::( + "dayofweek", + |_, _| FunctionDomain::Full, + vectorize_1_arg::(|val, _| ToNumberImpl::eval_date::(val)), + ); + registry.register_passthrough_nullable_1_arg::( + "yearweek", + |_, _| FunctionDomain::Full, + vectorize_1_arg::(|val, _| ToNumberImpl::eval_date::(val)), + ); + registry.register_passthrough_nullable_1_arg::( + "millennium", + |_, _| FunctionDomain::Full, + vectorize_1_arg::(|val, _| { + ToNumberImpl::eval_date::(val) + }), + ); + registry.register_passthrough_nullable_1_arg::( + "to_week_of_year", + |_, _| FunctionDomain::Full, + vectorize_1_arg::(|val, _| { + ToNumberImpl::eval_date::(val) + }), + ); + // timestamp + registry.register_1_arg::( + "to_yyyymm", + |_, _| FunctionDomain::Full, + |val, ctx| ToNumberImpl::eval_timestamp::(val, &ctx.func_ctx.tz), + ); + registry.register_1_arg::( + "to_yyyymmdd", + |_, _| FunctionDomain::Full, + |val, ctx| ToNumberImpl::eval_timestamp::(val, &ctx.func_ctx.tz), + ); + registry.register_1_arg::( + "to_yyyymmddhh", + |_, _| FunctionDomain::Full, + |val, ctx| ToNumberImpl::eval_timestamp::(val, &ctx.func_ctx.tz), + ); + registry.register_1_arg::( + "to_yyyymmddhhmmss", + |_, _| FunctionDomain::Full, + |val, ctx| ToNumberImpl::eval_timestamp::(val, &ctx.func_ctx.tz), + ); + registry.register_1_arg::( + "to_year", + |_, _| FunctionDomain::Full, + |val, ctx| ToNumberImpl::eval_timestamp::(val, &ctx.func_ctx.tz), + ); + registry.register_1_arg::( + "to_iso_year", + |_, _| FunctionDomain::Full, + |val, ctx| ToNumberImpl::eval_timestamp::(val, &ctx.func_ctx.tz), + ); + registry.register_1_arg::( + "to_quarter", + |_, _| FunctionDomain::Full, + |val, ctx| ToNumberImpl::eval_timestamp::(val, &ctx.func_ctx.tz), + ); + registry.register_1_arg::( + "to_month", + |_, _| FunctionDomain::Full, + |val, ctx| ToNumberImpl::eval_timestamp::(val, &ctx.func_ctx.tz), + ); + registry.register_1_arg::( + "to_day_of_year", + |_, _| FunctionDomain::Full, + |val, ctx| ToNumberImpl::eval_timestamp::(val, &ctx.func_ctx.tz), + ); + registry.register_1_arg::( + "to_day_of_month", + |_, _| FunctionDomain::Full, + |val, ctx| ToNumberImpl::eval_timestamp::(val, &ctx.func_ctx.tz), + ); + registry.register_1_arg::( + "to_day_of_week", + |_, _| FunctionDomain::Full, + |val, ctx| ToNumberImpl::eval_timestamp::(val, &ctx.func_ctx.tz), + ); + registry.register_1_arg::( + "dayofweek", + |_, _| FunctionDomain::Full, + |val, ctx| ToNumberImpl::eval_timestamp::(val, &ctx.func_ctx.tz), + ); + registry.register_1_arg::( + "yearweek", + |_, _| FunctionDomain::Full, + |val, ctx| ToNumberImpl::eval_timestamp::(val, &ctx.func_ctx.tz), + ); + registry.register_1_arg::( + "millennium", + |_, _| FunctionDomain::Full, + |val, ctx| ToNumberImpl::eval_timestamp::(val, &ctx.func_ctx.tz), + ); + registry.register_1_arg::( + "to_week_of_year", + |_, _| FunctionDomain::Full, + |val, ctx| ToNumberImpl::eval_timestamp::(val, &ctx.func_ctx.tz), + ); + registry.register_1_arg::( + "to_unix_timestamp", + |_, _| FunctionDomain::Full, + |val, _| val.div_euclid(MICROS_PER_SEC), + ); + + registry.register_1_arg::( + "epoch", + |_, domain| { + FunctionDomain::Domain(SimpleDomain:: { + min: (domain.min as f64 / 1_000_000f64).into(), + max: (domain.max as f64 / 1_000_000f64).into(), + }) + }, + |val, _| (val as f64 / 1_000_000f64).into(), + ); + + registry.register_1_arg::( + "to_hour", + |_, _| FunctionDomain::Full, + |val, ctx| { + let datetime = timestamp_from_micros(val, &ctx.func_ctx.tz); + datetime.hour() as u8 + }, + ); + registry.register_1_arg::( + "to_minute", + |_, _| FunctionDomain::Full, + |val, ctx| { + let datetime = timestamp_from_micros(val, &ctx.func_ctx.tz); + datetime.minute() as u8 + }, + ); + registry.register_1_arg::( + "to_second", + |_, _| FunctionDomain::Full, + |val, ctx| { + let datetime = timestamp_from_micros(val, &ctx.func_ctx.tz); + datetime.second() as u8 + }, + ); +} diff --git a/src/query/functions/src/scalars/timestamp/src/date_format.rs b/src/query/functions/src/scalars/timestamp/src/date_format.rs new file mode 100644 index 0000000000000..6505dc38a8f10 --- /dev/null +++ b/src/query/functions/src/scalars/timestamp/src/date_format.rs @@ -0,0 +1,313 @@ +// Copyright 2021 Datafuse Labs +// +// Licensed 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. + +use core::fmt; +use std::borrow::Cow; +use std::fmt::Display; +use std::fmt::FormattingOptions; +use std::io::Write; +use std::sync::LazyLock; + +use databend_common_expression::FunctionDomain; +use databend_common_expression::FunctionRegistry; +use databend_common_expression::types::DateType; +use databend_common_expression::types::NullableType; +use databend_common_expression::types::StringType; +use databend_common_expression::types::TimestampType; +use databend_common_expression::types::date::date_to_string; +use databend_common_expression::types::nullable::NullableDomain; +use databend_common_expression::types::string::StringDomain; +use databend_common_expression::types::timestamp::timestamp_from_micros; +use databend_common_expression::types::timestamp::timestamp_to_string; +use databend_common_expression::types::timestamp_tz::TimestampTzType; +use databend_common_expression::vectorize_with_builder_1_arg; +use databend_common_expression::vectorize_with_builder_2_arg; + +/// PostgreSQL to strftime format specifier mappings +/// +/// The vector contains tuples of (postgres_format, strftime_format): +/// - For case-insensitive PostgreSQL formats (e.g., "YYYY"), any case variation will match +/// - For case-sensitive strftime formats (prefixed with '%'), exact case matching is required +/// +/// Note: The sort order (by descending key length) is critical for correct pattern matching +static PG_STRFTIME_MAPPINGS: LazyLock> = LazyLock::new(|| { + let mut mappings = vec![ + // ============================================== + // Case-insensitive PostgreSQL format specifiers + // (will match regardless of letter case) + // ============================================== + // Date components + ("YYYY", "%Y"), // 4-digit year + ("YY", "%y"), // 2-digit year + ("MMMM", "%B"), // Full month name + ("MON", "%b"), // Abbreviated month name (special word boundary handling) + ("MM", "%m"), // Month number (01-12) + ("DD", "%d"), // Day of month (01-31) + ("DY", "%a"), // Abbreviated weekday name + // Time components + ("HH24", "%H"), // 24-hour format (00-23) + ("HH12", "%I"), // 12-hour format (01-12) + ("AM", "%p"), // AM/PM indicator (matches both AM/PM) + ("PM", "%p"), // AM/PM indicator (matches both AM/PM) + ("MI", "%M"), // Minutes (00-59) + ("SS", "%S"), // Seconds (00-59) + ("FF", "%f"), // Fractional seconds + // Special cases + ("UUUU", "%G"), // ISO week-numbering year + ("TZHTZM", "%z"), // Timezone as ±HHMM + ("TZH:TZM", "%z"), // Timezone as ±HH:MM + ("TZH", "%:::z"), // Timezone hour only + // ============================================== + // Case-sensitive strftime format specifiers + // (must match exactly including case) + // ============================================== + ("%Y", "%Y"), // Year aliases + ("%y", "%y"), + ("%B", "%B"), // Month aliases + ("%b", "%b"), + ("%m", "%m"), + ("%d", "%d"), // Day aliases + ("%a", "%a"), // Weekday alias + ("%H", "%H"), // Hour aliases + ("%I", "%I"), + ("%p", "%p"), // AM/PM indicator + ("%M", "%M"), // Minute alias + ("%S", "%S"), // Second alias + ("%f", "%f"), // Fractional second alias + ("%G", "%G"), // ISO year alias + ("%z", "%z"), // Timezone aliases + ("%:::z", "%:::z"), // Timezone hour alias + ]; + + // Critical: Sort by descending key length to ensure longest possible matches are found first + // This prevents shorter patterns from incorrectly matching parts of longer patterns + mappings.sort_by(|a, b| b.0.len().cmp(&a.0.len())); + mappings +}); + +static PG_KEY_LENGTHS: LazyLock> = + LazyLock::new(|| PG_STRFTIME_MAPPINGS.iter().map(|(k, _)| k.len()).collect()); + +fn starts_with_ignore_case(text: &str, prefix: &str) -> bool { + if text.len() < prefix.len() { + return false; + } + text.chars() + .zip(prefix.chars()) + .all(|(c1, c2)| c1.to_lowercase().eq(c2.to_lowercase())) +} + +fn is_word_char(c: char) -> bool { + c.is_ascii_alphanumeric() || c == '_' +} + +#[inline] +pub(super) fn pg_format_to_strftime(pg_format_string: &str) -> String { + let mut result = String::with_capacity(pg_format_string.len() + 16); + let mut current_byte_idx = 0; + let format_len = pg_format_string.len(); + + while current_byte_idx < format_len { + let remaining_slice = &pg_format_string[current_byte_idx..]; + let mut matched = false; + let first_char = remaining_slice.chars().next().unwrap_or('\0'); + + for ((key, value), &key_len) in PG_STRFTIME_MAPPINGS.iter().zip(PG_KEY_LENGTHS.iter()) { + if !key.is_empty() && !first_char.eq_ignore_ascii_case(&key.chars().next().unwrap()) { + continue; + } + + let is_case_sensitive_key = key.starts_with('%'); + let is_current_match = if is_case_sensitive_key { + remaining_slice.starts_with(key) + } else { + starts_with_ignore_case(remaining_slice, key) + }; + + if is_current_match { + let mut is_valid_match = true; + if !is_case_sensitive_key && key.eq_ignore_ascii_case("MON") { + let next_byte_idx = current_byte_idx + key_len; + + if current_byte_idx > 0 { + if let Some(prev_char) = + pg_format_string[..current_byte_idx].chars().next_back() + { + if is_word_char(prev_char) { + is_valid_match = false; + } + } + } + + if is_valid_match && next_byte_idx < format_len { + if let Some(next_char) = pg_format_string[next_byte_idx..].chars().next() { + if is_word_char(next_char) { + is_valid_match = false; + } + } + } + } + + if is_valid_match { + result.push_str(value); + current_byte_idx += key_len; + matched = true; + break; + } + } + } + + if !matched { + let c = first_char; + result.push(c); + current_byte_idx += c.len_utf8(); + } + } + + result +} + +// jiff don't support local formats: +// https://github.com/BurntSushi/jiff/issues/219 +fn replace_time_format(format: &str) -> Cow<'_, str> { + if ["%c", "x", "X"].iter().any(|f| format.contains(f)) { + let format = format + .replace("%c", "%x %X") + .replace("%x", "%F") + .replace("%X", "%T"); + Cow::Owned(format) + } else { + Cow::Borrowed(format) + } +} + +pub(super) fn register(registry: &mut FunctionRegistry) { + registry.register_aliases("to_string", &["date_format", "strftime", "to_char"]); + registry.register_combine_nullable_2_arg::( + "to_string", + |_, _, _| FunctionDomain::MayThrow, + vectorize_with_builder_2_arg::>( + |micros, format, output, ctx| { + let ts = timestamp_from_micros(micros, &ctx.func_ctx.tz); + let format = prepare_format_string(format, &ctx.func_ctx.date_format_style); + let mut buf = String::new(); + let mut formatter = fmt::Formatter::new(&mut buf, FormattingOptions::new()); + if Display::fmt(&ts.strftime(&format), &mut formatter).is_err() { + ctx.set_error(output.len(), format!("{format} is invalid time format")); + output.builder.commit_row(); + output.validity.push(true); + return; + } + match write!(output.builder.row_buffer, "{}", buf) { + Ok(_) => { + output.builder.commit_row(); + output.validity.push(true); + } + Err(e) => { + ctx.set_error( + output.len(), + format!("{format} is invalid time format, error {e}"), + ); + output.builder.commit_row(); + output.validity.push(true); + } + } + }, + ), + ); + + registry.register_passthrough_nullable_1_arg::( + "to_string", + |_, _| FunctionDomain::Full, + vectorize_with_builder_1_arg::(|val, output, _| { + write!(output.row_buffer, "{}", date_to_string(val)).unwrap(); + output.commit_row(); + }), + ); + + registry.register_passthrough_nullable_1_arg::( + "to_string", + |_, _| FunctionDomain::Full, + vectorize_with_builder_1_arg::(|val, output, ctx| { + write!( + output.row_buffer, + "{}", + timestamp_to_string(val, &ctx.func_ctx.tz) + ) + .unwrap(); + output.commit_row(); + }), + ); + + registry.register_passthrough_nullable_1_arg::( + "to_string", + |_, _| FunctionDomain::Full, + vectorize_with_builder_1_arg::(|val, output, _ctx| { + write!(output.row_buffer, "{}", val).unwrap(); + output.commit_row(); + }), + ); + + registry.register_combine_nullable_1_arg::( + "try_to_string", + |_, _| { + FunctionDomain::Domain(NullableDomain { + has_null: false, + value: Some(Box::new(StringDomain { + min: "".to_string(), + max: None, + })), + }) + }, + vectorize_with_builder_1_arg::>(|val, output, _| { + write!(output.builder.row_buffer, "{}", date_to_string(val)).unwrap(); + output.builder.commit_row(); + output.validity.push(true); + }), + ); + + registry.register_combine_nullable_1_arg::( + "try_to_string", + |_, _| { + FunctionDomain::Domain(NullableDomain { + has_null: false, + value: Some(Box::new(StringDomain { + min: "".to_string(), + max: None, + })), + }) + }, + vectorize_with_builder_1_arg::>( + |val, output, ctx| { + write!( + output.builder.row_buffer, + "{}", + timestamp_to_string(val, &ctx.func_ctx.tz) + ) + .unwrap(); + output.builder.commit_row(); + output.validity.push(true); + }, + ), + ); +} + +fn prepare_format_string(format: &str, date_format_style: &str) -> String { + let processed_format = if date_format_style == "oracle" { + pg_format_to_strftime(format) + } else { + format.to_string() + }; + replace_time_format(&processed_format).to_string() +} diff --git a/src/query/functions/src/scalars/timestamp/src/date_round.rs b/src/query/functions/src/scalars/timestamp/src/date_round.rs new file mode 100644 index 0000000000000..8f69f45776e00 --- /dev/null +++ b/src/query/functions/src/scalars/timestamp/src/date_round.rs @@ -0,0 +1,398 @@ +// Copyright 2021 Datafuse Labs +// +// Licensed 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. + +use databend_common_expression::FunctionDomain; +use databend_common_expression::FunctionRegistry; +use databend_common_expression::types::DateType; +use databend_common_expression::types::TimestampType; +use databend_common_expression::types::date::clamp_date; +use databend_common_expression::types::date::date_from_days; +use databend_common_expression::types::number::Int64Type; +use databend_common_expression::types::timestamp::timestamp_from_micros; +use databend_common_expression::vectorize_1_arg; +use databend_common_expression::vectorize_2_arg; +use jiff::Unit; +use jiff::civil::Date; +use jiff::civil::Weekday; +use jiff::civil::date; +use jiff::civil::datetime; +use jiff::tz::TimeZone; + +use crate::date_arithmetic::last_day_of_year_month; + +#[derive(Clone, Copy)] +enum Round { + Second, + Minute, + FiveMinutes, + TenMinutes, + FifteenMinutes, + TimeSlot, + Hour, + Day, +} + +fn round_timestamp(ts: i64, tz: &TimeZone, round: Round) -> i64 { + let dtz = timestamp_from_micros(ts, tz); + let res = match round { + Round::Second => tz + .to_zoned(datetime( + dtz.year(), + dtz.month(), + dtz.day(), + dtz.hour(), + dtz.minute(), + dtz.second(), + 0, + )) + .unwrap(), + Round::Minute => tz + .to_zoned(datetime( + dtz.year(), + dtz.month(), + dtz.day(), + dtz.hour(), + dtz.minute(), + 0, + 0, + )) + .unwrap(), + Round::FiveMinutes => tz + .to_zoned(datetime( + dtz.year(), + dtz.month(), + dtz.day(), + dtz.hour(), + dtz.minute() / 5 * 5, + 0, + 0, + )) + .unwrap(), + Round::TenMinutes => tz + .to_zoned(datetime( + dtz.year(), + dtz.month(), + dtz.day(), + dtz.hour(), + dtz.minute() / 10 * 10, + 0, + 0, + )) + .unwrap(), + Round::FifteenMinutes => tz + .to_zoned(datetime( + dtz.year(), + dtz.month(), + dtz.day(), + dtz.hour(), + dtz.minute() / 15 * 15, + 0, + 0, + )) + .unwrap(), + Round::TimeSlot => tz + .to_zoned(datetime( + dtz.year(), + dtz.month(), + dtz.day(), + dtz.hour(), + dtz.minute() / 30 * 30, + 0, + 0, + )) + .unwrap(), + Round::Hour => tz + .to_zoned(datetime( + dtz.year(), + dtz.month(), + dtz.day(), + dtz.hour(), + 0, + 0, + 0, + )) + .unwrap(), + Round::Day => tz + .to_zoned(datetime(dtz.year(), dtz.month(), dtz.day(), 0, 0, 0, 0)) + .unwrap(), + }; + res.timestamp().as_microsecond() +} + +trait RoundDate { + fn round(date: &Date) -> i32; +} + +struct DateRounder; + +impl DateRounder { + fn eval_timestamp(us: i64, tz: &TimeZone) -> i32 { + T::round(×tamp_from_micros(us, tz).date()) + } + + fn eval_date(date: i32) -> i32 { + T::round(&date_from_days(date)) + } +} + +#[inline] +fn date_to_inner_number(date: &Date) -> i32 { + date.since((Unit::Day, Date::new(1970, 1, 1).unwrap())) + .unwrap() + .get_days() +} + +struct ToLastMonday; +struct ToLastSunday; +struct ToStartOfMonth; +struct ToStartOfQuarter; +struct ToStartOfYear; +struct ToStartOfISOYear; + +struct ToLastOfYear; +struct ToLastOfWeek; +struct ToLastOfMonth; +struct ToLastOfQuarter; +struct ToPreviousMonday; +struct ToPreviousTuesday; +struct ToPreviousWednesday; +struct ToPreviousThursday; +struct ToPreviousFriday; +struct ToPreviousSaturday; +struct ToPreviousSunday; +struct ToNextMonday; +struct ToNextTuesday; +struct ToNextWednesday; +struct ToNextThursday; +struct ToNextFriday; +struct ToNextSaturday; +struct ToNextSunday; + +impl RoundDate for ToLastMonday { + fn round(date: &Date) -> i32 { + date_to_inner_number(date) - date.weekday().to_monday_zero_offset() as i32 + } +} + +impl RoundDate for ToLastSunday { + fn round(date: &Date) -> i32 { + date_to_inner_number(date) - date.weekday().to_sunday_zero_offset() as i32 + } +} + +impl RoundDate for ToStartOfMonth { + fn round(date: &Date) -> i32 { + date_to_inner_number(&date.first_of_month()) + } +} + +impl RoundDate for ToStartOfQuarter { + fn round(input: &Date) -> i32 { + let new_month = (input.month() - 1) / 3 * 3 + 1; + date_to_inner_number(&date(input.year(), new_month, 1)) + } +} + +impl RoundDate for ToStartOfYear { + fn round(date: &Date) -> i32 { + date_to_inner_number(&date.first_of_year()) + } +} + +impl RoundDate for ToStartOfISOYear { + fn round(input: &Date) -> i32 { + let iso_year = input.iso_week_date().year(); + for i in 1..=7 { + let new_date = date(iso_year, 1, i); + if new_date.iso_week_date().weekday() == Weekday::Monday { + return date_to_inner_number(&new_date); + } + } + 0 + } +} + +impl RoundDate for ToLastOfWeek { + fn round(date: &Date) -> i32 { + date_to_inner_number(date) - date.weekday().to_monday_zero_offset() as i32 + 6 + } +} + +impl RoundDate for ToLastOfMonth { + fn round(input: &Date) -> i32 { + let day = last_day_of_year_month(input.year(), input.month()); + date_to_inner_number(&date(input.year(), input.month(), day)) + } +} + +impl RoundDate for ToLastOfQuarter { + fn round(input: &Date) -> i32 { + let new_month = (input.month() - 1) / 3 * 3 + 3; + let day = last_day_of_year_month(input.year(), new_month); + date_to_inner_number(&date(input.year(), new_month, day)) + } +} + +impl RoundDate for ToLastOfYear { + fn round(input: &Date) -> i32 { + let day = last_day_of_year_month(input.year(), 12); + date_to_inner_number(&date(input.year(), 12, day)) + } +} + +macro_rules! impl_round_to_weekday { + ($type:ident, $weekday:ident, $is_previous:literal) => { + impl RoundDate for $type { + fn round(date: &Date) -> i32 { + previous_or_next_date_day(date, Weekday::$weekday, $is_previous) + } + } + }; +} + +impl_round_to_weekday!(ToPreviousMonday, Monday, true); +impl_round_to_weekday!(ToPreviousTuesday, Tuesday, true); +impl_round_to_weekday!(ToPreviousWednesday, Wednesday, true); +impl_round_to_weekday!(ToPreviousThursday, Thursday, true); +impl_round_to_weekday!(ToPreviousFriday, Friday, true); +impl_round_to_weekday!(ToPreviousSaturday, Saturday, true); +impl_round_to_weekday!(ToPreviousSunday, Sunday, true); +impl_round_to_weekday!(ToNextMonday, Monday, false); +impl_round_to_weekday!(ToNextTuesday, Tuesday, false); +impl_round_to_weekday!(ToNextWednesday, Wednesday, false); +impl_round_to_weekday!(ToNextThursday, Thursday, false); +impl_round_to_weekday!(ToNextFriday, Friday, false); +impl_round_to_weekday!(ToNextSaturday, Saturday, false); +impl_round_to_weekday!(ToNextSunday, Sunday, false); + +fn previous_or_next_date_day(date: &Date, target: Weekday, is_previous: bool) -> i32 { + let dir = if is_previous { -1 } else { 1 }; + let mut days_diff = (dir + * (target.to_monday_zero_offset() as i32 - date.weekday().to_monday_zero_offset() as i32) + + 7) + % 7; + + days_diff = if days_diff == 0 { 7 } else { days_diff }; + + clamp_date(date_to_inner_number(date) as i64 + (dir * days_diff) as i64) +} + +pub(super) fn register(registry: &mut FunctionRegistry) { + // timestamp -> timestamp + registry.register_1_arg::( + "to_start_of_second", + |_, _| FunctionDomain::Full, + |val, ctx| round_timestamp(val, &ctx.func_ctx.tz, Round::Second), + ); + registry.register_1_arg::( + "to_start_of_minute", + |_, _| FunctionDomain::Full, + |val, ctx| round_timestamp(val, &ctx.func_ctx.tz, Round::Minute), + ); + registry.register_1_arg::( + "to_start_of_five_minutes", + |_, _| FunctionDomain::Full, + |val, ctx| round_timestamp(val, &ctx.func_ctx.tz, Round::FiveMinutes), + ); + registry.register_1_arg::( + "to_start_of_ten_minutes", + |_, _| FunctionDomain::Full, + |val, ctx| round_timestamp(val, &ctx.func_ctx.tz, Round::TenMinutes), + ); + registry.register_1_arg::( + "to_start_of_fifteen_minutes", + |_, _| FunctionDomain::Full, + |val, ctx| round_timestamp(val, &ctx.func_ctx.tz, Round::FifteenMinutes), + ); + registry.register_1_arg::( + "to_start_of_hour", + |_, _| FunctionDomain::Full, + |val, ctx| round_timestamp(val, &ctx.func_ctx.tz, Round::Hour), + ); + registry.register_1_arg::( + "to_start_of_day", + |_, _| FunctionDomain::Full, + |val, ctx| round_timestamp(val, &ctx.func_ctx.tz, Round::Day), + ); + registry.register_1_arg::( + "time_slot", + |_, _| FunctionDomain::Full, + |val, ctx| round_timestamp(val, &ctx.func_ctx.tz, Round::TimeSlot), + ); + crate::date_time_slice::register(registry); + + // date | timestamp -> date + registry.register_aliases("to_monday", &["to_start_of_iso_week"]); + rounder_functions_helper::(registry, "to_monday"); + rounder_functions_helper::(registry, "to_start_of_week"); + rounder_functions_helper::(registry, "to_start_of_month"); + rounder_functions_helper::(registry, "to_start_of_quarter"); + rounder_functions_helper::(registry, "to_start_of_year"); + rounder_functions_helper::(registry, "to_start_of_iso_year"); + rounder_functions_helper::(registry, "to_last_of_week"); + rounder_functions_helper::(registry, "to_last_of_month"); + rounder_functions_helper::(registry, "to_last_of_quarter"); + rounder_functions_helper::(registry, "to_last_of_year"); + rounder_functions_helper::(registry, "to_previous_monday"); + rounder_functions_helper::(registry, "to_previous_tuesday"); + rounder_functions_helper::(registry, "to_previous_wednesday"); + rounder_functions_helper::(registry, "to_previous_thursday"); + rounder_functions_helper::(registry, "to_previous_friday"); + rounder_functions_helper::(registry, "to_previous_saturday"); + rounder_functions_helper::(registry, "to_previous_sunday"); + rounder_functions_helper::(registry, "to_next_monday"); + rounder_functions_helper::(registry, "to_next_tuesday"); + rounder_functions_helper::(registry, "to_next_wednesday"); + rounder_functions_helper::(registry, "to_next_thursday"); + rounder_functions_helper::(registry, "to_next_friday"); + rounder_functions_helper::(registry, "to_next_saturday"); + rounder_functions_helper::(registry, "to_next_sunday"); + + registry.register_passthrough_nullable_2_arg::( + "to_start_of_week", + |_, _, _| FunctionDomain::Full, + vectorize_2_arg::(|val, mode, _| { + if mode == 0 { + DateRounder::eval_date::(val) + } else { + DateRounder::eval_date::(val) + } + }), + ); + registry.register_passthrough_nullable_2_arg::( + "to_start_of_week", + |_, _, _| FunctionDomain::Full, + vectorize_2_arg::(|val, mode, ctx| { + if mode == 0 { + DateRounder::eval_timestamp::(val, &ctx.func_ctx.tz) + } else { + DateRounder::eval_timestamp::(val, &ctx.func_ctx.tz) + } + }), + ); +} + +fn rounder_functions_helper(registry: &mut FunctionRegistry, name: &str) +where T: RoundDate { + registry.register_passthrough_nullable_1_arg::( + name, + |_, _| FunctionDomain::Full, + vectorize_1_arg::(|val, _| DateRounder::eval_date::(val)), + ); + registry.register_1_arg::( + name, + |_, _| FunctionDomain::Full, + |val, ctx| DateRounder::eval_timestamp::(val, &ctx.func_ctx.tz), + ); +} diff --git a/src/query/functions/src/scalars/timestamp/src/date_time_slice.rs b/src/query/functions/src/scalars/timestamp/src/date_time_slice.rs new file mode 100644 index 0000000000000..338b79b0ba831 --- /dev/null +++ b/src/query/functions/src/scalars/timestamp/src/date_time_slice.rs @@ -0,0 +1,312 @@ +// Copyright 2021 Datafuse Labs +// +// Licensed 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. + +use databend_common_expression::FunctionDomain; +use databend_common_expression::FunctionRegistry; +use databend_common_expression::types::DateType; +use databend_common_expression::types::StringType; +use databend_common_expression::types::TimestampType; +use databend_common_expression::types::number::UInt64Type; +use databend_common_expression::types::timestamp::timestamp_from_micros; +use databend_common_expression::vectorize_4_arg; +use databend_common_expression::vectorize_with_builder_4_arg; +use jiff::SignedDuration; +use jiff::Timestamp; +use jiff::ToSpan; +use jiff::Unit; +use jiff::civil::Date; +use jiff::civil::DateTime; +use jiff::civil::Weekday; +use jiff::tz::TimeZone; + +#[derive(Debug, Clone, Copy)] +enum TimePart { + Year, + Quarter, + Month, + Week, + Day, + Hour, + Minute, + Second, + None, +} + +impl TimePart { + fn from(s: &str) -> Self { + match s.to_ascii_uppercase().as_str() { + "YEAR" => Self::Year, + "QUARTER" => Self::Quarter, + "MONTH" => Self::Month, + "WEEK" => Self::Week, + "DAY" => Self::Day, + "HOUR" => Self::Hour, + "MINUTE" => Self::Minute, + "SECOND" => Self::Second, + _ => Self::None, + } + } + + fn date_part(&self) -> bool { + matches!( + self, + Self::Year | Self::Quarter | Self::Month | Self::Week | Self::Day + ) + } +} + +#[derive(Debug, Clone, Copy)] +enum StartOrEnd { + Start, + End, + None, +} + +impl StartOrEnd { + fn from(s: &str) -> Self { + match s.to_ascii_uppercase().as_str() { + "START" => Self::Start, + "END" => Self::End, + _ => Self::None, + } + } +} + +/// Floor division for i64 (uses div_euclid which floors toward -inf for positive divisor). +fn floordiv(a: i64, b: i64) -> i64 { + a.div_euclid(b) +} + +/// Epoch reference for alignment +fn epoch_date() -> Date { + Date::new(1970, 1, 1).unwrap() +} + +/// Add months to a date safely (handles negative months and day overflow) +fn add_months_to_date(date: Date, months: i64) -> Date { + // Represent months from year 0 as i64 to avoid overflow; year is i32 so convert + let year = date.year() as i64; + let month0 = (date.month() - 1) as i64; // 0..11 + let total_month0 = year * 12 + month0 + months; + let new_year = (total_month0 / 12) as i32; + let new_month0 = total_month0.rem_euclid(12) as u32; // 0..11 + let new_month = new_month0 + 1; // 1..12 + + // day adjust: cap by last day of new month + let new_day = std::cmp::min(date.day() as u32, last_day_of_month(new_year, new_month)) as i8; + Date::new(new_year as i16, new_month as i8, new_day).unwrap() +} + +fn last_day_of_month(year: i32, month: u32) -> u32 { + // get first day of next month then -1 day + let (ny, nm) = if month == 12 { + (year + 1, 1) + } else { + (year, month + 1) + }; + let first_of_next = Date::new(ny as i16, nm as i8, 1).unwrap(); + + (first_of_next - 1.day()).day() as u32 +} + +fn time_slice_timestamp( + ts: i64, + slice_length: u64, + part: TimePart, + start_or_end: StartOrEnd, + week_start: Weekday, + tz: &TimeZone, +) -> i64 { + let slice_length = slice_length as i64; + + let ts = timestamp_from_micros(ts, tz); + let dt = ts.datetime(); + + let start = match part { + TimePart::Year | TimePart::Quarter | TimePart::Month | TimePart::Week | TimePart::Day => { + let date = convert_start_date(dt.date(), part, week_start, slice_length); + DateTime::from(date) + } + TimePart::Hour | TimePart::Minute | TimePart::Second => { + let unit_seconds = match part { + TimePart::Hour => 3600, + TimePart::Minute => 60, + TimePart::Second => 1, + _ => unreachable!(), + }; + let total_unit_seconds = unit_seconds * slice_length; + let secs = ts.timestamp().as_second(); + let slice_index = floordiv(secs, total_unit_seconds); + let start_secs = slice_index * total_unit_seconds; + Timestamp::new(start_secs, 0) + .unwrap() + .to_zoned(tz.clone()) + .datetime() + } + TimePart::None => unreachable!(), + }; + + let result = match start_or_end { + StartOrEnd::Start => start, + StartOrEnd::End => add_units_to_datetime(start, slice_length, part), + _ => unreachable!(), + }; + + result + .to_zoned(tz.clone()) + .unwrap() + .timestamp() + .as_microsecond() +} + +fn add_units_to_datetime(start: DateTime, slice_length: i64, part: TimePart) -> DateTime { + match part { + TimePart::Year | TimePart::Quarter | TimePart::Month | TimePart::Week | TimePart::Day => { + let new_date = add_units_to_date(start.date(), slice_length, part); + DateTime::from(new_date) + } + TimePart::Hour => start + (slice_length * 3600).seconds(), + TimePart::Minute => start + (slice_length * 60).seconds(), + TimePart::Second => start + slice_length.seconds(), + TimePart::None => unreachable!(), + } +} + +fn convert_start_date(date: Date, part: TimePart, week_start: Weekday, slice_length: i64) -> Date { + match part { + TimePart::Year => { + // Years: convert year to offset from 1970, floor to slice, align to Jan 1 + let year_offset = (date.year() as i64) - 1970; + let slice_index = floordiv(year_offset, slice_length); + let start_year = 1970 + slice_index * slice_length; + // START aligned to first day of that year + Date::new(start_year as i16, 1, 1).unwrap() + } + TimePart::Quarter | TimePart::Month => { + // Months/Quarters: compute months since 1970-01 (0-based), slice by slice_months + let unit_months = match part { + TimePart::Quarter => 3, + TimePart::Month => 1, + _ => unreachable!(), + }; + // months_since_epoch: months since 1970-01 (1970-01 = 0) + let months_since_epoch = (date.year() as i64 - 1970) * 12 + ((date.month() - 1) as i64); + let slice_months = slice_length * unit_months; + let slice_index = floordiv(months_since_epoch, slice_months); + let start_months_total = slice_index * slice_months; + let start_year = (1970 + start_months_total / 12) as i32; + let start_month0 = (start_months_total.rem_euclid(12)) as u32; + let start_month = start_month0 + 1; + // START aligned to first day of that month + Date::new(start_year as i16, start_month as i8, 1).unwrap() + } + TimePart::Week => { + // Weeks: compute epoch's week start per week_start, then floor slices by weeks + let epoch = epoch_date(); + let epoch_wd = epoch.weekday(); + // convert weekday to Monday=0 offset + let ep = epoch_wd.to_monday_zero_offset() as i32; + let ws = week_start.to_monday_zero_offset() as i32; + // delta = days from epoch back to the week's start + let delta = (ep - ws).rem_euclid(7) as i64; + let start_of_epoch_week = epoch - delta.days(); + // days from start_of_epoch_week to input date + let days_since_start = (date - start_of_epoch_week).get_days(); + let slice_days = slice_length * 7; + let slice_index = floordiv(days_since_start as i64, slice_days); + start_of_epoch_week + (slice_index * slice_days).days() + } + TimePart::Day => { + // Days: floor by days since epoch + let days_since_epoch = (date - epoch_date()).get_days(); + let slice_index = floordiv(days_since_epoch as i64, slice_length); + epoch_date() + (slice_index * slice_length).days() + } + _ => unreachable!(), + } +} + +fn time_slice_date( + date: i32, + slice_length: u64, + part: TimePart, + start_or_end: StartOrEnd, + week_start: Weekday, +) -> i32 { + let dur = SignedDuration::from_hours((date * 24) as i64); + let date = jiff::civil::date(1970, 1, 1).checked_add(dur).unwrap(); + let slice_length = slice_length as i64; + + let start = convert_start_date(date, part, week_start, slice_length); + + // Return Start or End (End = Start + slice_length * unit) + let result = match start_or_end { + StartOrEnd::Start => start, + StartOrEnd::End => add_units_to_date(start, slice_length, part), + _ => unreachable!(), + }; + + result + .since((Unit::Day, Date::new(1970, 1, 1).unwrap())) + .unwrap() + .get_days() +} + +fn add_units_to_date(start: Date, slice_length: i64, part: TimePart) -> Date { + match part { + TimePart::Year => add_months_to_date(start, slice_length * 12), + TimePart::Quarter => add_months_to_date(start, slice_length * 3), + TimePart::Month => add_months_to_date(start, slice_length), + TimePart::Week => start + (slice_length * 7).days(), + TimePart::Day => start + slice_length.days(), + _ => unreachable!(), + } +} + +pub(super) fn register(registry: &mut FunctionRegistry) { + registry.register_passthrough_nullable_4_arg::( + "time_slice", + |_,_,_, _,_| FunctionDomain::Full, + vectorize_with_builder_4_arg::(|ts, slice_length, start_or_end, part,output, ctx| { + let start_or_end = StartOrEnd::from(start_or_end); + let part = TimePart::from(part); + if !part.date_part() { + ctx.set_error(output.len(), "Date type only support Year | Quarter | Month | Week | Day".to_string()); + output.push(0); + } else { + let mode = ctx.func_ctx.week_start; + let res = if mode == 0 { + time_slice_date(ts, slice_length, part, start_or_end, Weekday::Sunday) + } else { + time_slice_date(ts, slice_length, part, start_or_end, Weekday::Monday) + }; + output.push(res) + } + }), + ); + registry.register_passthrough_nullable_4_arg::( + "time_slice", + |_,_,_, _,_| FunctionDomain::Full, + vectorize_4_arg::(|ts, slice_length, start_or_end, part, ctx| { + let start_or_end = StartOrEnd::from(start_or_end); + let part = TimePart::from(part); + let mode = ctx.func_ctx.week_start; + if mode == 0 { + time_slice_timestamp(ts, slice_length, part, start_or_end, Weekday::Sunday, &ctx.func_ctx.tz) + } else { + time_slice_timestamp(ts, slice_length, part, start_or_end, Weekday::Monday, &ctx.func_ctx.tz) + } + }), + ); +} diff --git a/src/query/functions/src/scalars/timestamp/src/datetime.rs b/src/query/functions/src/scalars/timestamp/src/datetime.rs index f4fa722e5acbf..df738bac111fd 100644 --- a/src/query/functions/src/scalars/timestamp/src/datetime.rs +++ b/src/query/functions/src/scalars/timestamp/src/datetime.rs @@ -12,17 +12,11 @@ // See the License for the specific language governing permissions and // limitations under the License. -use core::fmt; -use std::borrow::Cow; -use std::fmt::Display; -use std::fmt::FormattingOptions; -use std::io::Write; use std::sync::Arc; use chrono::Datelike; use chrono::NaiveDate; use databend_common_base::runtime::catch_unwind; -use databend_common_column::types::months_days_micros; use databend_common_column::types::timestamp_tz; use databend_common_exception::ErrorCode; use databend_common_expression::Column; @@ -31,7 +25,6 @@ use databend_common_expression::Function; use databend_common_expression::FunctionContext; use databend_common_expression::FunctionDomain; use databend_common_expression::FunctionFactory; -use databend_common_expression::FunctionProperty; use databend_common_expression::FunctionRegistry; use databend_common_expression::FunctionSignature; use databend_common_expression::Scalar; @@ -39,38 +32,26 @@ use databend_common_expression::Value; use databend_common_expression::error_to_null; use databend_common_expression::serialize::EPOCH_DAYS_FROM_CE; use databend_common_expression::types::AnyType; -use databend_common_expression::types::Bitmap; use databend_common_expression::types::DataType; use databend_common_expression::types::DateType; -use databend_common_expression::types::F64; -use databend_common_expression::types::Float64Type; -use databend_common_expression::types::Int32Type; -use databend_common_expression::types::IntervalType; use databend_common_expression::types::NullableType; use databend_common_expression::types::NumberDataType; -use databend_common_expression::types::NumberType; use databend_common_expression::types::StringType; use databend_common_expression::types::TimestampType; use databend_common_expression::types::date::DATE_MAX; use databend_common_expression::types::date::DATE_MIN; use databend_common_expression::types::date::clamp_date; -use databend_common_expression::types::date::date_to_string; use databend_common_expression::types::date::string_to_date; -use databend_common_expression::types::nullable::NullableColumn; use databend_common_expression::types::nullable::NullableDomain; use databend_common_expression::types::number::Int64Type; use databend_common_expression::types::number::SimpleDomain; -use databend_common_expression::types::number::UInt8Type; -use databend_common_expression::types::number::UInt16Type; -use databend_common_expression::types::number::UInt32Type; use databend_common_expression::types::number::UInt64Type; -use databend_common_expression::types::string::StringDomain; use databend_common_expression::types::timestamp::MICROS_PER_SEC; use databend_common_expression::types::timestamp::TIMESTAMP_MAX; use databend_common_expression::types::timestamp::TIMESTAMP_MIN; use databend_common_expression::types::timestamp::clamp_timestamp; use databend_common_expression::types::timestamp::string_to_timestamp; -use databend_common_expression::types::timestamp::timestamp_to_string; +use databend_common_expression::types::timestamp::timestamp_from_micros; use databend_common_expression::types::timestamp_tz::TimestampTzType; use databend_common_expression::utils::auto_detect_datetime::auto_detect_date; use databend_common_expression::utils::auto_detect_datetime::auto_detect_timestamp; @@ -79,13 +60,9 @@ use databend_common_expression::utils::auto_detect_datetime::fast_timestamp_from use databend_common_expression::utils::auto_detect_datetime::int64_to_timestamp; use databend_common_expression::utils::auto_detect_datetime::parse_epoch_str; use databend_common_expression::utils::auto_detect_datetime::parse_timestamp_tz_with_auto; -use databend_common_expression::utils::date_helper::*; -use databend_common_expression::vectorize_2_arg; -use databend_common_expression::vectorize_4_arg; use databend_common_expression::vectorize_with_builder_1_arg; use databend_common_expression::vectorize_with_builder_2_arg; use databend_common_expression::vectorize_with_builder_3_arg; -use databend_common_expression::vectorize_with_builder_4_arg; use databend_common_timezone::fast_components_from_timestamp; use databend_common_timezone::fast_utc_from_local; use dtparse::parse; @@ -94,13 +71,16 @@ use jiff::Span; use jiff::Timestamp; use jiff::Unit; use jiff::civil::Date; -use jiff::civil::Weekday; use jiff::civil::date; use jiff::fmt::strtime::BrokenDownTime; use jiff::tz::Offset; use jiff::tz::TimeZone; use num_traits::AsPrimitive; +use crate::date_arithmetic::timestamp_tz_components_via_lut; +use crate::date_conversion::calc_date_to_timestamp; +use crate::date_format::pg_format_to_strftime; + const MONTHS_PER_YEAR: i64 = 12; pub fn register(registry: &mut FunctionRegistry) { @@ -122,37 +102,32 @@ pub fn register(registry: &mut FunctionRegistry) { // cast([date | timestamp] AS string) // to_string([date | timestamp]) - register_to_string(registry); + crate::date_format::register(registry); // cast([date | timestamp] AS [uint8 | int8 | ...]) // to_[uint8 | int8 | ...]([date | timestamp]) - register_to_number(registry); + crate::date_extract::register_cast(registry); // [add | subtract]_[years | months | days | hours | minutes | seconds]([date | timestamp], number) // date_[add | sub]([year | quarter | month | week | day | hour | minute | second], [date | timestamp], number) // [date | timestamp] [+ | -] interval number [year | quarter | month | week | day | hour | minute | second] - register_add_functions(registry); - register_sub_functions(registry); - // date_diff([year | quarter | month | week | day | hour | minute | second], [date | timestamp], [date | timestamp]) // [date | timestamp] +/- [date | timestamp] - register_diff_functions(registry); - // datesub([year | quarter | month | week | day | hour | minute | second], [date | timestamp], [date | timestamp]) // The number of complete partitions between the dates. - register_between_functions(registry); + crate::date_arithmetic::register(registry); // now, today, yesterday, tomorrow - register_real_time_functions(registry); + crate::date_conversion::register_real_time_functions(registry); // to_*([date | timestamp]) -> number - register_to_number_functions(registry); + crate::date_extract::register(registry); // to_*([date | timestamp]) -> [date | timestamp] - register_rounder_functions(registry); + crate::date_round::register(registry); // [date | timestamp] +/- number - register_timestamp_add_sub(registry); + crate::date_arithmetic::register_timestamp_add_sub(registry); // convert_timezone( target_timezone, 'timestamp') register_convert_timezone(registry); @@ -207,20 +182,6 @@ fn timestamp_tz_domain_to_timestamp_domain( }) } -// jiff don't support local formats: -// https://github.com/BurntSushi/jiff/issues/219 -fn replace_time_format(format: &str) -> Cow<'_, str> { - if ["%c", "x", "X"].iter().any(|f| format.contains(f)) { - let format = format - .replace("%c", "%x %X") - .replace("%x", "%F") - .replace("%X", "%T"); - Cow::Owned(format) - } else { - Cow::Borrowed(format) - } -} - fn register_convert_timezone(registry: &mut FunctionRegistry) { // 2 arguments function [target_timezone, src_timestamp] registry.register_passthrough_nullable_2_arg::( @@ -262,7 +223,7 @@ fn register_convert_timezone(registry: &mut FunctionRegistry) { } else { // Fall back to the slower Jiff conversion for timestamps // outside the LUT coverage (e.g. <1900 or >2299). - let src_zoned = src_timestamp.to_timestamp(source_tz); + let src_zoned = timestamp_from_micros(src_timestamp, source_tz); let target_zoned = src_zoned.with_time_zone(t_tz.clone()); ( target_zoned.timestamp().as_microsecond(), @@ -1042,8 +1003,7 @@ fn days_from_components(year: i32, month: u8, day: u8) -> Option { } fn timestamp_days_via_jiff(value: i64, tz: &TimeZone) -> i32 { - value - .to_timestamp(tz) + timestamp_from_micros(value, tz) .date() .since((Unit::Day, Date::new(1970, 1, 1).unwrap())) .unwrap() @@ -1108,13 +1068,13 @@ fn register_timestamp_tz_to_date(registry: &mut FunctionRegistry) { let offset = Offset::from_seconds(val.seconds_offset()).map_err(|err| err.to_string())?; - Ok(val - .timestamp() - .to_timestamp(&TimeZone::fixed(offset)) - .date() - .since((Unit::Day, Date::new(1970, 1, 1).unwrap())) - .unwrap() - .get_days()) + Ok( + timestamp_from_micros(val.timestamp(), &TimeZone::fixed(offset)) + .date() + .since((Unit::Day, Date::new(1970, 1, 1).unwrap())) + .unwrap() + .get_days(), + ) } } } @@ -1151,1666 +1111,6 @@ fn register_number_to_date(registry: &mut FunctionRegistry) { } } -fn register_to_string(registry: &mut FunctionRegistry) { - registry.register_aliases("to_string", &["date_format", "strftime", "to_char"]); - registry.register_combine_nullable_2_arg::( - "to_string", - |_, _, _| FunctionDomain::MayThrow, - vectorize_with_builder_2_arg::>( - |micros, format, output, ctx| { - let ts = micros.to_timestamp(&ctx.func_ctx.tz); - let format = prepare_format_string(format, &ctx.func_ctx.date_format_style); - let mut buf = String::new(); - let mut formatter = fmt::Formatter::new(&mut buf, FormattingOptions::new()); - if Display::fmt(&ts.strftime(&format), &mut formatter).is_err() { - ctx.set_error(output.len(), format!("{format} is invalid time format")); - output.builder.commit_row(); - output.validity.push(true); - return; - } - match write!(output.builder.row_buffer, "{}", buf) { - Ok(_) => { - output.builder.commit_row(); - output.validity.push(true); - } - Err(e) => { - ctx.set_error( - output.len(), - format!("{format} is invalid time format, error {e}"), - ); - output.builder.commit_row(); - output.validity.push(true); - } - } - }, - ), - ); - - registry.register_passthrough_nullable_1_arg::( - "to_string", - |_, _| FunctionDomain::Full, - vectorize_with_builder_1_arg::(|val, output, ctx| { - write!( - output.row_buffer, - "{}", - date_to_string(val, &ctx.func_ctx.tz) - ) - .unwrap(); - output.commit_row(); - }), - ); - - registry.register_passthrough_nullable_1_arg::( - "to_string", - |_, _| FunctionDomain::Full, - vectorize_with_builder_1_arg::(|val, output, ctx| { - write!( - output.row_buffer, - "{}", - timestamp_to_string(val, &ctx.func_ctx.tz) - ) - .unwrap(); - output.commit_row(); - }), - ); - - registry.register_passthrough_nullable_1_arg::( - "to_string", - |_, _| FunctionDomain::Full, - vectorize_with_builder_1_arg::(|val, output, _ctx| { - write!(output.row_buffer, "{}", val).unwrap(); - output.commit_row(); - }), - ); - - registry.register_combine_nullable_1_arg::( - "try_to_string", - |_, _| { - FunctionDomain::Domain(NullableDomain { - has_null: false, - value: Some(Box::new(StringDomain { - min: "".to_string(), - max: None, - })), - }) - }, - vectorize_with_builder_1_arg::>(|val, output, ctx| { - write!( - output.builder.row_buffer, - "{}", - date_to_string(val, &ctx.func_ctx.tz) - ) - .unwrap(); - output.builder.commit_row(); - output.validity.push(true); - }), - ); - - registry.register_combine_nullable_1_arg::( - "try_to_string", - |_, _| { - FunctionDomain::Domain(NullableDomain { - has_null: false, - value: Some(Box::new(StringDomain { - min: "".to_string(), - max: None, - })), - }) - }, - vectorize_with_builder_1_arg::>( - |val, output, ctx| { - write!( - output.builder.row_buffer, - "{}", - timestamp_to_string(val, &ctx.func_ctx.tz) - ) - .unwrap(); - output.builder.commit_row(); - output.validity.push(true); - }, - ), - ); -} - -fn register_to_number(registry: &mut FunctionRegistry) { - registry.register_1_arg::, _>( - "to_int64", - |_, domain| FunctionDomain::Domain(domain.overflow_cast().0), - |val, _| val as i64, - ); - - registry.register_passthrough_nullable_1_arg::, _>( - "to_int64", - |_, domain| FunctionDomain::Domain(*domain), - |val, _| match val { - Value::Scalar(scalar) => Value::Scalar(scalar), - Value::Column(col) => Value::Column(col), - }, - ); - - registry.register_combine_nullable_1_arg::, _, _>( - "try_to_int64", - |_, domain| { - FunctionDomain::Domain(NullableDomain { - has_null: false, - value: Some(Box::new(domain.overflow_cast().0)), - }) - }, - |val, _| match val { - Value::Scalar(scalar) => Value::Scalar(Some(scalar as i64)), - Value::Column(col) => Value::Column(NullableColumn::new_unchecked( - col.iter().map(|val| *val as i64).collect(), - Bitmap::new_constant(true, col.len()), - )), - }, - ); - - registry.register_combine_nullable_1_arg::, _, _>( - "try_to_int64", - |_, domain| { - FunctionDomain::Domain(NullableDomain { - has_null: false, - value: Some(Box::new(*domain)), - }) - }, - |val, _| match val { - Value::Scalar(scalar) => Value::Scalar(Some(scalar)), - Value::Column(col) => { - let validity = Bitmap::new_constant(true, col.len()); - Value::Column(NullableColumn::new_unchecked(col, validity)) - } - }, - ); -} - -fn register_year_arith_function( - registry: &mut FunctionRegistry, - name: &'static str, - delta_sign: i64, -) { - registry.register_passthrough_nullable_2_arg::( - name, - |_, _, _| FunctionDomain::MayThrow, - vectorize_with_builder_2_arg::( - move |date, delta, builder, ctx| match EvalYearsImpl::eval_date( - date, - &ctx.func_ctx.tz, - delta * delta_sign, - false, - ) { - Ok(t) => builder.push(t), - Err(e) => { - ctx.set_error(builder.len(), e); - builder.push(0); - } - }, - ), - ); - registry.register_passthrough_nullable_2_arg::( - name, - |_, _, _| FunctionDomain::MayThrow, - vectorize_with_builder_2_arg::( - move |ts, delta, builder, ctx| match EvalYearsImpl::eval_timestamp( - ts, - &ctx.func_ctx.tz, - delta * delta_sign, - false, - ) { - Ok(t) => builder.push(t), - Err(e) => { - ctx.set_error(builder.len(), e); - builder.push(0); - } - }, - ), - ); -} - -fn register_month_based_arith_function( - registry: &mut FunctionRegistry, - name: &'static str, - month_multiplier: i64, - keep_end_of_month: bool, -) { - registry.register_passthrough_nullable_2_arg::( - name, - |_, _, _| FunctionDomain::MayThrow, - vectorize_with_builder_2_arg::( - move |date, delta, builder, ctx| match EvalMonthsImpl::eval_date( - date, - &ctx.func_ctx.tz, - delta * month_multiplier, - keep_end_of_month, - ) { - Ok(t) => builder.push(t), - Err(e) => { - ctx.set_error(builder.len(), e); - builder.push(0); - } - }, - ), - ); - registry.register_passthrough_nullable_2_arg::( - name, - |_, _, _| FunctionDomain::MayThrow, - vectorize_with_builder_2_arg::( - move |ts, delta, builder, ctx| match EvalMonthsImpl::eval_timestamp( - ts, - &ctx.func_ctx.tz, - delta * month_multiplier, - keep_end_of_month, - ) { - Ok(t) => builder.push(t), - Err(e) => { - ctx.set_error(builder.len(), e); - builder.push(0); - } - }, - ), - ); -} - -fn register_day_based_arith_function( - registry: &mut FunctionRegistry, - name: &'static str, - day_multiplier: i64, -) { - registry.register_2_arg::( - name, - |_, _, _| FunctionDomain::Full, - move |date, delta, _| EvalDaysImpl::eval_date(date, delta * day_multiplier), - ); - - registry.register_2_arg::( - name, - |_, _, _| FunctionDomain::Full, - move |ts, delta, _| EvalDaysImpl::eval_timestamp(ts, delta * day_multiplier), - ); -} - -fn register_time_arith_function( - registry: &mut FunctionRegistry, - name: &'static str, - delta_sign: i64, - factor: i64, -) { - registry.register_2_arg::( - name, - |_, _, _| FunctionDomain::Full, - move |date, delta, _| { - let val = (date as i64) * 24 * 3600 * MICROS_PER_SEC; - EvalTimesImpl::eval_timestamp(val, delta * delta_sign, factor) - }, - ); - - registry.register_2_arg::( - name, - |_, _, _| FunctionDomain::Full, - move |ts, delta, _| EvalTimesImpl::eval_timestamp(ts, delta * delta_sign, factor), - ); -} - -fn register_add_functions(registry: &mut FunctionRegistry) { - register_year_arith_function(registry, "add_years", 1); - register_month_based_arith_function(registry, "add_quarters", 3, false); - register_month_based_arith_function(registry, "date_add_months", 1, false); - // For both ADD_MONTHS and DATEADD, if the result month has fewer days than the original day, the result day of the month is the last day of the result month. - // For ADD_MONTHS only, if the original day is the last day of the month, the result day of month will be the last day of the result month. - register_month_based_arith_function(registry, "add_months", 1, true); - register_day_based_arith_function(registry, "add_days", 1); - register_day_based_arith_function(registry, "add_weeks", 7); - register_time_arith_function(registry, "add_hours", 1, FACTOR_HOUR); - register_time_arith_function(registry, "add_minutes", 1, FACTOR_MINUTE); - register_time_arith_function(registry, "add_seconds", 1, FACTOR_SECOND); -} - -fn register_sub_functions(registry: &mut FunctionRegistry) { - register_year_arith_function(registry, "subtract_years", -1); - register_month_based_arith_function(registry, "subtract_quarters", -3, false); - register_month_based_arith_function(registry, "date_subtract_months", -1, false); - register_month_based_arith_function(registry, "subtract_months", -1, true); - register_day_based_arith_function(registry, "subtract_days", -1); - register_day_based_arith_function(registry, "subtract_weeks", -7); - register_time_arith_function(registry, "subtract_hours", -1, FACTOR_HOUR); - register_time_arith_function(registry, "subtract_minutes", -1, FACTOR_MINUTE); - register_time_arith_function(registry, "subtract_seconds", -1, FACTOR_SECOND); -} - -fn register_diff_functions(registry: &mut FunctionRegistry) { - registry.register_passthrough_nullable_2_arg::( - "diff_years", - |_, _, _| FunctionDomain::MayThrow, - vectorize_with_builder_2_arg::( - |date_end, date_start, builder, ctx| { - let diff_years = - EvalYearsImpl::eval_date_diff(date_start, date_end, &ctx.func_ctx.tz); - builder.push(diff_years as i64); - }, - ), - ); - - registry.register_passthrough_nullable_2_arg::( - "diff_years", - |_, _, _| FunctionDomain::MayThrow, - vectorize_with_builder_2_arg::( - |date_end, date_start, builder, ctx| { - let diff_years = - EvalYearsImpl::eval_timestamp_diff(date_start, date_end, &ctx.func_ctx.tz); - builder.push(diff_years); - }, - ), - ); - - registry.register_passthrough_nullable_2_arg::( - "diff_quarters", - |_, _, _| FunctionDomain::MayThrow, - vectorize_with_builder_2_arg::( - |date_end, date_start, builder, ctx| { - let diff_years = - EvalQuartersImpl::eval_date_diff(date_start, date_end, &ctx.func_ctx.tz); - builder.push(diff_years as i64); - }, - ), - ); - - registry.register_passthrough_nullable_2_arg::( - "diff_quarters", - |_, _, _| FunctionDomain::MayThrow, - vectorize_with_builder_2_arg::( - |date_end, date_start, builder, ctx| { - let diff_years = - EvalQuartersImpl::eval_timestamp_diff(date_start, date_end, &ctx.func_ctx.tz); - builder.push(diff_years); - }, - ), - ); - - registry.register_passthrough_nullable_2_arg::( - "diff_months", - |_, _, _| FunctionDomain::MayThrow, - vectorize_with_builder_2_arg::( - |date_end, date_start, builder, ctx| { - let diff_months = - EvalMonthsImpl::eval_date_diff(date_start, date_end, &ctx.func_ctx.tz); - builder.push(diff_months as i64); - }, - ), - ); - - registry.register_passthrough_nullable_2_arg::( - "diff_months", - |_, _, _| FunctionDomain::MayThrow, - vectorize_with_builder_2_arg::( - |date_end, date_start, builder, ctx| { - let diff_months = - EvalMonthsImpl::eval_timestamp_diff(date_start, date_end, &ctx.func_ctx.tz); - builder.push(diff_months); - }, - ), - ); - - registry.register_passthrough_nullable_2_arg::( - "diff_weeks", - |_, _, _| FunctionDomain::MayThrow, - vectorize_with_builder_2_arg::( - |date_end, date_start, builder, _| { - let diff_years = EvalWeeksImpl::eval_date_diff(date_start, date_end); - builder.push(diff_years as i64); - }, - ), - ); - - registry.register_passthrough_nullable_2_arg::( - "diff_weeks", - |_, _, _| FunctionDomain::MayThrow, - vectorize_with_builder_2_arg::( - |date_end, date_start, builder, _| { - let diff_years = EvalWeeksImpl::eval_timestamp_diff(date_start, date_end); - builder.push(diff_years); - }, - ), - ); - - registry.register_passthrough_nullable_2_arg::( - "diff_days", - |_, _, _| FunctionDomain::MayThrow, - vectorize_with_builder_2_arg::( - |date_end, date_start, builder, _| { - let diff_days = EvalDaysImpl::eval_date_diff(date_start, date_end); - builder.push(diff_days as i64); - }, - ), - ); - - registry.register_passthrough_nullable_2_arg::( - "diff_days", - |_, _, _| FunctionDomain::MayThrow, - vectorize_with_builder_2_arg::( - |date_end, date_start, builder, _| { - let diff_days = EvalDaysImpl::eval_timestamp_diff(date_start, date_end); - builder.push(diff_days); - }, - ), - ); - - registry.register_passthrough_nullable_2_arg::( - "diff_hours", - |_, _, _| FunctionDomain::MayThrow, - vectorize_with_builder_2_arg::( - |date_end, date_start, builder, _| { - let diff_hours = - EvalTimesImpl::eval_timestamp_diff(date_start, date_end, FACTOR_HOUR); - builder.push(diff_hours); - }, - ), - ); - - registry.register_passthrough_nullable_2_arg::( - "diff_minutes", - |_, _, _| FunctionDomain::MayThrow, - vectorize_with_builder_2_arg::( - |date_end, date_start, builder, _| { - let diff_minutes = - EvalTimesImpl::eval_timestamp_diff(date_start, date_end, FACTOR_MINUTE); - builder.push(diff_minutes); - }, - ), - ); - - registry.register_passthrough_nullable_2_arg::( - "diff_seconds", - |_, _, _| FunctionDomain::MayThrow, - vectorize_with_builder_2_arg::( - |date_end, date_start, builder, _| { - let diff_seconds = - EvalTimesImpl::eval_timestamp_diff(date_start, date_end, FACTOR_SECOND); - builder.push(diff_seconds); - }, - ), - ); - - registry.register_passthrough_nullable_2_arg::( - "diff_microseconds", - |_, _, _| FunctionDomain::MayThrow, - vectorize_with_builder_2_arg::( - |date_end, date_start, builder, _| { - let diff_microseconds = - EvalDaysImpl::eval_date_diff(date_start, date_end) as i64 * MICROSECS_PER_DAY; - builder.push(diff_microseconds); - }, - ), - ); - - registry.register_passthrough_nullable_2_arg::( - "diff_microseconds", - |_, _, _| FunctionDomain::MayThrow, - vectorize_with_builder_2_arg::( - |date_end, date_start, builder, _| { - builder.push(date_end - date_start); - }, - ), - ); - - registry.register_passthrough_nullable_2_arg::( - "diff_yearweeks", - |_, _, _| FunctionDomain::MayThrow, - vectorize_with_builder_2_arg::( - |date_end, date_start, builder, ctx| { - let diff = - EvalYearWeeksImpl::eval_date_diff(date_start, date_end, &ctx.func_ctx.tz); - builder.push(diff as i64); - }, - ), - ); - - registry.register_passthrough_nullable_2_arg::( - "diff_yearweeks", - |_, _, _| FunctionDomain::MayThrow, - vectorize_with_builder_2_arg::( - |date_end, date_start, builder, ctx| { - let diff = - EvalYearWeeksImpl::eval_timestamp_diff(date_start, date_end, &ctx.func_ctx.tz); - builder.push(diff); - }, - ), - ); - - registry.register_passthrough_nullable_2_arg::( - "diff_isoyears", - |_, _, _| FunctionDomain::MayThrow, - vectorize_with_builder_2_arg::( - |date_end, date_start, builder, ctx| { - let diff = EvalISOYearsImpl::eval_date_diff(date_start, date_end, &ctx.func_ctx.tz); - builder.push(diff as i64); - }, - ), - ); - - registry.register_passthrough_nullable_2_arg::( - "diff_isoyears", - |_, _, _| FunctionDomain::MayThrow, - vectorize_with_builder_2_arg::( - |date_end, date_start, builder, ctx| { - let diff = - EvalISOYearsImpl::eval_timestamp_diff(date_start, date_end, &ctx.func_ctx.tz); - builder.push(diff); - }, - ), - ); - - registry.register_passthrough_nullable_2_arg::( - "diff_millenniums", - |_, _, _| FunctionDomain::MayThrow, - vectorize_with_builder_2_arg::( - |date_end, date_start, builder, ctx| { - let diff_years = - EvalYearsImpl::eval_date_diff(date_start, date_end, &ctx.func_ctx.tz); - builder.push((diff_years / 1000) as i64); - }, - ), - ); - - registry.register_passthrough_nullable_2_arg::( - "diff_millenniums", - |_, _, _| FunctionDomain::MayThrow, - vectorize_with_builder_2_arg::( - |date_end, date_start, builder, ctx| { - let diff_years = - EvalYearsImpl::eval_timestamp_diff(date_start, date_end, &ctx.func_ctx.tz); - - builder.push(diff_years / 1000); - }, - ), - ); - registry.register_aliases("diff_seconds", &["diff_epochs"]); - registry.register_aliases("diff_days", &["diff_dows", "diff_isodows", "diff_doys"]); - - registry.register_2_arg::( - "minus", - |_, lhs, rhs| { - (|| { - let lm = lhs.max; - let ln = lhs.min; - let rm: i32 = num_traits::cast::cast(rhs.max)?; - let rn: i32 = num_traits::cast::cast(rhs.min)?; - - Some(FunctionDomain::Domain(SimpleDomain:: { - min: ln.checked_sub(rm)?, - max: lm.checked_sub(rn)?, - })) - })() - .unwrap_or(FunctionDomain::Full) - }, - |a, b, _| a - b, - ); - - registry.register_2_arg::( - "timestamp_diff", - |_, _, _| FunctionDomain::MayThrow, - |a, b, _| months_days_micros::new(0, 0, a - b), - ); - - registry.register_2_arg::( - "minus", - |_, lhs, rhs| { - (|| { - let lm = lhs.max; - let ln = lhs.min; - let rm = rhs.max; - let rn = rhs.min; - - Some(FunctionDomain::Domain(SimpleDomain:: { - min: ln.checked_sub(rm)?, - max: lm.checked_sub(rn)?, - })) - })() - .unwrap_or(FunctionDomain::Full) - }, - |a, b, _| a - b, - ); - - registry.register_passthrough_nullable_2_arg::( - "months_between", - |_, lhs, rhs| { - let lm = lhs.max; - let ln = lhs.min; - let rm = rhs.max; - let rn = rhs.min; - - let min = EvalMonthsImpl::months_between(ln, rm); - let max = EvalMonthsImpl::months_between(lm, rn); - FunctionDomain::Domain(SimpleDomain:: { - min: min.into(), - max: max.into(), - }) - }, - vectorize_2_arg::(|a, b, _ctx| { - EvalMonthsImpl::months_between(a, b).into() - }), - ); - - registry - .register_passthrough_nullable_2_arg::( - "months_between", - |_, lhs, rhs| { - let lm = lhs.max; - let ln = lhs.min; - let rm = rhs.max; - let rn = rhs.min; - - FunctionDomain::Domain(SimpleDomain:: { - min: EvalMonthsImpl::months_between_ts(ln, rm).into(), - max: EvalMonthsImpl::months_between_ts(lm, rn).into(), - }) - }, - vectorize_2_arg::(|a, b, _ctx| { - EvalMonthsImpl::months_between_ts(a, b).into() - }), - ); -} - -fn register_between_functions(registry: &mut FunctionRegistry) { - registry.register_passthrough_nullable_2_arg::( - "between_years", - |_, _, _| FunctionDomain::MayThrow, - vectorize_with_builder_2_arg::( - |date_end, date_start, builder, ctx| { - let between_years = - EvalYearsImpl::eval_date_between(date_start, date_end, &ctx.func_ctx.tz); - builder.push(between_years as i64); - }, - ), - ); - - registry.register_passthrough_nullable_2_arg::( - "between_years", - |_, _, _| FunctionDomain::MayThrow, - vectorize_with_builder_2_arg::( - |date_end, date_start, builder, ctx| { - let between_years = - EvalYearsImpl::eval_timestamp_between(date_start, date_end, &ctx.func_ctx.tz); - builder.push(between_years); - }, - ), - ); - - registry.register_passthrough_nullable_2_arg::( - "between_quarters", - |_, _, _| FunctionDomain::MayThrow, - vectorize_with_builder_2_arg::( - |date_end, date_start, builder, ctx| { - let between_quarters = - EvalMonthsImpl::eval_date_between(date_start, date_end, &ctx.func_ctx.tz) / 3; - builder.push(between_quarters as i64); - }, - ), - ); - - registry.register_passthrough_nullable_2_arg::( - "between_quarters", - |_, _, _| FunctionDomain::MayThrow, - vectorize_with_builder_2_arg::( - |date_end, date_start, builder, ctx| { - let between_quarters = - EvalMonthsImpl::eval_timestamp_between(date_start, date_end, &ctx.func_ctx.tz) - / 3; - builder.push(between_quarters); - }, - ), - ); - - registry.register_passthrough_nullable_2_arg::( - "between_months", - |_, _, _| FunctionDomain::MayThrow, - vectorize_with_builder_2_arg::( - |date_end, date_start, builder, ctx| { - let between_months = - EvalMonthsImpl::eval_date_between(date_start, date_end, &ctx.func_ctx.tz); - builder.push(between_months as i64); - }, - ), - ); - - registry.register_passthrough_nullable_2_arg::( - "between_months", - |_, _, _| FunctionDomain::MayThrow, - vectorize_with_builder_2_arg::( - |date_end, date_start, builder, ctx| { - let between_months = - EvalMonthsImpl::eval_timestamp_between(date_start, date_end, &ctx.func_ctx.tz); - builder.push(between_months); - }, - ), - ); - - registry.register_passthrough_nullable_2_arg::( - "between_weeks", - |_, _, _| FunctionDomain::MayThrow, - vectorize_with_builder_2_arg::( - |date_end, date_start, builder, ctx| { - let between_weeks = - EvalWeeksImpl::eval_date_between(date_start, date_end, &ctx.func_ctx.tz); - builder.push(between_weeks as i64); - }, - ), - ); - - registry.register_passthrough_nullable_2_arg::( - "between_weeks", - |_, _, _| FunctionDomain::MayThrow, - vectorize_with_builder_2_arg::( - |date_end, date_start, builder, ctx| { - let between_weeks = - EvalWeeksImpl::eval_timestamp_between(date_start, date_end, &ctx.func_ctx.tz); - builder.push(between_weeks); - }, - ), - ); - - registry.register_passthrough_nullable_2_arg::( - "between_days", - |_, _, _| FunctionDomain::MayThrow, - vectorize_with_builder_2_arg::( - |date_end, date_start, builder, _| { - // day is date type unit - let between_days = EvalDaysImpl::eval_date_diff(date_start, date_end); - builder.push(between_days as i64); - }, - ), - ); - - registry.register_passthrough_nullable_2_arg::( - "between_days", - |_, _, _| FunctionDomain::MayThrow, - vectorize_with_builder_2_arg::( - |date_end, date_start, builder, ctx| { - let between_days = - EvalDaysImpl::eval_timestamp_between(date_start, date_end, &ctx.func_ctx.tz); - builder.push(between_days); - }, - ), - ); - - registry.register_passthrough_nullable_2_arg::( - "between_hours", - |_, _, _| FunctionDomain::MayThrow, - vectorize_with_builder_2_arg::( - |date_end, date_start, builder, _| { - let between_hours = EvalDaysImpl::eval_date_diff(date_start, date_end) as i64 * 24; - builder.push(between_hours); - }, - ), - ); - - registry.register_passthrough_nullable_2_arg::( - "between_hours", - |_, _, _| FunctionDomain::MayThrow, - vectorize_with_builder_2_arg::( - |date_end, date_start, builder, _| { - let between_hours = - EvalTimesImpl::eval_timestamp_between("hours", date_start, date_end); - builder.push(between_hours); - }, - ), - ); - - registry.register_passthrough_nullable_2_arg::( - "between_minutes", - |_, _, _| FunctionDomain::MayThrow, - vectorize_with_builder_2_arg::( - |date_end, date_start, builder, _| { - let between_minutes = - EvalDaysImpl::eval_date_diff(date_start, date_end) as i64 * 24 * 60; - builder.push(between_minutes); - }, - ), - ); - - registry.register_passthrough_nullable_2_arg::( - "between_minutes", - |_, _, _| FunctionDomain::MayThrow, - vectorize_with_builder_2_arg::( - |date_end, date_start, builder, _| { - let between_minutes = - EvalTimesImpl::eval_timestamp_between("minutes", date_start, date_end); - builder.push(between_minutes); - }, - ), - ); - - registry.register_passthrough_nullable_2_arg::( - "between_seconds", - |_, _, _| FunctionDomain::MayThrow, - vectorize_with_builder_2_arg::( - |date_end, date_start, builder, _| { - let between_seconds = - EvalDaysImpl::eval_date_diff(date_start, date_end) as i64 * 24 * 3600; - builder.push(between_seconds); - }, - ), - ); - - registry.register_passthrough_nullable_2_arg::( - "between_seconds", - |_, _, _| FunctionDomain::MayThrow, - vectorize_with_builder_2_arg::( - |date_end, date_start, builder, _| { - let between_seconds = - EvalTimesImpl::eval_timestamp_between("seconds", date_start, date_end); - builder.push(between_seconds); - }, - ), - ); - - registry.register_passthrough_nullable_2_arg::( - "between_microseconds", - |_, _, _| FunctionDomain::MayThrow, - vectorize_with_builder_2_arg::( - |date_end, date_start, builder, _| { - let between_microseconds = - EvalDaysImpl::eval_date_diff(date_start, date_end) as i64 * MICROSECS_PER_DAY; - builder.push(between_microseconds); - }, - ), - ); - - registry.register_passthrough_nullable_2_arg::( - "between_microseconds", - |_, _, _| FunctionDomain::MayThrow, - vectorize_with_builder_2_arg::( - |date_end, date_start, builder, _| { - builder.push(date_end - date_start); - }, - ), - ); - - registry.register_passthrough_nullable_2_arg::( - "between_isoyears", - |_, _, _| FunctionDomain::MayThrow, - vectorize_with_builder_2_arg::( - |date_end, date_start, builder, ctx| { - let between_isoyears = - EvalISOYearsImpl::eval_date_between(date_start, date_end, &ctx.func_ctx.tz); - builder.push(between_isoyears as i64); - }, - ), - ); - - registry.register_passthrough_nullable_2_arg::( - "between_isoyears", - |_, _, _| FunctionDomain::MayThrow, - vectorize_with_builder_2_arg::( - |date_end, date_start, builder, ctx| { - let between_isoyears = EvalISOYearsImpl::eval_timestamp_between( - date_start, - date_end, - &ctx.func_ctx.tz, - ); - builder.push(between_isoyears); - }, - ), - ); - - registry.register_passthrough_nullable_2_arg::( - "between_millenniums", - |_, _, _| FunctionDomain::MayThrow, - vectorize_with_builder_2_arg::( - |date_end, date_start, builder, ctx| { - let between_millenniums = - EvalYearsImpl::eval_date_between(date_start, date_end, &ctx.func_ctx.tz); - builder.push((between_millenniums / 1000) as i64); - }, - ), - ); - - registry.register_passthrough_nullable_2_arg::( - "between_millenniums", - |_, _, _| FunctionDomain::MayThrow, - vectorize_with_builder_2_arg::( - |date_end, date_start, builder, ctx| { - let between_millenniums = - EvalYearsImpl::eval_timestamp_between(date_start, date_end, &ctx.func_ctx.tz); - - builder.push(between_millenniums / 1000); - }, - ), - ); - registry.register_aliases("between_seconds", &["between_epochs"]); - registry.register_aliases("between_weeks", &["between_yearweeks"]); - registry.register_aliases("between_days", &[ - "between_dows", - "between_isodows", - "between_doys", - ]); -} - -fn normalize_time_precision(raw: i64) -> Result { - if (0..=9).contains(&raw) { - Ok(raw as u8) - } else { - Err(format!( - "Invalid fractional seconds precision `{raw}` for `current_time` (expect 0-9)" - )) - } -} - -fn current_time_string(func_ctx: &FunctionContext, precision: Option) -> String { - let datetime = func_ctx.now.with_time_zone(func_ctx.tz.clone()).datetime(); - let nanos = datetime.subsec_nanosecond() as u32; - let mut value = format!( - "{:02}:{:02}:{:02}", - datetime.hour(), - datetime.minute(), - datetime.second() - ); - - let precision = precision.unwrap_or(9).min(9); - if precision > 0 { - let divisor = 10_u32.pow(9 - precision as u32); - let truncated = nanos / divisor; - let frac = format!("{:0width$}", truncated, width = precision as usize); - value.push('.'); - value.push_str(&frac); - } - - value -} - -fn register_real_time_functions(registry: &mut FunctionRegistry) { - registry.register_aliases("now", &["current_timestamp"]); - registry.register_aliases("today", &["current_date"]); - - registry.properties.insert( - "now".to_string(), - FunctionProperty::default().non_deterministic(), - ); - registry.properties.insert( - "current_time".to_string(), - FunctionProperty::default().non_deterministic(), - ); - registry.properties.insert( - "today".to_string(), - FunctionProperty::default().non_deterministic(), - ); - registry.properties.insert( - "yesterday".to_string(), - FunctionProperty::default().non_deterministic(), - ); - registry.properties.insert( - "tomorrow".to_string(), - FunctionProperty::default().non_deterministic(), - ); - - for name in &[ - "to_timestamp", - "to_timestamp_tz", - "to_date", - "to_yyyymm", - "to_yyyymmdd", - ] { - registry - .properties - .insert(name.to_string(), FunctionProperty::default().monotonicity()); - } - - registry.properties.insert( - "to_string".to_string(), - FunctionProperty::default() - .monotonicity_type(DataType::Timestamp) - .monotonicity_type(DataType::Timestamp.wrap_nullable()), - ); - - registry.properties.insert( - "to_string".to_string(), - FunctionProperty::default() - .monotonicity_type(DataType::Date) - .monotonicity_type(DataType::Date.wrap_nullable()), - ); - - registry.register_0_arg_core::( - "now", - |_| FunctionDomain::Full, - |ctx| Value::Scalar(ctx.func_ctx.now.timestamp().as_microsecond()), - ); - - registry.register_0_arg_core::( - "current_time", - |_| FunctionDomain::Full, - |ctx| Value::Scalar(current_time_string(ctx.func_ctx, None)), - ); - - registry.register_passthrough_nullable_1_arg::( - "current_time", - |_, _| FunctionDomain::MayThrow, - vectorize_with_builder_1_arg::(|precision, output, ctx| { - match normalize_time_precision(precision) { - Ok(valid_precision) => { - output.put_and_commit(current_time_string(ctx.func_ctx, Some(valid_precision))); - } - Err(err) => { - ctx.set_error(output.len(), err); - output.commit_row(); - } - } - }), - ); - - registry.register_0_arg_core::( - "today", - |_| FunctionDomain::Full, - |ctx| Value::Scalar(today_date(&ctx.func_ctx.now, &ctx.func_ctx.tz)), - ); - - registry.register_0_arg_core::( - "yesterday", - |_| FunctionDomain::Full, - |ctx| Value::Scalar(today_date(&ctx.func_ctx.now, &ctx.func_ctx.tz) - 1), - ); - - registry.register_0_arg_core::( - "tomorrow", - |_| FunctionDomain::Full, - |ctx| Value::Scalar(today_date(&ctx.func_ctx.now, &ctx.func_ctx.tz) + 1), - ); -} - -fn register_to_number_functions(registry: &mut FunctionRegistry) { - // date - registry.register_passthrough_nullable_1_arg::( - "to_yyyymm", - |_, _| FunctionDomain::Full, - vectorize_with_builder_1_arg::(|val, output, ctx| { - match ToNumberImpl::eval_date::(val, &ctx.func_ctx.tz) { - Ok(t) => output.push(t), - Err(e) => { - ctx.set_error(output.len(), format!("cannot parse to type `Date`. {}", e)); - output.push(0); - } - } - }), - ); - registry.register_passthrough_nullable_1_arg::( - "to_yyyymmdd", - |_, _| FunctionDomain::Full, - vectorize_with_builder_1_arg::(|val, output, ctx| { - match ToNumberImpl::eval_date::(val, &ctx.func_ctx.tz) { - Ok(t) => output.push(t), - Err(e) => { - ctx.set_error(output.len(), format!("cannot parse to type `Date`. {}", e)); - output.push(0); - } - } - }), - ); - registry.register_passthrough_nullable_1_arg::( - "to_year", - |_, _| FunctionDomain::Full, - vectorize_with_builder_1_arg::(|val, output, ctx| { - match ToNumberImpl::eval_date::(val, &ctx.func_ctx.tz) { - Ok(t) => output.push(t), - Err(e) => { - ctx.set_error(output.len(), format!("cannot parse to type `Date`. {}", e)); - output.push(0); - } - } - }), - ); - - registry.register_passthrough_nullable_1_arg::( - "to_iso_year", - |_, _| FunctionDomain::Full, - vectorize_with_builder_1_arg::(|val, output, ctx| { - match ToNumberImpl::eval_date::(val, &ctx.func_ctx.tz) { - Ok(t) => output.push(t), - Err(e) => { - ctx.set_error(output.len(), format!("cannot parse to type `Date`. {}", e)); - output.push(0); - } - } - }), - ); - - registry.register_passthrough_nullable_1_arg::( - "to_quarter", - |_, _| FunctionDomain::Full, - vectorize_with_builder_1_arg::(|val, output, ctx| { - match ToNumberImpl::eval_date::(val, &ctx.func_ctx.tz) { - Ok(t) => output.push(t), - Err(e) => { - ctx.set_error(output.len(), format!("cannot parse to type `Date`. {}", e)); - output.push(0); - } - } - }), - ); - registry.register_passthrough_nullable_1_arg::( - "to_month", - |_, _| FunctionDomain::Full, - vectorize_with_builder_1_arg::(|val, output, ctx| { - match ToNumberImpl::eval_date::(val, &ctx.func_ctx.tz) { - Ok(t) => output.push(t), - Err(e) => { - ctx.set_error(output.len(), format!("cannot parse to type `Date`. {}", e)); - output.push(0); - } - } - }), - ); - registry.register_passthrough_nullable_1_arg::( - "to_day_of_year", - |_, _| FunctionDomain::Full, - vectorize_with_builder_1_arg::(|val, output, ctx| { - match ToNumberImpl::eval_date::(val, &ctx.func_ctx.tz) { - Ok(t) => output.push(t), - Err(e) => { - ctx.set_error(output.len(), format!("cannot parse to type `Date`. {}", e)); - output.push(0); - } - } - }), - ); - registry.register_passthrough_nullable_1_arg::( - "to_day_of_month", - |_, _| FunctionDomain::Full, - vectorize_with_builder_1_arg::(|val, output, ctx| { - match ToNumberImpl::eval_date::(val, &ctx.func_ctx.tz) { - Ok(t) => output.push(t), - Err(e) => { - ctx.set_error(output.len(), format!("cannot parse to type `Date`. {}", e)); - output.push(0); - } - } - }), - ); - registry.register_passthrough_nullable_1_arg::( - "to_day_of_week", - |_, _| FunctionDomain::Full, - vectorize_with_builder_1_arg::(|val, output, ctx| { - match ToNumberImpl::eval_date::(val, &ctx.func_ctx.tz) { - Ok(t) => output.push(t), - Err(e) => { - ctx.set_error(output.len(), format!("cannot parse to type `Date`. {}", e)); - output.push(0); - } - } - }), - ); - registry.register_passthrough_nullable_1_arg::( - "dayofweek", - |_, _| FunctionDomain::Full, - vectorize_with_builder_1_arg::(|val, output, ctx| { - match ToNumberImpl::eval_date::(val, &ctx.func_ctx.tz) { - Ok(t) => output.push(t), - Err(e) => { - ctx.set_error(output.len(), format!("cannot parse to type `Date`. {}", e)); - output.push(0); - } - } - }), - ); - registry.register_passthrough_nullable_1_arg::( - "yearweek", - |_, _| FunctionDomain::Full, - vectorize_with_builder_1_arg::(|val, output, ctx| { - match ToNumberImpl::eval_date::(val, &ctx.func_ctx.tz) { - Ok(t) => output.push(t), - Err(e) => { - ctx.set_error(output.len(), format!("cannot parse to type `Date`. {}", e)); - output.push(0); - } - } - }), - ); - registry.register_passthrough_nullable_1_arg::( - "millennium", - |_, _| FunctionDomain::Full, - vectorize_with_builder_1_arg::(|val, output, ctx| { - match ToNumberImpl::eval_date::(val, &ctx.func_ctx.tz) { - Ok(t) => output.push(t), - Err(e) => { - ctx.set_error(output.len(), format!("cannot parse to type `Date`. {}", e)); - output.push(0); - } - } - }), - ); - registry.register_passthrough_nullable_1_arg::( - "to_week_of_year", - |_, _| FunctionDomain::Full, - vectorize_with_builder_1_arg::(|val, output, ctx| { - match ToNumberImpl::eval_date::(val, &ctx.func_ctx.tz) { - Ok(t) => output.push(t), - Err(e) => { - ctx.set_error(output.len(), format!("cannot parse to type `Date`. {}", e)); - output.push(0); - } - } - }), - ); - // timestamp - registry.register_1_arg::( - "to_yyyymm", - |_, _| FunctionDomain::Full, - |val, ctx| ToNumberImpl::eval_timestamp::(val, &ctx.func_ctx.tz), - ); - registry.register_1_arg::( - "to_yyyymmdd", - |_, _| FunctionDomain::Full, - |val, ctx| ToNumberImpl::eval_timestamp::(val, &ctx.func_ctx.tz), - ); - registry.register_1_arg::( - "to_yyyymmddhh", - |_, _| FunctionDomain::Full, - |val, ctx| ToNumberImpl::eval_timestamp::(val, &ctx.func_ctx.tz), - ); - registry.register_1_arg::( - "to_yyyymmddhhmmss", - |_, _| FunctionDomain::Full, - |val, ctx| ToNumberImpl::eval_timestamp::(val, &ctx.func_ctx.tz), - ); - registry.register_1_arg::( - "to_year", - |_, _| FunctionDomain::Full, - |val, ctx| ToNumberImpl::eval_timestamp::(val, &ctx.func_ctx.tz), - ); - registry.register_1_arg::( - "to_iso_year", - |_, _| FunctionDomain::Full, - |val, ctx| ToNumberImpl::eval_timestamp::(val, &ctx.func_ctx.tz), - ); - registry.register_1_arg::( - "to_quarter", - |_, _| FunctionDomain::Full, - |val, ctx| ToNumberImpl::eval_timestamp::(val, &ctx.func_ctx.tz), - ); - registry.register_1_arg::( - "to_month", - |_, _| FunctionDomain::Full, - |val, ctx| ToNumberImpl::eval_timestamp::(val, &ctx.func_ctx.tz), - ); - registry.register_1_arg::( - "to_day_of_year", - |_, _| FunctionDomain::Full, - |val, ctx| ToNumberImpl::eval_timestamp::(val, &ctx.func_ctx.tz), - ); - registry.register_1_arg::( - "to_day_of_month", - |_, _| FunctionDomain::Full, - |val, ctx| ToNumberImpl::eval_timestamp::(val, &ctx.func_ctx.tz), - ); - registry.register_1_arg::( - "to_day_of_week", - |_, _| FunctionDomain::Full, - |val, ctx| ToNumberImpl::eval_timestamp::(val, &ctx.func_ctx.tz), - ); - registry.register_1_arg::( - "dayofweek", - |_, _| FunctionDomain::Full, - |val, ctx| ToNumberImpl::eval_timestamp::(val, &ctx.func_ctx.tz), - ); - registry.register_1_arg::( - "yearweek", - |_, _| FunctionDomain::Full, - |val, ctx| ToNumberImpl::eval_timestamp::(val, &ctx.func_ctx.tz), - ); - registry.register_1_arg::( - "millennium", - |_, _| FunctionDomain::Full, - |val, ctx| ToNumberImpl::eval_timestamp::(val, &ctx.func_ctx.tz), - ); - registry.register_1_arg::( - "to_week_of_year", - |_, _| FunctionDomain::Full, - |val, ctx| ToNumberImpl::eval_timestamp::(val, &ctx.func_ctx.tz), - ); - registry.register_1_arg::( - "to_unix_timestamp", - |_, _| FunctionDomain::Full, - |val, _| val.div_euclid(MICROS_PER_SEC), - ); - - registry.register_1_arg::( - "epoch", - |_, domain| { - FunctionDomain::Domain(SimpleDomain:: { - min: (domain.min as f64 / 1_000_000f64).into(), - max: (domain.max as f64 / 1_000_000f64).into(), - }) - }, - |val, _| (val as f64 / 1_000_000f64).into(), - ); - - registry.register_1_arg::( - "to_hour", - |_, _| FunctionDomain::Full, - |val, ctx| { - let datetime = val.to_timestamp(&ctx.func_ctx.tz); - datetime.hour() as u8 - }, - ); - registry.register_1_arg::( - "to_minute", - |_, _| FunctionDomain::Full, - |val, ctx| { - let datetime = val.to_timestamp(&ctx.func_ctx.tz); - datetime.minute() as u8 - }, - ); - registry.register_1_arg::( - "to_second", - |_, _| FunctionDomain::Full, - |val, ctx| { - let datetime = val.to_timestamp(&ctx.func_ctx.tz); - datetime.second() as u8 - }, - ); -} - -// Compute a correct date domain from a raw arithmetic range. -// `clamp_date` maps out-of-range values to DATE_MIN (non-monotonic), so naively -// clamping endpoints can produce a reversed domain. This function accounts for -// partial overlap with the valid date range. -fn clamp_date_domain(raw_min: i64, raw_max: i64) -> SimpleDomain { - if raw_min >= DATE_MIN as i64 && raw_max <= DATE_MAX as i64 { - SimpleDomain { - min: raw_min as i32, - max: raw_max as i32, - } - } else if raw_min > DATE_MAX as i64 || raw_max < DATE_MIN as i64 { - SimpleDomain { - min: DATE_MIN, - max: DATE_MIN, - } - } else { - SimpleDomain { - min: DATE_MIN, - max: raw_max.min(DATE_MAX as i64) as i32, - } - } -} - -fn clamp_timestamp_domain(raw_min: i64, raw_max: i64) -> SimpleDomain { - if raw_min >= TIMESTAMP_MIN && raw_max <= TIMESTAMP_MAX { - SimpleDomain { - min: raw_min, - max: raw_max, - } - } else if raw_min > TIMESTAMP_MAX || raw_max < TIMESTAMP_MIN { - SimpleDomain { - min: TIMESTAMP_MIN, - max: TIMESTAMP_MIN, - } - } else { - SimpleDomain { - min: TIMESTAMP_MIN, - max: raw_max.min(TIMESTAMP_MAX), - } - } -} - -fn register_timestamp_add_sub(registry: &mut FunctionRegistry) { - registry.register_passthrough_nullable_2_arg::( - "plus", - |_, lhs, rhs| { - (|| { - let lm: i64 = num_traits::cast::cast(lhs.max)?; - let ln: i64 = num_traits::cast::cast(lhs.min)?; - let rm = rhs.max; - let rn = rhs.min; - - let raw_min = ln.saturating_add(rn); - let raw_max = lm.saturating_add(rm); - Some(FunctionDomain::Domain(clamp_date_domain(raw_min, raw_max))) - })() - .unwrap_or(FunctionDomain::MayThrow) - }, - vectorize_with_builder_2_arg::(|a, b, output, _| { - output.push(clamp_date((a as i64).saturating_add(b))) - }), - ); - - registry.register_passthrough_nullable_2_arg::( - "plus", - |_, lhs, rhs| { - { - let lm = lhs.max; - let ln = lhs.min; - let rm = rhs.max; - let rn = rhs.min; - let raw_min = ln.saturating_add(rn); - let raw_max = lm.saturating_add(rm); - Some(FunctionDomain::Domain(clamp_timestamp_domain( - raw_min, raw_max, - ))) - } - .unwrap_or(FunctionDomain::MayThrow) - }, - vectorize_with_builder_2_arg::( - |a, b, output, _| { - let mut sum = a.saturating_add(b); - clamp_timestamp(&mut sum); - output.push(sum); - }, - ), - ); - - registry.register_passthrough_nullable_2_arg::( - "minus", - |_, lhs, rhs| { - (|| { - let lm: i64 = num_traits::cast::cast(lhs.max)?; - let ln: i64 = num_traits::cast::cast(lhs.min)?; - let rm = rhs.max; - let rn = rhs.min; - - let raw_min = ln.saturating_sub(rm); - let raw_max = lm.saturating_sub(rn); - Some(FunctionDomain::Domain(clamp_date_domain(raw_min, raw_max))) - })() - .unwrap_or(FunctionDomain::MayThrow) - }, - vectorize_with_builder_2_arg::(|a, b, output, _| { - output.push(clamp_date((a as i64).saturating_sub(b))); - }), - ); - - registry.register_passthrough_nullable_2_arg::( - "minus", - |_, lhs, rhs| { - { - let lm = lhs.max; - let ln = lhs.min; - let rm = rhs.max; - let rn = rhs.min; - let raw_min = ln.saturating_sub(rm); - let raw_max = lm.saturating_sub(rn); - Some(FunctionDomain::Domain(clamp_timestamp_domain( - raw_min, raw_max, - ))) - } - .unwrap_or(FunctionDomain::MayThrow) - }, - vectorize_with_builder_2_arg::( - |a, b, output, _| { - let mut minus = a.saturating_sub(b); - clamp_timestamp(&mut minus); - output.push(minus); - }, - ), - ); -} - -fn register_rounder_functions(registry: &mut FunctionRegistry) { - // timestamp -> timestamp - registry.register_1_arg::( - "to_start_of_second", - |_, _| FunctionDomain::Full, - |val, ctx| round_timestamp(val, &ctx.func_ctx.tz, Round::Second), - ); - registry.register_1_arg::( - "to_start_of_minute", - |_, _| FunctionDomain::Full, - |val, ctx| round_timestamp(val, &ctx.func_ctx.tz, Round::Minute), - ); - registry.register_1_arg::( - "to_start_of_five_minutes", - |_, _| FunctionDomain::Full, - |val, ctx| round_timestamp(val, &ctx.func_ctx.tz, Round::FiveMinutes), - ); - registry.register_1_arg::( - "to_start_of_ten_minutes", - |_, _| FunctionDomain::Full, - |val, ctx| round_timestamp(val, &ctx.func_ctx.tz, Round::TenMinutes), - ); - registry.register_1_arg::( - "to_start_of_fifteen_minutes", - |_, _| FunctionDomain::Full, - |val, ctx| round_timestamp(val, &ctx.func_ctx.tz, Round::FifteenMinutes), - ); - registry.register_1_arg::( - "to_start_of_hour", - |_, _| FunctionDomain::Full, - |val, ctx| round_timestamp(val, &ctx.func_ctx.tz, Round::Hour), - ); - registry.register_1_arg::( - "to_start_of_day", - |_, _| FunctionDomain::Full, - |val, ctx| round_timestamp(val, &ctx.func_ctx.tz, Round::Day), - ); - registry.register_1_arg::( - "time_slot", - |_, _| FunctionDomain::Full, - |val, ctx| round_timestamp(val, &ctx.func_ctx.tz, Round::TimeSlot), - ); - registry.register_passthrough_nullable_4_arg::( - "time_slice", - |_,_,_, _,_| FunctionDomain::Full, - vectorize_with_builder_4_arg::(|ts, slice_length, start_or_end, part,output, ctx| { - let start_or_end = StartOrEnd::from(start_or_end); - let part = TimePart::from(part); - if !part.date_part() { - ctx.set_error(output.len(), "Date type only support Year | Quarter | Month | Week | Day".to_string()); - output.push(0); - } else { - let mode = ctx.func_ctx.week_start; - let res = if mode == 0 { - time_slice_date(ts, slice_length, part, start_or_end, Weekday::Sunday) - } else { - time_slice_date(ts, slice_length, part, start_or_end, Weekday::Monday) - }; - output.push(res) - } - }), - ); - registry.register_passthrough_nullable_4_arg::( - "time_slice", - |_,_,_, _,_| FunctionDomain::Full, - vectorize_4_arg::(|ts, slice_length, start_or_end, part, ctx| { - let start_or_end = StartOrEnd::from(start_or_end); - let part = TimePart::from(part); - let mode = ctx.func_ctx.week_start; - if mode == 0 { - time_slice_timestamp(ts, slice_length, part, start_or_end, Weekday::Sunday, &ctx.func_ctx.tz) - } else { - time_slice_timestamp(ts, slice_length, part, start_or_end, Weekday::Monday, &ctx.func_ctx.tz) - } - }), - ); - - // date | timestamp -> date - registry.register_aliases("to_monday", &["to_start_of_iso_week"]); - rounder_functions_helper::(registry, "to_monday"); - rounder_functions_helper::(registry, "to_start_of_week"); - rounder_functions_helper::(registry, "to_start_of_month"); - rounder_functions_helper::(registry, "to_start_of_quarter"); - rounder_functions_helper::(registry, "to_start_of_year"); - rounder_functions_helper::(registry, "to_start_of_iso_year"); - rounder_functions_helper::(registry, "to_last_of_week"); - rounder_functions_helper::(registry, "to_last_of_month"); - rounder_functions_helper::(registry, "to_last_of_quarter"); - rounder_functions_helper::(registry, "to_last_of_year"); - rounder_functions_helper::(registry, "to_previous_monday"); - rounder_functions_helper::(registry, "to_previous_tuesday"); - rounder_functions_helper::(registry, "to_previous_wednesday"); - rounder_functions_helper::(registry, "to_previous_thursday"); - rounder_functions_helper::(registry, "to_previous_friday"); - rounder_functions_helper::(registry, "to_previous_saturday"); - rounder_functions_helper::(registry, "to_previous_sunday"); - rounder_functions_helper::(registry, "to_next_monday"); - rounder_functions_helper::(registry, "to_next_tuesday"); - rounder_functions_helper::(registry, "to_next_wednesday"); - rounder_functions_helper::(registry, "to_next_thursday"); - rounder_functions_helper::(registry, "to_next_friday"); - rounder_functions_helper::(registry, "to_next_saturday"); - rounder_functions_helper::(registry, "to_next_sunday"); - - registry.register_passthrough_nullable_2_arg::( - "to_start_of_week", - |_, _, _| FunctionDomain::Full, - vectorize_with_builder_2_arg::(|val, mode, output, ctx| { - if mode == 0 { - match DateRounder::eval_date::(val, &ctx.func_ctx.tz) { - Ok(t) => output.push(t), - Err(e) => { - ctx.set_error(output.len(), format!("cannot parse to type `Date`. {}", e)); - output.push(0); - } - } - } else { - match DateRounder::eval_date::(val, &ctx.func_ctx.tz) { - Ok(t) => output.push(t), - Err(e) => { - ctx.set_error(output.len(), format!("cannot parse to type `Date`. {}", e)); - output.push(0); - } - } - } - }), - ); - registry.register_passthrough_nullable_2_arg::( - "to_start_of_week", - |_, _, _| FunctionDomain::Full, - vectorize_2_arg::(|val, mode, ctx| { - if mode == 0 { - DateRounder::eval_timestamp::(val, &ctx.func_ctx.tz) - } else { - DateRounder::eval_timestamp::(val, &ctx.func_ctx.tz) - } - }), - ); -} - -fn rounder_functions_helper(registry: &mut FunctionRegistry, name: &str) -where T: DateToNumber { - registry.register_passthrough_nullable_1_arg::( - name, - |_, _| FunctionDomain::Full, - vectorize_with_builder_1_arg::(|val, output, ctx| { - match DateRounder::eval_date::(val, &ctx.func_ctx.tz) { - Ok(t) => output.push(t), - Err(e) => { - ctx.set_error(output.len(), format!("cannot parse to type `Date`. {}", e)); - output.push(0); - } - } - }), - ); - registry.register_1_arg::( - name, - |_, _| FunctionDomain::Full, - |val, ctx| DateRounder::eval_timestamp::(val, &ctx.func_ctx.tz), - ); -} - -fn prepare_format_string(format: &str, date_format_style: &str) -> String { - let processed_format = if date_format_style == "oracle" { - pg_format_to_strftime(format) - } else { - format.to_string() - }; - replace_time_format(&processed_format).to_string() -} - fn normalize_date_parts(year: i64, month: i64, day: i64) -> std::result::Result { let month_offset = month .checked_sub(1) diff --git a/src/query/functions/src/scalars/timestamp/src/interval.rs b/src/query/functions/src/scalars/timestamp/src/interval.rs index b39015dfb17f1..b46e41f838075 100644 --- a/src/query/functions/src/scalars/timestamp/src/interval.rs +++ b/src/query/functions/src/scalars/timestamp/src/interval.rs @@ -19,12 +19,6 @@ use databend_common_expression::EvalContext; use databend_common_expression::FunctionDomain; use databend_common_expression::FunctionRegistry; use databend_common_expression::Value; -use databend_common_expression::date_helper::DateConverter; -use databend_common_expression::date_helper::EvalDaysImpl; -use databend_common_expression::date_helper::EvalMonthsImpl; -use databend_common_expression::date_helper::calc_date_to_timestamp; -use databend_common_expression::date_helper::timestamp_tz_components_via_lut; -use databend_common_expression::date_helper::today_date; use databend_common_expression::error_to_null; use databend_common_expression::types::AccessType; use databend_common_expression::types::DateType; @@ -35,6 +29,7 @@ use databend_common_expression::types::StringType; use databend_common_expression::types::TimestampType; use databend_common_expression::types::interval::interval_to_string; use databend_common_expression::types::interval::string_to_interval; +use databend_common_expression::types::timestamp::timestamp_from_micros; use databend_common_expression::types::timestamp_tz::TimestampTzType; use databend_common_expression::vectorize_2_arg; use databend_common_expression::vectorize_with_builder_1_arg; @@ -46,6 +41,12 @@ use jiff::Zoned; use jiff::tz::Offset; use jiff::tz::TimeZone; +use crate::date_arithmetic::EvalDaysImpl; +use crate::date_arithmetic::EvalMonthsImpl; +use crate::date_arithmetic::timestamp_tz_components_via_lut; +use crate::date_conversion::calc_date_to_timestamp; +use crate::date_conversion::today_date; + pub fn register(registry: &mut FunctionRegistry) { // cast(xx AS interval) // to_interval(xx) @@ -333,8 +334,8 @@ fn register_interval_add_sub_mul(registry: &mut FunctionRegistry) { ) { output.push(calc_age_from_components(&c1, &c2, is_negative)); } else { - let t1 = t1.to_timestamp(tz); - let t2 = t2.to_timestamp(tz); + let t1 = timestamp_from_micros(t1, tz); + let t2 = timestamp_from_micros(t2, tz); output.push(calc_age(t1, t2, is_negative)); } }, @@ -435,8 +436,8 @@ fn register_interval_add_sub_mul(registry: &mut FunctionRegistry) { ) { output.push(calc_age_from_components(&c1, &c2, is_negative)); } else { - let mut t1 = t1.to_timestamp(tz); - let mut t2 = t2_val.to_timestamp(tz); + let mut t1 = timestamp_from_micros(t1, tz); + let mut t2 = timestamp_from_micros(t2_val, tz); if t1 < t2 { std::mem::swap(&mut t1, &mut t2); @@ -597,7 +598,7 @@ fn eval_date_plus( output: &mut Vec, ctx: &mut EvalContext, ) { - match apply_interval_to_date(date, interval, &ctx.func_ctx.tz, true) { + match apply_interval_to_date(date, interval, true) { Ok(result) => output.push(result), Err(err) => { ctx.set_error(output.len(), err); @@ -612,7 +613,7 @@ fn eval_date_minus( output: &mut Vec, ctx: &mut EvalContext, ) { - match apply_interval_to_date(date, interval, &ctx.func_ctx.tz, false) { + match apply_interval_to_date(date, interval, false) { Ok(result) => output.push(result), Err(err) => { ctx.set_error(output.len(), err); @@ -624,7 +625,6 @@ fn eval_date_minus( fn apply_interval_to_date( mut date: i32, interval: months_days_micros, - tz: &TimeZone, is_addition: bool, ) -> std::result::Result { if interval.microseconds() != 0 { @@ -643,7 +643,7 @@ fn apply_interval_to_date( date = EvalDaysImpl::eval_date(date, days); } if months != 0 { - date = EvalMonthsImpl::eval_date(date, tz, months, false)?; + date = EvalMonthsImpl::eval_date(date, months, false)?; } Ok(date) diff --git a/src/query/functions/src/scalars/timestamp/src/lib.rs b/src/query/functions/src/scalars/timestamp/src/lib.rs index b316e75941ac9..02479571d876f 100644 --- a/src/query/functions/src/scalars/timestamp/src/lib.rs +++ b/src/query/functions/src/scalars/timestamp/src/lib.rs @@ -27,7 +27,15 @@ #![feature(str_internals)] #![feature(fmt_internals)] #![feature(formatting_options)] +#![feature(int_roundings)] extern crate core; +pub mod date_arithmetic; +pub mod date_conversion; +mod date_extract; +mod date_format; +mod date_round; +mod date_time_slice; + pub mod datetime; pub mod interval; diff --git a/src/query/pipeline/transforms/src/processors/transforms/window/transform_window.rs b/src/query/pipeline/transforms/src/processors/transforms/window/transform_window.rs index 35fa89e9d7345..954581b3e9e86 100644 --- a/src/query/pipeline/transforms/src/processors/transforms/window/transform_window.rs +++ b/src/query/pipeline/transforms/src/processors/transforms/window/transform_window.rs @@ -32,8 +32,6 @@ use databend_common_expression::Scalar; use databend_common_expression::ScalarRef; use databend_common_expression::SortColumnDescription; use databend_common_expression::arithmetics_type::ResultTypeOfUnary; -use databend_common_expression::date_helper::EvalMonthsImpl; -use databend_common_expression::date_helper::calc_date_to_timestamp; use databend_common_expression::types::AccessType; use databend_common_expression::types::DataType; use databend_common_expression::types::DateType; @@ -44,6 +42,8 @@ use databend_common_expression::types::NumberScalar; use databend_common_expression::types::NumberType; use databend_common_expression::types::TimestampType; use databend_common_expression::with_number_mapped_type; +use databend_common_functions::scalars::dt_func::date_arithmetic::EvalMonthsImpl; +use databend_common_functions::scalars::dt_func::date_conversion::calc_date_to_timestamp; use databend_common_pipeline::core::Event; use databend_common_pipeline::core::InputPort; use databend_common_pipeline::core::OutputPort; diff --git a/src/query/service/src/pipelines/processors/transforms/transform_dictionary.rs b/src/query/service/src/pipelines/processors/transforms/transform_dictionary.rs index 140186ee9c22a..da171455b056c 100644 --- a/src/query/service/src/pipelines/processors/transforms/transform_dictionary.rs +++ b/src/query/service/src/pipelines/processors/transforms/transform_dictionary.rs @@ -517,7 +517,7 @@ impl DictionaryOperator { fn format_key(&self, key: ScalarRef<'_>) -> String { match key { ScalarRef::String(s) => format!("'{}'", s.replace("'", "\\'")), - ScalarRef::Date(d) => format!("{}", date_to_string(d as i64, &TimeZone::UTC)), + ScalarRef::Date(d) => format!("{}", date_to_string(d as i64)), ScalarRef::Timestamp(t) => { format!("{}", timestamp_to_string(t, &TimeZone::UTC)) } diff --git a/src/query/service/tests/it/servers/http/json_block.rs b/src/query/service/tests/it/servers/http/json_block.rs index becc5d215382a..8035bedfe52ea 100644 --- a/src/query/service/tests/it/servers/http/json_block.rs +++ b/src/query/service/tests/it/servers/http/json_block.rs @@ -150,7 +150,7 @@ fn test_driver_mode_data_block() -> anyhow::Result<()> { assert_eq!( serde_json::to_value(&display_serializer)?, serde_json::json!([[ - date_to_string(1, &display.jiff_timezone).to_string(), + date_to_string(1), timestamp_to_string(1_000_000, &display.jiff_timezone).to_string(), ts_tz.to_string(), "eA==" diff --git a/src/query/task_support/src/system_tables/task_history.rs b/src/query/task_support/src/system_tables/task_history.rs index 1bf6d92e12215..cb0264220a295 100644 --- a/src/query/task_support/src/system_tables/task_history.rs +++ b/src/query/task_support/src/system_tables/task_history.rs @@ -28,7 +28,6 @@ use databend_common_expression::DataBlock; use databend_common_expression::FromData; use databend_common_expression::FunctionContext; use databend_common_expression::Scalar; -use databend_common_expression::date_helper::DateConverter; use databend_common_expression::expr::*; use databend_common_expression::filter_helper::FilterHelpers; use databend_common_expression::infer_table_schema; @@ -39,6 +38,7 @@ use databend_common_expression::types::StringType; use databend_common_expression::types::TimestampType; use databend_common_expression::types::UInt64Type; use databend_common_expression::types::VariantType; +use databend_common_expression::types::timestamp::timestamp_from_micros; use databend_common_functions::BUILTIN_FUNCTIONS; use databend_common_meta_app::schema::TableIdent; use databend_common_meta_app::schema::TableInfo; @@ -162,16 +162,22 @@ impl AsyncSystemTable for TaskHistoryTable { if col_name == "scheduled_time" && let Scalar::Timestamp(s) = scalar { - scheduled_time_end = - Some(s.to_timestamp(&TimeZone::UTC).timestamp().to_string()); + scheduled_time_end = Some( + timestamp_from_micros(*s, &TimeZone::UTC) + .timestamp() + .to_string(), + ); } }); find_gt_filter(&expr, &mut |col_name, scalar| { if col_name == "scheduled_time" && let Scalar::Timestamp(s) = scalar { - scheduled_time_start = - Some(s.to_timestamp(&TimeZone::UTC).timestamp().to_string()); + scheduled_time_start = Some( + timestamp_from_micros(*s, &TimeZone::UTC) + .timestamp() + .to_string(), + ); } }); } diff --git a/src/query/task_support/src/table_functions/task_history.rs b/src/query/task_support/src/table_functions/task_history.rs index 55833a887e9e7..2f49d7ea0bf33 100644 --- a/src/query/task_support/src/table_functions/task_history.rs +++ b/src/query/task_support/src/table_functions/task_history.rs @@ -32,8 +32,9 @@ use databend_common_exception::ErrorCode; use databend_common_exception::Result; use databend_common_expression::DataBlock; use databend_common_expression::Scalar; -use databend_common_expression::date_helper::DateConverter; use databend_common_expression::infer_table_schema; +use databend_common_expression::types::date::date_from_days; +use databend_common_expression::types::timestamp::timestamp_from_micros; use databend_common_meta_app::schema::TableIdent; use databend_common_meta_app::schema::TableInfo; use databend_common_meta_app::schema::TableMeta; @@ -253,26 +254,16 @@ pub(crate) struct TableHistoryArgsParsed { } fn parse_date_or_timestamp(v: &Scalar) -> Option { - if v.as_timestamp().is_some() { - Some( - v.as_timestamp() - .map(|s| s.to_timestamp(&TimeZone::UTC).to_string()) - .unwrap(), - ) - } else if v.as_date().is_some() { - Some( - v.as_date() - .map(|s| { - s.to_date(&TimeZone::UTC) - .at(0, 0, 0, 0) - .in_tz("UTC") - .unwrap() - .to_string() - }) - .unwrap(), - ) - } else { - None + match v { + Scalar::Timestamp(ts) => Some(timestamp_from_micros(*ts, &TimeZone::UTC).to_string()), + Scalar::Date(date) => Some( + date_from_days(*date) + .at(0, 0, 0, 0) + .in_tz("UTC") + .unwrap() // FIXME: maybe overflow + .to_string(), + ), + _ => None, } }