Skip to content

dayname scalar function #1046

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jun 10, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
143 changes: 143 additions & 0 deletions crates/embucket-functions/src/datetime/dayname.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
use chrono::{DateTime, Datelike, NaiveDateTime, Utc};
use datafusion::arrow::array::{Array, StringBuilder};
use datafusion::arrow::datatypes::{DataType, TimeUnit};
use datafusion::error::Result as DFResult;
use datafusion::logical_expr::TypeSignature::{Coercible, Exact};
use datafusion::logical_expr::{Coercion, ColumnarValue, TypeSignatureClass};
use datafusion_common::{ScalarValue, exec_err};
use datafusion_expr::{ScalarFunctionArgs, ScalarUDFImpl, Signature, Volatility};
use std::any::Any;
use std::sync::Arc;

/// `DAYNAME` SQL function
///
/// Extracts the three-letter day-of-week name from the specified date or timestamp.
///
/// Syntax: `DAYNAME(<date_or_timestamp>)`
///
/// Arguments:
/// - `date_or_timestamp`: A date or timestamp value.
///
/// Example: `SELECT dayname('2025-05-08T23:39:20.123-07:00'::timestamp) AS value;`
///
/// Returns:
/// - Returns a string representing the three-letter day-of-week name (e.g., "Mon", "Tue", "Wed", etc.).
#[derive(Debug)]
pub struct DayNameFunc {
signature: Signature,
}

impl Default for DayNameFunc {
fn default() -> Self {
Self::new()
}
}

impl DayNameFunc {
#[must_use]
pub fn new() -> Self {
Self {
signature: Signature::one_of(
vec![
Coercible(vec![Coercion::new_exact(TypeSignatureClass::Timestamp)]),
Exact(vec![DataType::Date32]),
Exact(vec![DataType::Date64]),
],
Volatility::Immutable,
),
}
}
}

impl ScalarUDFImpl for DayNameFunc {
fn as_any(&self) -> &dyn Any {
self
}

fn name(&self) -> &'static str {
"dayname"
}

fn signature(&self) -> &Signature {
&self.signature
}

fn return_type(&self, _arg_types: &[DataType]) -> DFResult<DataType> {
Ok(DataType::Utf8)
}

#[allow(
clippy::unwrap_used,
clippy::as_conversions,
clippy::cast_possible_truncation
)]
fn invoke_with_args(&self, args: ScalarFunctionArgs) -> DFResult<ColumnarValue> {
let ScalarFunctionArgs { args, .. } = args;

let arr = match args[0].clone() {
ColumnarValue::Array(arr) => arr,
ColumnarValue::Scalar(v) => v.to_array()?,
};

let mut res = StringBuilder::with_capacity(arr.len(), 1024);
for i in 0..arr.len() {
let v = ScalarValue::try_from_array(&arr, i)?
.cast_to(&DataType::Timestamp(TimeUnit::Nanosecond, None))?;
let ScalarValue::TimestampNanosecond(Some(ts), None) = v else {
return exec_err!("First argument must be a timestamp with nanosecond precision");
};
let naive = DateTime::<Utc>::from_timestamp_nanos(ts).naive_utc();
res.append_value(dayname(&naive));
}

let res = res.finish();
Ok(if res.len() == 1 {
ColumnarValue::Scalar(ScalarValue::try_from_array(&res, 0)?)
} else {
ColumnarValue::Array(Arc::new(res))
})
}
}

fn dayname(date: &NaiveDateTime) -> String {
match date.weekday() {
chrono::Weekday::Mon => "Mon".to_string(),
chrono::Weekday::Tue => "Tue".to_string(),
chrono::Weekday::Wed => "Wed".to_string(),
chrono::Weekday::Thu => "Thu".to_string(),
chrono::Weekday::Fri => "Fri".to_string(),
chrono::Weekday::Sat => "Sat".to_string(),
chrono::Weekday::Sun => "Sun".to_string(),
}
}

crate::macros::make_udf_function!(DayNameFunc);
#[cfg(test)]
mod tests {
use super::*;
use datafusion::prelude::SessionContext;
use datafusion_common::assert_batches_eq;
use datafusion_expr::ScalarUDF;

#[tokio::test]
async fn test_basic() -> DFResult<()> {
let ctx = SessionContext::new();
ctx.register_udf(ScalarUDF::from(DayNameFunc::new()));

let sql = "SELECT dayname('2025-05-08T23:39:20.123-07:00'::date) AS value;";
let result = ctx.sql(sql).await?.collect().await?;

assert_batches_eq!(
&[
"+-------+",
"| value |",
"+-------+",
"| Fri |",
"+-------+",
],
&result
);

Ok(())
}
}
1 change: 1 addition & 0 deletions crates/embucket-functions/src/datetime/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ pub mod convert_timezone;
pub mod date_add;
pub mod date_diff;
pub mod date_from_parts;
pub mod dayname;
pub mod last_day;
pub mod next_day;
pub mod previous_day;
Expand Down
2 changes: 1 addition & 1 deletion crates/embucket-functions/src/datetime/previous_day.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ impl ScalarUDFImpl for PreviousDayFunc {
fn prev_day(ndt: &NaiveDateTime, dow: &str) -> DFResult<NaiveDateTime> {
let target_dow = match dow.chars().take(2).collect::<String>().as_str() {
"mo" => Weekday::Mon,
"tu" => Weekday::Thu,
"tu" => Weekday::Tue,
"we" => Weekday::Wed,
"th" => Weekday::Thu,
"fr" => Weekday::Fri,
Expand Down
1 change: 1 addition & 0 deletions crates/embucket-functions/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ pub fn register_udfs(registry: &mut dyn FunctionRegistry) -> Result<()> {
datetime::date_from_parts::get_udf(),
datetime::last_day::get_udf(),
datetime::add_months::get_udf(),
datetime::dayname::get_udf(),
datetime::previous_day::get_udf(),
datetime::next_day::get_udf(),
conditional::booland::get_udf(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -841,12 +841,6 @@ pub const DATETIME_FUNCTIONS: &[(&str, FunctionInfo)] = &[
)
.with_docs("https://docs.snowflake.com/en/sql-reference/functions/year")
),
("DAYNAME", FunctionInfo::new(
"DAYNAME",
"Extracts the three-letter day-of-week name from the specified date or timestamp."
)
.with_docs("https://docs.snowflake.com/en/sql-reference/functions/dayname")
),
("DAYOFMONTH", FunctionInfo::new(
"DAYOFMONTH",
"Extracts the corresponding date part from a date or timestamp."
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,8 @@ dateadd
datediff
datefromparts
datepart
datetrunc
datetrunc
dayname
decode
degrees
dense_rank
Expand Down