From 1da480c0f24603f0e3e67a6e21dd7832e2bca976 Mon Sep 17 00:00:00 2001 From: Kazantsev Maksim Date: Mon, 9 Mar 2026 21:29:50 +0400 Subject: [PATCH 01/20] Spark function: dayname --- .../spark/src/function/datetime/dayname.rs | 126 ++++++++++++++++++ datafusion/spark/src/function/datetime/mod.rs | 8 ++ .../test_files/spark/datetime/dayname.slt | 56 ++++++++ 3 files changed, 190 insertions(+) create mode 100644 datafusion/spark/src/function/datetime/dayname.rs create mode 100644 datafusion/sqllogictest/test_files/spark/datetime/dayname.slt diff --git a/datafusion/spark/src/function/datetime/dayname.rs b/datafusion/spark/src/function/datetime/dayname.rs new file mode 100644 index 0000000000000..a5278f70175a2 --- /dev/null +++ b/datafusion/spark/src/function/datetime/dayname.rs @@ -0,0 +1,126 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 arrow::array::{ArrayRef, AsArray, StringArray}; +use arrow::datatypes::{DataType, Date32Type, Field, FieldRef}; +use chrono::Datelike; +use datafusion::logical_expr::{ColumnarValue, Signature, Volatility}; +use datafusion_common::utils::take_function_args; +use datafusion_common::{Result, ScalarValue, internal_err}; +use datafusion_expr::{ReturnFieldArgs, ScalarFunctionArgs, ScalarUDFImpl}; +use std::any::Any; +use std::sync::Arc; + +#[derive(Debug, PartialEq, Eq, Hash)] +pub struct SparkDayName { + signature: Signature, +} + +impl Default for SparkDayName { + fn default() -> Self { + Self::new() + } +} + +impl SparkDayName { + pub fn new() -> Self { + Self { + signature: Signature::exact(vec![DataType::Date32], Volatility::Immutable), + } + } +} + +impl ScalarUDFImpl for SparkDayName { + fn as_any(&self) -> &dyn Any { + self + } + + fn name(&self) -> &str { + "dayname" + } + + fn signature(&self) -> &Signature { + &self.signature + } + + fn return_type(&self, _arg_types: &[DataType]) -> Result { + internal_err!("return_field_from_args should be used instead") + } + + fn return_field_from_args(&self, args: ReturnFieldArgs) -> Result { + Ok(Arc::new(Field::new( + self.name(), + DataType::Utf8, + args.arg_fields[0].is_nullable(), + ))) + } + + fn invoke_with_args(&self, args: ScalarFunctionArgs) -> Result { + let [arg] = take_function_args("dayname", args.args)?; + match arg { + ColumnarValue::Scalar(ScalarValue::Date32(days)) => { + if let Some(days) = days { + Ok(ColumnarValue::Scalar(ScalarValue::Utf8(Some( + spark_day_name(days), + )))) + } else { + Ok(ColumnarValue::Scalar(ScalarValue::Utf8(None))) + } + } + ColumnarValue::Array(array) => { + let result = match array.data_type() { + DataType::Date32 => { + let result: StringArray = array + .as_primitive::() + .iter() + .map(|x| x.map(spark_day_name)) + .collect(); + Ok(Arc::new(result) as ArrayRef) + } + other => { + internal_err!( + "Unsupported data type {other:?} for Spark function `dayname`" + ) + } + }?; + Ok(ColumnarValue::Array(result)) + } + other => { + internal_err!("Unsupported arg {other:?} for Spark function `dayname`") + } + } + } +} + +fn spark_day_name(days: i32) -> String { + let weekday = Date32Type::to_naive_date_opt(days).unwrap().weekday(); + let display_name = get_display_name(weekday.num_days_from_monday()); + display_name.unwrap() +} + +fn get_display_name(day: u32) -> Option { + match day { + 0 => Some(String::from("Mon")), + 1 => Some(String::from("Tue")), + 2 => Some(String::from("Wed")), + 3 => Some(String::from("Thu")), + 4 => Some(String::from("Fri")), + 5 => Some(String::from("Sat")), + 6 => Some(String::from("Sun")), + _ => None, + } +} diff --git a/datafusion/spark/src/function/datetime/mod.rs b/datafusion/spark/src/function/datetime/mod.rs index 3133ed7337f25..53127138600ff 100644 --- a/datafusion/spark/src/function/datetime/mod.rs +++ b/datafusion/spark/src/function/datetime/mod.rs @@ -21,6 +21,7 @@ pub mod date_diff; pub mod date_part; pub mod date_sub; pub mod date_trunc; +pub mod dayname; pub mod extract; pub mod from_utc_timestamp; pub mod last_day; @@ -72,6 +73,7 @@ make_udf_function!( unix_seconds, unix::SparkUnixTimestamp::seconds ); +make_udf_function!(dayname::SparkDayName, dayname); pub mod expr_fn { use datafusion_functions::export_functions; @@ -179,6 +181,11 @@ pub mod expr_fn { "Returns the number of seconds since epoch (1970-01-01 00:00:00 UTC) for the given timestamp `ts`.", ts )); + export_functions!(( + dayname, + "Returns the three-letter abbreviated day name from the given date.", + arg1 + )); } pub fn functions() -> Vec> { @@ -204,5 +211,6 @@ pub fn functions() -> Vec> { unix_micros(), unix_millis(), unix_seconds(), + dayname(), ] } diff --git a/datafusion/sqllogictest/test_files/spark/datetime/dayname.slt b/datafusion/sqllogictest/test_files/spark/datetime/dayname.slt new file mode 100644 index 0000000000000..3647400522c40 --- /dev/null +++ b/datafusion/sqllogictest/test_files/spark/datetime/dayname.slt @@ -0,0 +1,56 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +query T +SELECT dayname('2008-02-20'::DATE); +---- +Wed + +query T +SELECT dayname(NULL::DATE); +---- +NULL + +query T +SELECT dayname('2026-03-09'::DATE); +---- +Mon + +query T +SELECT dayname('2026-03-08'::DATE); +---- +Sun + +query T +SELECT dayname('1948-08-11'::DATE); +---- +Tue + +query T +SELECT dayname('1987-11-13'::DATE); +---- +Fri + +query T +SELECT dayname('2000-07-27'::DATE); +---- +Thu + +query T +SELECT dayname('2010-04-24'::DATE); +---- +Sat From 412c9b5a21be694fd4625bd9c602d669c92bc818 Mon Sep 17 00:00:00 2001 From: Kazantsev Maksim Date: Mon, 9 Mar 2026 21:42:07 +0400 Subject: [PATCH 02/20] Fix tests --- datafusion/sqllogictest/test_files/spark/datetime/dayname.slt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/datafusion/sqllogictest/test_files/spark/datetime/dayname.slt b/datafusion/sqllogictest/test_files/spark/datetime/dayname.slt index 3647400522c40..fc7d6cb7fe063 100644 --- a/datafusion/sqllogictest/test_files/spark/datetime/dayname.slt +++ b/datafusion/sqllogictest/test_files/spark/datetime/dayname.slt @@ -36,7 +36,7 @@ SELECT dayname('2026-03-08'::DATE); Sun query T -SELECT dayname('1948-08-11'::DATE); +SELECT dayname('1948-08-10'::DATE); ---- Tue From 7500ee611efd28ac976dd4f167e03f8fb9331503 Mon Sep 17 00:00:00 2001 From: Kazantsev Maksim Date: Fri, 13 Mar 2026 22:24:07 +0400 Subject: [PATCH 03/20] Fix PR issues --- .../spark/src/function/datetime/dayname.rs | 33 +++++++++++++------ .../test_files/spark/datetime/dayname.slt | 15 +++++++++ 2 files changed, 38 insertions(+), 10 deletions(-) diff --git a/datafusion/spark/src/function/datetime/dayname.rs b/datafusion/spark/src/function/datetime/dayname.rs index a5278f70175a2..70b6972ab16f6 100644 --- a/datafusion/spark/src/function/datetime/dayname.rs +++ b/datafusion/spark/src/function/datetime/dayname.rs @@ -18,9 +18,12 @@ use arrow::array::{ArrayRef, AsArray, StringArray}; use arrow::datatypes::{DataType, Date32Type, Field, FieldRef}; use chrono::Datelike; -use datafusion::logical_expr::{ColumnarValue, Signature, Volatility}; +use datafusion::logical_expr::{ + Coercion, ColumnarValue, Signature, TypeSignatureClass, Volatility, +}; +use datafusion_common::types::{NativeType, logical_date, logical_string}; use datafusion_common::utils::take_function_args; -use datafusion_common::{Result, ScalarValue, internal_err}; +use datafusion_common::{Result, ScalarValue, exec_datafusion_err, internal_err}; use datafusion_expr::{ReturnFieldArgs, ScalarFunctionArgs, ScalarUDFImpl}; use std::any::Any; use std::sync::Arc; @@ -39,7 +42,17 @@ impl Default for SparkDayName { impl SparkDayName { pub fn new() -> Self { Self { - signature: Signature::exact(vec![DataType::Date32], Volatility::Immutable), + signature: Signature::coercible( + vec![Coercion::new_implicit( + TypeSignatureClass::Native(logical_date()), + vec![ + TypeSignatureClass::Native(logical_string()), + TypeSignatureClass::Timestamp, + ], + NativeType::Date, + )], + Volatility::Immutable, + ), } } } @@ -74,8 +87,8 @@ impl ScalarUDFImpl for SparkDayName { match arg { ColumnarValue::Scalar(ScalarValue::Date32(days)) => { if let Some(days) = days { - Ok(ColumnarValue::Scalar(ScalarValue::Utf8(Some( - spark_day_name(days), + Ok(ColumnarValue::Scalar(ScalarValue::Utf8(spark_day_name( + days, )))) } else { Ok(ColumnarValue::Scalar(ScalarValue::Utf8(None))) @@ -87,7 +100,7 @@ impl ScalarUDFImpl for SparkDayName { let result: StringArray = array .as_primitive::() .iter() - .map(|x| x.map(spark_day_name)) + .map(|x| x.and_then(spark_day_name)) .collect(); Ok(Arc::new(result) as ArrayRef) } @@ -106,10 +119,10 @@ impl ScalarUDFImpl for SparkDayName { } } -fn spark_day_name(days: i32) -> String { - let weekday = Date32Type::to_naive_date_opt(days).unwrap().weekday(); - let display_name = get_display_name(weekday.num_days_from_monday()); - display_name.unwrap() +fn spark_day_name(days: i32) -> Option { + Date32Type::to_naive_date_opt(days).and_then(|native_date| { + get_display_name(native_date.weekday().num_days_from_monday()) + }) } fn get_display_name(day: u32) -> Option { diff --git a/datafusion/sqllogictest/test_files/spark/datetime/dayname.slt b/datafusion/sqllogictest/test_files/spark/datetime/dayname.slt index fc7d6cb7fe063..e1b71a625b47d 100644 --- a/datafusion/sqllogictest/test_files/spark/datetime/dayname.slt +++ b/datafusion/sqllogictest/test_files/spark/datetime/dayname.slt @@ -54,3 +54,18 @@ query T SELECT dayname('2010-04-24'::DATE); ---- Sat + +query T +SELECT dayname('2010-04-24'::STRING); +---- +Sat + +query T +SELECT dayname('2010-04-24'::TIMESTAMP); +---- +Sat + +query T +SELECT dayname(''::STRING); +---- +NULL From 7352c8b23b4ba318e46c785784006d5bd2985d4f Mon Sep 17 00:00:00 2001 From: Kazantsev Maksim Date: Fri, 13 Mar 2026 22:31:35 +0400 Subject: [PATCH 04/20] Fix PR issues --- datafusion/spark/src/function/datetime/dayname.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/datafusion/spark/src/function/datetime/dayname.rs b/datafusion/spark/src/function/datetime/dayname.rs index 70b6972ab16f6..7ec3d455cc08b 100644 --- a/datafusion/spark/src/function/datetime/dayname.rs +++ b/datafusion/spark/src/function/datetime/dayname.rs @@ -23,7 +23,7 @@ use datafusion::logical_expr::{ }; use datafusion_common::types::{NativeType, logical_date, logical_string}; use datafusion_common::utils::take_function_args; -use datafusion_common::{Result, ScalarValue, exec_datafusion_err, internal_err}; +use datafusion_common::{Result, ScalarValue, internal_err}; use datafusion_expr::{ReturnFieldArgs, ScalarFunctionArgs, ScalarUDFImpl}; use std::any::Any; use std::sync::Arc; From 56378d32a129d6d29b0e903cf73d42d308f4166e Mon Sep 17 00:00:00 2001 From: Kazantsev Maksim Date: Fri, 13 Mar 2026 22:48:26 +0400 Subject: [PATCH 05/20] Fix PR issues --- .../sqllogictest/test_files/spark/datetime/dayname.slt | 5 ----- 1 file changed, 5 deletions(-) diff --git a/datafusion/sqllogictest/test_files/spark/datetime/dayname.slt b/datafusion/sqllogictest/test_files/spark/datetime/dayname.slt index e1b71a625b47d..2bf70d3bb8ccc 100644 --- a/datafusion/sqllogictest/test_files/spark/datetime/dayname.slt +++ b/datafusion/sqllogictest/test_files/spark/datetime/dayname.slt @@ -64,8 +64,3 @@ query T SELECT dayname('2010-04-24'::TIMESTAMP); ---- Sat - -query T -SELECT dayname(''::STRING); ----- -NULL From 835585cecec29c4f0b352c5b1a275661f58bdde0 Mon Sep 17 00:00:00 2001 From: Kazantsev Maksim Date: Wed, 25 Mar 2026 21:28:23 +0400 Subject: [PATCH 06/20] Add empty string test case --- .../sqllogictest/test_files/spark/datetime/dayname.slt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/datafusion/sqllogictest/test_files/spark/datetime/dayname.slt b/datafusion/sqllogictest/test_files/spark/datetime/dayname.slt index 2bf70d3bb8ccc..805fe4672065d 100644 --- a/datafusion/sqllogictest/test_files/spark/datetime/dayname.slt +++ b/datafusion/sqllogictest/test_files/spark/datetime/dayname.slt @@ -60,6 +60,11 @@ SELECT dayname('2010-04-24'::STRING); ---- Sat +query T +SELECT dayname(''::STRING); +---- +NULL + query T SELECT dayname('2010-04-24'::TIMESTAMP); ---- From ddbbb434d5c2aae36756d4cba7a6b2a76776e184 Mon Sep 17 00:00:00 2001 From: Kazantsev Maksim Date: Wed, 25 Mar 2026 21:29:12 +0400 Subject: [PATCH 07/20] Add empty string test case --- .../sqllogictest/test_files/spark/datetime/dayname.slt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/datafusion/sqllogictest/test_files/spark/datetime/dayname.slt b/datafusion/sqllogictest/test_files/spark/datetime/dayname.slt index 805fe4672065d..994ba8bb5d9f6 100644 --- a/datafusion/sqllogictest/test_files/spark/datetime/dayname.slt +++ b/datafusion/sqllogictest/test_files/spark/datetime/dayname.slt @@ -60,6 +60,11 @@ SELECT dayname('2010-04-24'::STRING); ---- Sat +query T +SELECT dayname(NULL::STRING); +---- +NULL + query T SELECT dayname(''::STRING); ---- From 8ecb07912595d1d84d4814653dfa3fdb248165cf Mon Sep 17 00:00:00 2001 From: Kazantsev Maksim Date: Wed, 25 Mar 2026 21:31:16 +0400 Subject: [PATCH 08/20] Add empty string test case --- .../spark/src/function/datetime/dayname.rs | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/datafusion/spark/src/function/datetime/dayname.rs b/datafusion/spark/src/function/datetime/dayname.rs index 7ec3d455cc08b..38a04266720c9 100644 --- a/datafusion/spark/src/function/datetime/dayname.rs +++ b/datafusion/spark/src/function/datetime/dayname.rs @@ -19,9 +19,9 @@ use arrow::array::{ArrayRef, AsArray, StringArray}; use arrow::datatypes::{DataType, Date32Type, Field, FieldRef}; use chrono::Datelike; use datafusion::logical_expr::{ - Coercion, ColumnarValue, Signature, TypeSignatureClass, Volatility, + Coercion, ColumnarValue, Signature, TypeSignature, TypeSignatureClass, Volatility, }; -use datafusion_common::types::{NativeType, logical_date, logical_string}; +use datafusion_common::types::{logical_date, logical_string}; use datafusion_common::utils::take_function_args; use datafusion_common::{Result, ScalarValue, internal_err}; use datafusion_expr::{ReturnFieldArgs, ScalarFunctionArgs, ScalarUDFImpl}; @@ -42,15 +42,17 @@ impl Default for SparkDayName { impl SparkDayName { pub fn new() -> Self { Self { - signature: Signature::coercible( - vec![Coercion::new_implicit( - TypeSignatureClass::Native(logical_date()), - vec![ - TypeSignatureClass::Native(logical_string()), - TypeSignatureClass::Timestamp, - ], - NativeType::Date, - )], + signature: Signature::one_of( + vec![ + TypeSignature::Coercible(vec![ + Coercion::new_exact(TypeSignatureClass::Native(logical_string())), + Coercion::new_exact(TypeSignatureClass::Timestamp), + ]), + TypeSignature::Coercible(vec![ + Coercion::new_exact(TypeSignatureClass::Native(logical_string())), + Coercion::new_exact(TypeSignatureClass::Native(logical_date())), + ]), + ], Volatility::Immutable, ), } From b07e2adf48982bc3f317cd5ab9806ef31e5a9a59 Mon Sep 17 00:00:00 2001 From: Kazantsev Maksim Date: Wed, 25 Mar 2026 21:42:33 +0400 Subject: [PATCH 09/20] Add empty string test case --- .../spark/src/function/datetime/dayname.rs | 24 +++++++++---------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/datafusion/spark/src/function/datetime/dayname.rs b/datafusion/spark/src/function/datetime/dayname.rs index 38a04266720c9..7ec3d455cc08b 100644 --- a/datafusion/spark/src/function/datetime/dayname.rs +++ b/datafusion/spark/src/function/datetime/dayname.rs @@ -19,9 +19,9 @@ use arrow::array::{ArrayRef, AsArray, StringArray}; use arrow::datatypes::{DataType, Date32Type, Field, FieldRef}; use chrono::Datelike; use datafusion::logical_expr::{ - Coercion, ColumnarValue, Signature, TypeSignature, TypeSignatureClass, Volatility, + Coercion, ColumnarValue, Signature, TypeSignatureClass, Volatility, }; -use datafusion_common::types::{logical_date, logical_string}; +use datafusion_common::types::{NativeType, logical_date, logical_string}; use datafusion_common::utils::take_function_args; use datafusion_common::{Result, ScalarValue, internal_err}; use datafusion_expr::{ReturnFieldArgs, ScalarFunctionArgs, ScalarUDFImpl}; @@ -42,17 +42,15 @@ impl Default for SparkDayName { impl SparkDayName { pub fn new() -> Self { Self { - signature: Signature::one_of( - vec![ - TypeSignature::Coercible(vec![ - Coercion::new_exact(TypeSignatureClass::Native(logical_string())), - Coercion::new_exact(TypeSignatureClass::Timestamp), - ]), - TypeSignature::Coercible(vec![ - Coercion::new_exact(TypeSignatureClass::Native(logical_string())), - Coercion::new_exact(TypeSignatureClass::Native(logical_date())), - ]), - ], + signature: Signature::coercible( + vec![Coercion::new_implicit( + TypeSignatureClass::Native(logical_date()), + vec![ + TypeSignatureClass::Native(logical_string()), + TypeSignatureClass::Timestamp, + ], + NativeType::Date, + )], Volatility::Immutable, ), } From 76bcf9c186d69558fe1abb7faea6e983494db272 Mon Sep 17 00:00:00 2001 From: Kazantsev Maksim Date: Sat, 28 Mar 2026 22:04:51 +0400 Subject: [PATCH 10/20] fix --- .../spark/src/function/datetime/dayname.rs | 92 +++++++++---------- 1 file changed, 41 insertions(+), 51 deletions(-) diff --git a/datafusion/spark/src/function/datetime/dayname.rs b/datafusion/spark/src/function/datetime/dayname.rs index 7ec3d455cc08b..6a44768e54d29 100644 --- a/datafusion/spark/src/function/datetime/dayname.rs +++ b/datafusion/spark/src/function/datetime/dayname.rs @@ -15,16 +15,17 @@ // specific language governing permissions and limitations // under the License. -use arrow::array::{ArrayRef, AsArray, StringArray}; -use arrow::datatypes::{DataType, Date32Type, Field, FieldRef}; -use chrono::Datelike; +use arrow::array::{Array, ArrayRef, AsArray, StringArray}; +use arrow::compute::{CastOptions, DatePart, cast_with_options, date_part}; +use arrow::datatypes::{DataType, Field, FieldRef, Int8Type}; use datafusion::logical_expr::{ - Coercion, ColumnarValue, Signature, TypeSignatureClass, Volatility, + Coercion, ColumnarValue, Signature, TypeSignature, TypeSignatureClass, Volatility, }; -use datafusion_common::types::{NativeType, logical_date, logical_string}; +use datafusion_common::types::{logical_date, logical_string}; use datafusion_common::utils::take_function_args; -use datafusion_common::{Result, ScalarValue, internal_err}; +use datafusion_common::{Result, internal_err}; use datafusion_expr::{ReturnFieldArgs, ScalarFunctionArgs, ScalarUDFImpl}; +use datafusion_functions::utils::make_scalar_function; use std::any::Any; use std::sync::Arc; @@ -42,15 +43,18 @@ impl Default for SparkDayName { impl SparkDayName { pub fn new() -> Self { Self { - signature: Signature::coercible( - vec![Coercion::new_implicit( - TypeSignatureClass::Native(logical_date()), - vec![ - TypeSignatureClass::Native(logical_string()), + signature: Signature::one_of( + vec![ + TypeSignature::Coercible(vec![Coercion::new_exact( TypeSignatureClass::Timestamp, - ], - NativeType::Date, - )], + )]), + TypeSignature::Coercible(vec![Coercion::new_exact( + TypeSignatureClass::Native(logical_date()), + )]), + TypeSignature::Coercible(vec![Coercion::new_exact( + TypeSignatureClass::Native(logical_string()), + )]), + ], Volatility::Immutable, ), } @@ -83,49 +87,35 @@ impl ScalarUDFImpl for SparkDayName { } fn invoke_with_args(&self, args: ScalarFunctionArgs) -> Result { - let [arg] = take_function_args("dayname", args.args)?; - match arg { - ColumnarValue::Scalar(ScalarValue::Date32(days)) => { - if let Some(days) = days { - Ok(ColumnarValue::Scalar(ScalarValue::Utf8(spark_day_name( - days, - )))) - } else { - Ok(ColumnarValue::Scalar(ScalarValue::Utf8(None))) - } - } - ColumnarValue::Array(array) => { - let result = match array.data_type() { - DataType::Date32 => { - let result: StringArray = array - .as_primitive::() - .iter() - .map(|x| x.and_then(spark_day_name)) - .collect(); - Ok(Arc::new(result) as ArrayRef) - } - other => { - internal_err!( - "Unsupported data type {other:?} for Spark function `dayname`" - ) - } - }?; - Ok(ColumnarValue::Array(result)) - } - other => { - internal_err!("Unsupported arg {other:?} for Spark function `dayname`") - } + make_scalar_function(spark_day_name, vec![])(&args.args) + } +} + +fn spark_day_name(args: &[ArrayRef]) -> Result { + let [array] = take_function_args("dayname", args)?; + match array.data_type() { + DataType::Date32 | DataType::Timestamp(_, _) => spark_day_name_inner(array), + DataType::Utf8 | DataType::Utf8View | DataType::LargeUtf8 => { + let date_array = + cast_with_options(array, &DataType::Date32, &CastOptions::default())?; + spark_day_name_inner(&date_array) + } + other => { + internal_err!("Unsupported arg {other:?} for Spark function `dayname`") } } } -fn spark_day_name(days: i32) -> Option { - Date32Type::to_naive_date_opt(days).and_then(|native_date| { - get_display_name(native_date.weekday().num_days_from_monday()) - }) +fn spark_day_name_inner(array: &ArrayRef) -> Result { + let result: StringArray = date_part(array, DatePart::DayOfWeekMonday0)? + .as_primitive::() + .iter() + .map(|x| x.and_then(get_display_name)) + .collect(); + Ok(Arc::new(result)) } -fn get_display_name(day: u32) -> Option { +fn get_display_name(day: i8) -> Option { match day { 0 => Some(String::from("Mon")), 1 => Some(String::from("Tue")), From e900486ac757dd836f41f9d1efcf39c5d8dc1b05 Mon Sep 17 00:00:00 2001 From: Kazantsev Maksim Date: Sat, 28 Mar 2026 22:08:16 +0400 Subject: [PATCH 11/20] fix --- datafusion/spark/src/function/datetime/dayname.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/datafusion/spark/src/function/datetime/dayname.rs b/datafusion/spark/src/function/datetime/dayname.rs index 6a44768e54d29..3019a3a4ec9c5 100644 --- a/datafusion/spark/src/function/datetime/dayname.rs +++ b/datafusion/spark/src/function/datetime/dayname.rs @@ -62,10 +62,6 @@ impl SparkDayName { } impl ScalarUDFImpl for SparkDayName { - fn as_any(&self) -> &dyn Any { - self - } - fn name(&self) -> &str { "dayname" } From 7562581710d8e33b747ad5cd113ce262409d3c7e Mon Sep 17 00:00:00 2001 From: Kazantsev Maksim Date: Sat, 28 Mar 2026 22:18:01 +0400 Subject: [PATCH 12/20] fix --- datafusion/spark/src/function/datetime/dayname.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/datafusion/spark/src/function/datetime/dayname.rs b/datafusion/spark/src/function/datetime/dayname.rs index 3019a3a4ec9c5..dca69c81914b5 100644 --- a/datafusion/spark/src/function/datetime/dayname.rs +++ b/datafusion/spark/src/function/datetime/dayname.rs @@ -26,7 +26,6 @@ use datafusion_common::utils::take_function_args; use datafusion_common::{Result, internal_err}; use datafusion_expr::{ReturnFieldArgs, ScalarFunctionArgs, ScalarUDFImpl}; use datafusion_functions::utils::make_scalar_function; -use std::any::Any; use std::sync::Arc; #[derive(Debug, PartialEq, Eq, Hash)] From 8a7e413bc2b7d9c98e1732437f676cf3a2816d6c Mon Sep 17 00:00:00 2001 From: Kazantsev Maksim Date: Sat, 28 Mar 2026 22:49:10 +0400 Subject: [PATCH 13/20] fix --- datafusion/spark/src/function/datetime/dayname.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/datafusion/spark/src/function/datetime/dayname.rs b/datafusion/spark/src/function/datetime/dayname.rs index dca69c81914b5..be398d216f162 100644 --- a/datafusion/spark/src/function/datetime/dayname.rs +++ b/datafusion/spark/src/function/datetime/dayname.rs @@ -17,7 +17,7 @@ use arrow::array::{Array, ArrayRef, AsArray, StringArray}; use arrow::compute::{CastOptions, DatePart, cast_with_options, date_part}; -use arrow::datatypes::{DataType, Field, FieldRef, Int8Type}; +use arrow::datatypes::{DataType, Field, FieldRef, Int32Type}; use datafusion::logical_expr::{ Coercion, ColumnarValue, Signature, TypeSignature, TypeSignatureClass, Volatility, }; @@ -103,14 +103,14 @@ fn spark_day_name(args: &[ArrayRef]) -> Result { fn spark_day_name_inner(array: &ArrayRef) -> Result { let result: StringArray = date_part(array, DatePart::DayOfWeekMonday0)? - .as_primitive::() + .as_primitive::() .iter() .map(|x| x.and_then(get_display_name)) .collect(); Ok(Arc::new(result)) } -fn get_display_name(day: i8) -> Option { +fn get_display_name(day: i32) -> Option { match day { 0 => Some(String::from("Mon")), 1 => Some(String::from("Tue")), From 180971091290e790198058d9c48ebef5675f744a Mon Sep 17 00:00:00 2001 From: Kazantsev Maksim Date: Sun, 29 Mar 2026 21:40:33 +0400 Subject: [PATCH 14/20] tests --- .../sqllogictest/test_files/spark/datetime/dayname.slt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/datafusion/sqllogictest/test_files/spark/datetime/dayname.slt b/datafusion/sqllogictest/test_files/spark/datetime/dayname.slt index 994ba8bb5d9f6..ebc6d1059cee0 100644 --- a/datafusion/sqllogictest/test_files/spark/datetime/dayname.slt +++ b/datafusion/sqllogictest/test_files/spark/datetime/dayname.slt @@ -74,3 +74,8 @@ query T SELECT dayname('2010-04-24'::TIMESTAMP); ---- Sat + +query T +SELECT dayname(NULL::TIMESTAMP); +---- +NULL From 989c812036d7fbfca08aae594247cb5f3944a261 Mon Sep 17 00:00:00 2001 From: Kazantsev Maksim Date: Tue, 31 Mar 2026 20:54:42 +0400 Subject: [PATCH 15/20] Fix PR issues --- datafusion/spark/src/function/datetime/dayname.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/datafusion/spark/src/function/datetime/dayname.rs b/datafusion/spark/src/function/datetime/dayname.rs index be398d216f162..500a468b4ff34 100644 --- a/datafusion/spark/src/function/datetime/dayname.rs +++ b/datafusion/spark/src/function/datetime/dayname.rs @@ -110,6 +110,8 @@ fn spark_day_name_inner(array: &ArrayRef) -> Result { Ok(Arc::new(result)) } +/// This function supports only the English locale, matching the behavior of Spark's +/// `dayname` function which return English day names regardless of the system or session locale. fn get_display_name(day: i32) -> Option { match day { 0 => Some(String::from("Mon")), From ab9a5ada9c48ccd442f309e9fb0ceefd7834f48b Mon Sep 17 00:00:00 2001 From: Kazantsev Maksim Date: Mon, 13 Apr 2026 21:24:06 +0400 Subject: [PATCH 16/20] WIP --- .../spark/src/function/datetime/dayname.rs | 44 ++++++++++++++----- 1 file changed, 33 insertions(+), 11 deletions(-) diff --git a/datafusion/spark/src/function/datetime/dayname.rs b/datafusion/spark/src/function/datetime/dayname.rs index 500a468b4ff34..f256bc1280f04 100644 --- a/datafusion/spark/src/function/datetime/dayname.rs +++ b/datafusion/spark/src/function/datetime/dayname.rs @@ -22,11 +22,10 @@ use datafusion::logical_expr::{ Coercion, ColumnarValue, Signature, TypeSignature, TypeSignatureClass, Volatility, }; use datafusion_common::types::{logical_date, logical_string}; -use datafusion_common::utils::take_function_args; -use datafusion_common::{Result, internal_err}; +use datafusion_common::{Result, internal_err, plan_err, ScalarValue}; use datafusion_expr::{ReturnFieldArgs, ScalarFunctionArgs, ScalarUDFImpl}; -use datafusion_functions::utils::make_scalar_function; use std::sync::Arc; +use arrow::util::display::FormatOptions; #[derive(Debug, PartialEq, Eq, Hash)] pub struct SparkDayName { @@ -82,18 +81,41 @@ impl ScalarUDFImpl for SparkDayName { } fn invoke_with_args(&self, args: ScalarFunctionArgs) -> Result { - make_scalar_function(spark_day_name, vec![])(&args.args) + if args.args.len() != 1 { + return plan_err!("dayname expects exactly 1 argument"); + } + let cast_options = CastOptions { + safe: args.config_options.execution.enable_ansi_mode, + format_options: FormatOptions::default(), + }; + match &args.args[0] { + ColumnarValue::Array(array) => { + let array = spark_day_name_array(array, cast_options)?; + Ok(ColumnarValue::Array(array)) + } + ColumnarValue::Scalar(scalar) => { + let scalar = spark_day_name_scalar(scalar, cast_options)?; + Ok(ColumnarValue::Scalar(scalar)) + } + } + } +} + +fn spark_day_name_scalar(scalar: &ScalarValue, cast_options: &CastOptions) -> Result { + match scalar.data_type() { + DataType::Date32 | DataType::Timestamp(_, _) => unimplemented!(), + other => { + internal_err!("Unsupported arg {other:?} for Spark function `dayname`") + } } } -fn spark_day_name(args: &[ArrayRef]) -> Result { - let [array] = take_function_args("dayname", args)?; +fn spark_day_name_array(array: &ArrayRef, cast_options: &CastOptions) -> Result { match array.data_type() { - DataType::Date32 | DataType::Timestamp(_, _) => spark_day_name_inner(array), + DataType::Date32 | DataType::Timestamp(_, _) => spark_day_name_array_inner(array), DataType::Utf8 | DataType::Utf8View | DataType::LargeUtf8 => { - let date_array = - cast_with_options(array, &DataType::Date32, &CastOptions::default())?; - spark_day_name_inner(&date_array) + let date_array = cast_with_options(array, &DataType::Date32, cast_options)?; + spark_day_name_array_inner(&date_array) } other => { internal_err!("Unsupported arg {other:?} for Spark function `dayname`") @@ -101,7 +123,7 @@ fn spark_day_name(args: &[ArrayRef]) -> Result { } } -fn spark_day_name_inner(array: &ArrayRef) -> Result { +fn spark_day_name_array_inner(array: &ArrayRef) -> Result { let result: StringArray = date_part(array, DatePart::DayOfWeekMonday0)? .as_primitive::() .iter() From a3452c1908584155be47e33e9733d856e0b90f50 Mon Sep 17 00:00:00 2001 From: Kazantsev Maksim Date: Tue, 14 Apr 2026 20:57:24 +0400 Subject: [PATCH 17/20] Add ansi mode flag --- .../spark/src/function/datetime/dayname.rs | 34 ++++++------------- 1 file changed, 11 insertions(+), 23 deletions(-) diff --git a/datafusion/spark/src/function/datetime/dayname.rs b/datafusion/spark/src/function/datetime/dayname.rs index f256bc1280f04..d5137d4f33966 100644 --- a/datafusion/spark/src/function/datetime/dayname.rs +++ b/datafusion/spark/src/function/datetime/dayname.rs @@ -18,14 +18,14 @@ use arrow::array::{Array, ArrayRef, AsArray, StringArray}; use arrow::compute::{CastOptions, DatePart, cast_with_options, date_part}; use arrow::datatypes::{DataType, Field, FieldRef, Int32Type}; +use arrow::util::display::FormatOptions; use datafusion::logical_expr::{ Coercion, ColumnarValue, Signature, TypeSignature, TypeSignatureClass, Volatility, }; use datafusion_common::types::{logical_date, logical_string}; -use datafusion_common::{Result, internal_err, plan_err, ScalarValue}; +use datafusion_common::{Result, internal_err, plan_err}; use datafusion_expr::{ReturnFieldArgs, ScalarFunctionArgs, ScalarUDFImpl}; use std::sync::Arc; -use arrow::util::display::FormatOptions; #[derive(Debug, PartialEq, Eq, Hash)] pub struct SparkDayName { @@ -88,34 +88,22 @@ impl ScalarUDFImpl for SparkDayName { safe: args.config_options.execution.enable_ansi_mode, format_options: FormatOptions::default(), }; - match &args.args[0] { - ColumnarValue::Array(array) => { - let array = spark_day_name_array(array, cast_options)?; - Ok(ColumnarValue::Array(array)) - } + let result = match &args.args[0] { + ColumnarValue::Array(array) => spark_day_name(array, &cast_options)?, ColumnarValue::Scalar(scalar) => { - let scalar = spark_day_name_scalar(scalar, cast_options)?; - Ok(ColumnarValue::Scalar(scalar)) + spark_day_name(&scalar.to_array()?, &cast_options)? } - } - } -} - -fn spark_day_name_scalar(scalar: &ScalarValue, cast_options: &CastOptions) -> Result { - match scalar.data_type() { - DataType::Date32 | DataType::Timestamp(_, _) => unimplemented!(), - other => { - internal_err!("Unsupported arg {other:?} for Spark function `dayname`") - } + }; + Ok(ColumnarValue::Array(result)) } } -fn spark_day_name_array(array: &ArrayRef, cast_options: &CastOptions) -> Result { +fn spark_day_name(array: &ArrayRef, cast_options: &CastOptions) -> Result { match array.data_type() { - DataType::Date32 | DataType::Timestamp(_, _) => spark_day_name_array_inner(array), + DataType::Date32 | DataType::Timestamp(_, _) => spark_day_name_inner(array), DataType::Utf8 | DataType::Utf8View | DataType::LargeUtf8 => { let date_array = cast_with_options(array, &DataType::Date32, cast_options)?; - spark_day_name_array_inner(&date_array) + spark_day_name_inner(&date_array) } other => { internal_err!("Unsupported arg {other:?} for Spark function `dayname`") @@ -123,7 +111,7 @@ fn spark_day_name_array(array: &ArrayRef, cast_options: &CastOptions) -> Result< } } -fn spark_day_name_array_inner(array: &ArrayRef) -> Result { +fn spark_day_name_inner(array: &ArrayRef) -> Result { let result: StringArray = date_part(array, DatePart::DayOfWeekMonday0)? .as_primitive::() .iter() From 7ec344ba76f51c8382f3b9262d54343104c167d2 Mon Sep 17 00:00:00 2001 From: Kazantsev Maksim Date: Tue, 14 Apr 2026 21:07:23 +0400 Subject: [PATCH 18/20] Fix tests --- datafusion/sqllogictest/test_files/spark/datetime/dayname.slt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/datafusion/sqllogictest/test_files/spark/datetime/dayname.slt b/datafusion/sqllogictest/test_files/spark/datetime/dayname.slt index ebc6d1059cee0..18ebb786747a2 100644 --- a/datafusion/sqllogictest/test_files/spark/datetime/dayname.slt +++ b/datafusion/sqllogictest/test_files/spark/datetime/dayname.slt @@ -15,6 +15,9 @@ # specific language governing permissions and limitations # under the License. +statement ok +set datafusion.execution.enable_ansi_mode = false; + query T SELECT dayname('2008-02-20'::DATE); ---- From 85978d41ad9ad1b52fead099b8590c02d8aa334b Mon Sep 17 00:00:00 2001 From: Kazantsev Maksim Date: Tue, 14 Apr 2026 21:19:54 +0400 Subject: [PATCH 19/20] Fix tests --- datafusion/spark/src/function/datetime/dayname.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/datafusion/spark/src/function/datetime/dayname.rs b/datafusion/spark/src/function/datetime/dayname.rs index d5137d4f33966..8ef3a3450d82b 100644 --- a/datafusion/spark/src/function/datetime/dayname.rs +++ b/datafusion/spark/src/function/datetime/dayname.rs @@ -85,7 +85,7 @@ impl ScalarUDFImpl for SparkDayName { return plan_err!("dayname expects exactly 1 argument"); } let cast_options = CastOptions { - safe: args.config_options.execution.enable_ansi_mode, + safe: !args.config_options.execution.enable_ansi_mode, format_options: FormatOptions::default(), }; let result = match &args.args[0] { From 9ef27141b0fdef7759b1af99a98cfffa87e72fb8 Mon Sep 17 00:00:00 2001 From: Kazantsev Maksim Date: Tue, 14 Apr 2026 22:20:06 +0400 Subject: [PATCH 20/20] Fix tests --- datafusion/spark/src/function/datetime/dayname.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/datafusion/spark/src/function/datetime/dayname.rs b/datafusion/spark/src/function/datetime/dayname.rs index 8ef3a3450d82b..10aa551d399d1 100644 --- a/datafusion/spark/src/function/datetime/dayname.rs +++ b/datafusion/spark/src/function/datetime/dayname.rs @@ -91,7 +91,8 @@ impl ScalarUDFImpl for SparkDayName { let result = match &args.args[0] { ColumnarValue::Array(array) => spark_day_name(array, &cast_options)?, ColumnarValue::Scalar(scalar) => { - spark_day_name(&scalar.to_array()?, &cast_options)? + let array = scalar.to_array()?; + spark_day_name(&array, &cast_options)? } }; Ok(ColumnarValue::Array(result))