Skip to content

Commit

Permalink
add toStartOfWeek
Browse files Browse the repository at this point in the history
  • Loading branch information
dust1 committed Sep 23, 2021
1 parent 3df848d commit 35c9b09
Show file tree
Hide file tree
Showing 6 changed files with 242 additions and 2 deletions.
3 changes: 3 additions & 0 deletions common/functions/src/scalars/dates/date.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ use super::RoundFunction;
use super::ToStartOfISOYearFunction;
use super::ToStartOfMonthFunction;
use super::ToStartOfQuarterFunction;
use super::ToStartOfWeekFunction;
use super::ToStartOfYearFunction;
use super::ToYYYYMMDDFunction;
use super::ToYYYYMMDDhhmmssFunction;
Expand Down Expand Up @@ -53,7 +54,9 @@ impl DateFunction {
"toStartOfQuarter".into(),
ToStartOfQuarterFunction::try_create,
);
map.insert("toStartOfWeek".into(), ToStartOfWeekFunction::try_create);
map.insert("toStartOfMonth".into(), ToStartOfMonthFunction::try_create);

// rounders
{
map.insert("toStartOfSecond".into(), |display_name| {
Expand Down
4 changes: 2 additions & 2 deletions common/functions/src/scalars/dates/date_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,10 @@ fn test_round_function() -> Result<()> {
fn test_toStartOf_function() -> Result<()> {
let test = Test {
name: "test-timeSlot-now",
display: "toStartOfQuarter()",
display: "toStartOfWeek()",
nullable: false,
columns: vec![Series::new(vec![1631705259u32]).into()],
func: ToStartOfQuarterFunction::try_create("toStartOfQuarter"),
func: ToStartOfQuarterFunction::try_create("toStartOfWeek"),
expect: Series::new(vec![18809u32]),
error: "",
};
Expand Down
2 changes: 2 additions & 0 deletions common/functions/src/scalars/dates/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ mod now;
mod number_function;
mod round_function;
mod simple_date;
mod week_date;

pub use date::DateFunction;
pub use number_function::ToStartOfISOYearFunction;
Expand All @@ -35,3 +36,4 @@ pub use round_function::RoundFunction;
pub use simple_date::TodayFunction;
pub use simple_date::TomorrowFunction;
pub use simple_date::YesterdayFunction;
pub use week_date::ToStartOfWeekFunction;
190 changes: 190 additions & 0 deletions common/functions/src/scalars/dates/week_date.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
// Copyright 2020 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::fmt;
use std::marker::PhantomData;
use std::ops::Sub;

use common_datavalues::chrono::DateTime;
use common_datavalues::chrono::Datelike;
use common_datavalues::chrono::Duration;
use common_datavalues::chrono::TimeZone;
use common_datavalues::chrono::Utc;
use common_datavalues::prelude::*;
use common_exception::ErrorCode;
use common_exception::Result;

use crate::scalars::Function;

#[derive(Clone, Debug)]
pub struct WeekFunction<T, R> {
display_name: String,
t: PhantomData<T>,
r: PhantomData<R>,
}

pub trait WeekResultFunction<R> {
fn return_type() -> Result<DataType>;
fn to_number(_value: DateTime<Utc>, mode: Option<u64>) -> R;
fn to_constant_value(_value: DateTime<Utc>, mode: Option<u64>) -> DataValue;
}

#[derive(Clone)]
pub struct ToStartOfWeek;

impl WeekResultFunction<u32> for ToStartOfWeek {
fn return_type() -> Result<DataType> {
Ok(DataType::Date16)
}
fn to_number(value: DateTime<Utc>, mode: Option<u64>) -> u32 {
let week_mode = mode.unwrap_or(0);
let mut weekday = value.weekday().number_from_monday();
if (week_mode & 1) == 0 {
weekday = value.weekday().number_from_sunday();
}
weekday -= 1;
let duration = Duration::days(weekday as i64);
let result = value.sub(duration);
get_day(result)
}

fn to_constant_value(value: DateTime<Utc>, mode: Option<u64>) -> DataValue {
DataValue::UInt16(Some(Self::to_number(value, mode) as u16))
}
}

impl<T, R> WeekFunction<T, R>
where
T: WeekResultFunction<R> + Clone + Sync + Send + 'static,
R: DFPrimitiveType + Clone,
DFPrimitiveArray<R>: IntoSeries,
{
pub fn try_create(display_name: &str) -> Result<Box<dyn Function>> {
Ok(Box::new(WeekFunction::<T, R> {
display_name: display_name.to_string(),
t: PhantomData,
r: PhantomData,
}))
}
}

impl<T, R> Function for WeekFunction<T, R>
where
T: WeekResultFunction<R> + Clone + Sync + Send,
R: DFPrimitiveType + Clone,
DFPrimitiveArray<R>: IntoSeries,
{
fn name(&self) -> &str {
self.display_name.as_str()
}

fn return_type(&self, _args: &[DataType]) -> Result<DataType> {
T::return_type()
}

fn variadic_arguments(&self) -> Option<(usize, usize)> {
Some((1, 2))
}

fn nullable(&self, _input_schema: &DataSchema) -> Result<bool> {
Ok(false)
}

fn eval(&self, columns: &DataColumnsWithField, input_rows: usize) -> Result<DataColumn> {
let data_type = columns[0].data_type();
let mut mode: Option<u64> = None;
if columns.len() == 2 && !columns[1].column().is_empty() {
let week_mode = columns[1].column().to_values()?[0].clone().as_u64()?;
if !(0..=9).contains(&week_mode) {
return Err(ErrorCode::BadArguments(format!(
"The parameter:{} range is abnormal, it should be between 0-9",
week_mode
)));
}
mode = Some(week_mode);
}
let number_array: DataColumn = match data_type {
DataType::Date16 => {
if let DataColumn::Constant(v, _) = columns[0].column() {
let date_time = Utc.timestamp(v.as_u64()? as i64 * 24 * 3600, 0_u32);
let constant_result = T::to_constant_value(date_time, mode);
Ok(DataColumn::Constant(constant_result, input_rows))
} else {
let result: DFPrimitiveArray<R> = columns[0].column()
.to_array()?
.u16()?
.apply_cast_numeric(|v| {
let date_time = Utc.timestamp(v as i64 * 24 * 3600, 0_u32);
T::to_number(date_time, mode)
}
);
Ok(result.into())
}
},
DataType::Date32 => {
if let DataColumn::Constant(v, _) = columns[0].column() {
let date_time = Utc.timestamp(v.as_u64()? as i64 * 24 * 3600, 0_u32);
let constant_result = T::to_constant_value(date_time, mode);
Ok(DataColumn::Constant(constant_result, input_rows))
} else {
let result = columns[0].column()
.to_array()?
.u32()?
.apply_cast_numeric(|v| {
let date_time = Utc.timestamp(v as i64 * 24 * 3600, 0_u32);
T::to_number(date_time, mode)
}
);
Ok(result.into())
}
},
DataType::DateTime32(_) => {
if let DataColumn::Constant(v, _) = columns[0].column() {
let date_time = Utc.timestamp(v.as_u64()? as i64, 0_u32);
let constant_result = T::to_constant_value(date_time, mode);
Ok(DataColumn::Constant(constant_result, input_rows))
} else {
let result = columns[0].column()
.to_array()?
.u32()?
.apply_cast_numeric(|v| {
let date_time = Utc.timestamp(v as i64, 0_u32);
T::to_number(date_time, mode)
}
);
Ok(result.into())
}
},
other => Result::Err(ErrorCode::IllegalDataType(format!(
"Illegal type {:?} of argument of function {}.Should be a date16/data32 or a dateTime32",
other,
self.name()))),
}?;
Ok(number_array)
}
}

impl<T, R> fmt::Display for WeekFunction<T, R> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}()", self.display_name)
}
}

fn get_day(date: DateTime<Utc>) -> u32 {
let start: DateTime<Utc> = Utc.ymd(1970, 1, 1).and_hms(0, 0, 0);
let duration = date.signed_duration_since(start);
duration.num_days() as u32
}

pub type ToStartOfWeekFunction = WeekFunction<ToStartOfWeek, u32>;
22 changes: 22 additions & 0 deletions tests/suites/0_stateless/02_0012_function_datetimes.result
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,26 @@
2021-07-01
2021-04-01
2021-09-01
2021-09-19
2021-09-19
2021-09-20
2021-09-19
2021-09-20
2021-09-19
2021-09-20
2021-09-19
2021-09-20
2021-09-19
2021-09-20
2021-05-16
2021-05-16
2021-05-17
2021-05-16
2021-05-17
2021-05-16
2021-05-17
2021-05-16
2021-05-17
2021-05-16
2021-05-17
===toStartOf===
23 changes: 23 additions & 0 deletions tests/suites/0_stateless/02_0012_function_datetimes.sql
Original file line number Diff line number Diff line change
Expand Up @@ -58,4 +58,27 @@ select toStartOfMonth(toDateTime(1631705259));
select toStartOfQuarter(toDate(18885));
select toStartOfQuarter(toDate(18762));
select toStartOfMonth(toDate(18885));

select toStartOfWeek(toDateTime(1632397739));
select toStartOfWeek(toDateTime(1632397739), 0);
select toStartOfWeek(toDateTime(1632397739), 1);
select toStartOfWeek(toDateTime(1632397739), 2);
select toStartOfWeek(toDateTime(1632397739), 3);
select toStartOfWeek(toDateTime(1632397739), 4);
select toStartOfWeek(toDateTime(1632397739), 5);
select toStartOfWeek(toDateTime(1632397739), 6);
select toStartOfWeek(toDateTime(1632397739), 7);
select toStartOfWeek(toDateTime(1632397739), 8);
select toStartOfWeek(toDateTime(1632397739), 9);
select toStartOfWeek(toDate(18769));
select toStartOfWeek(toDate(18769), 0);
select toStartOfWeek(toDate(18769), 1);
select toStartOfWeek(toDate(18769), 2);
select toStartOfWeek(toDate(18769), 3);
select toStartOfWeek(toDate(18769), 4);
select toStartOfWeek(toDate(18769), 5);
select toStartOfWeek(toDate(18769), 6);
select toStartOfWeek(toDate(18769), 7);
select toStartOfWeek(toDate(18769), 8);
select toStartOfWeek(toDate(18769), 9);
select '===toStartOf===';

0 comments on commit 35c9b09

Please sign in to comment.