From da68d11154d9e3ef2118a504dff72e591c751565 Mon Sep 17 00:00:00 2001 From: Kumar Ujjawal Date: Mon, 5 Jan 2026 09:40:44 +0530 Subject: [PATCH 1/6] feat: add Time type support to date_trunc function --- .../functions/src/datetime/date_trunc.rs | 224 +++++++++++++++++- .../test_files/datetime/timestamps.slt | 53 +++++ 2 files changed, 264 insertions(+), 13 deletions(-) diff --git a/datafusion/functions/src/datetime/date_trunc.rs b/datafusion/functions/src/datetime/date_trunc.rs index aca1d24c3116a..9b0db09c88900 100644 --- a/datafusion/functions/src/datetime/date_trunc.rs +++ b/datafusion/functions/src/datetime/date_trunc.rs @@ -22,15 +22,17 @@ use std::str::FromStr; use std::sync::Arc; use arrow::array::temporal_conversions::{ - as_datetime_with_timezone, timestamp_ns_to_datetime, + MICROSECONDS, MILLISECONDS, NANOSECONDS, as_datetime_with_timezone, + timestamp_ns_to_datetime, }; use arrow::array::timezone::Tz; use arrow::array::types::{ - ArrowTimestampType, TimestampMicrosecondType, TimestampMillisecondType, + ArrowTimestampType, Time32MillisecondType, Time32SecondType, Time64MicrosecondType, + Time64NanosecondType, TimestampMicrosecondType, TimestampMillisecondType, TimestampNanosecondType, TimestampSecondType, }; use arrow::array::{Array, ArrayRef, PrimitiveArray}; -use arrow::datatypes::DataType::{self, Null, Timestamp, Utf8, Utf8View}; +use arrow::datatypes::DataType::{self, Null, Time32, Time64, Timestamp, Utf8, Utf8View}; use arrow::datatypes::TimeUnit::{self, Microsecond, Millisecond, Nanosecond, Second}; use datafusion_common::cast::as_primitive_array; use datafusion_common::{ @@ -116,16 +118,30 @@ impl DateTruncGranularity { fn is_fine_granularity_utc(&self) -> bool { self.is_fine_granularity() || matches!(self, Self::Hour | Self::Day) } + + /// Returns true if this granularity is valid for Time types + /// Time types don't have date components, so day/week/month/quarter/year are not valid + fn valid_for_time(&self) -> bool { + matches!( + self, + Self::Hour + | Self::Minute + | Self::Second + | Self::Millisecond + | Self::Microsecond + ) + } } #[user_doc( doc_section(label = "Time and Date Functions"), - description = "Truncates a timestamp value to a specified precision.", + description = "Truncates a timestamp or time value to a specified precision.", syntax_example = "date_trunc(precision, expression)", argument( name = "precision", description = r#"Time precision to truncate to. The following precisions are supported: + For Timestamp types: - year / YEAR - quarter / QUARTER - month / MONTH @@ -136,11 +152,18 @@ impl DateTruncGranularity { - second / SECOND - millisecond / MILLISECOND - microsecond / MICROSECOND + + For Time types (hour, minute, second, millisecond, microsecond only): + - hour / HOUR + - minute / MINUTE + - second / SECOND + - millisecond / MILLISECOND + - microsecond / MICROSECOND "# ), argument( name = "expression", - description = "Time expression to operate on. Can be a constant, column, or function." + description = "Timestamp or Time expression to operate on. Can be a constant, column, or function." ) )] #[derive(Debug, PartialEq, Eq, Hash)] @@ -160,6 +183,7 @@ impl DateTruncFunc { Self { signature: Signature::one_of( vec![ + // Timestamp signatures Exact(vec![Utf8, Timestamp(Nanosecond, None)]), Exact(vec![Utf8View, Timestamp(Nanosecond, None)]), Exact(vec![ @@ -200,6 +224,14 @@ impl DateTruncFunc { Utf8View, Timestamp(Second, Some(TIMEZONE_WILDCARD.into())), ]), + Exact(vec![Utf8, Time64(Nanosecond)]), + Exact(vec![Utf8View, Time64(Nanosecond)]), + Exact(vec![Utf8, Time64(Microsecond)]), + Exact(vec![Utf8View, Time64(Microsecond)]), + Exact(vec![Utf8, Time32(Millisecond)]), + Exact(vec![Utf8View, Time32(Millisecond)]), + Exact(vec![Utf8, Time32(Second)]), + Exact(vec![Utf8View, Time32(Second)]), ], Volatility::Immutable, ), @@ -230,8 +262,10 @@ impl ScalarUDFImpl for DateTruncFunc { Timestamp(Microsecond, tz_opt) => Ok(Timestamp(Microsecond, tz_opt.clone())), Timestamp(Millisecond, tz_opt) => Ok(Timestamp(Millisecond, tz_opt.clone())), Timestamp(Second, tz_opt) => Ok(Timestamp(Second, tz_opt.clone())), + Time64(tu) => Ok(Time64(*tu)), + Time32(tu) => Ok(Time32(*tu)), _ => plan_err!( - "The date_trunc function can only accept timestamp as the second arg." + "The date_trunc function can only accept timestamp or time as the second arg." ), } } @@ -315,10 +349,50 @@ impl ScalarUDFImpl for DateTruncFunc { ColumnarValue::Scalar(ScalarValue::TimestampSecond(v, tz_opt)) => { process_scalar::(v, granularity, tz_opt)? } + ColumnarValue::Scalar(ScalarValue::Time64Nanosecond(v)) => { + if !granularity.valid_for_time() { + return exec_err!( + "date_trunc does not support '{}' granularity for Time types. Valid values are: hour, minute, second, millisecond, microsecond", + granularity_str + ); + } + let truncated = v.map(|val| truncate_time_nanos(val, granularity)); + ColumnarValue::Scalar(ScalarValue::Time64Nanosecond(truncated)) + } + ColumnarValue::Scalar(ScalarValue::Time64Microsecond(v)) => { + if !granularity.valid_for_time() { + return exec_err!( + "date_trunc does not support '{}' granularity for Time types. Valid values are: hour, minute, second, millisecond, microsecond", + granularity_str + ); + } + let truncated = v.map(|val| truncate_time_micros(val, granularity)); + ColumnarValue::Scalar(ScalarValue::Time64Microsecond(truncated)) + } + ColumnarValue::Scalar(ScalarValue::Time32Millisecond(v)) => { + if !granularity.valid_for_time() { + return exec_err!( + "date_trunc does not support '{}' granularity for Time types. Valid values are: hour, minute, second, millisecond, microsecond", + granularity_str + ); + } + let truncated = v.map(|val| truncate_time_millis(val, granularity)); + ColumnarValue::Scalar(ScalarValue::Time32Millisecond(truncated)) + } + ColumnarValue::Scalar(ScalarValue::Time32Second(v)) => { + if !granularity.valid_for_time() { + return exec_err!( + "date_trunc does not support '{}' granularity for Time types. Valid values are: hour, minute, second, millisecond, microsecond", + granularity_str + ); + } + let truncated = v.map(|val| truncate_time_secs(val, granularity)); + ColumnarValue::Scalar(ScalarValue::Time32Second(truncated)) + } ColumnarValue::Array(array) => { let array_type = array.data_type(); - if let Timestamp(unit, tz_opt) = array_type { - match unit { + match array_type { + Timestamp(unit, tz_opt) => match unit { Second => process_array::( array, granularity, @@ -339,16 +413,70 @@ impl ScalarUDFImpl for DateTruncFunc { granularity, tz_opt, )?, + }, + Time64(unit) => { + if !granularity.valid_for_time() { + return exec_err!( + "date_trunc does not support '{}' granularity for Time types. Valid values are: hour, minute, second, millisecond, microsecond", + granularity_str + ); + } + match unit { + Nanosecond => { + let arr = + as_primitive_array::(array)?; + let result: PrimitiveArray = + arr.unary(|v| truncate_time_nanos(v, granularity)); + ColumnarValue::Array(Arc::new(result)) + } + Microsecond => { + let arr = + as_primitive_array::(array)?; + let result: PrimitiveArray = + arr.unary(|v| truncate_time_micros(v, granularity)); + ColumnarValue::Array(Arc::new(result)) + } + _ => { + return exec_err!("Unsupported Time64 unit: {:?}", unit); + } + } + } + Time32(unit) => { + if !granularity.valid_for_time() { + return exec_err!( + "date_trunc does not support '{}' granularity for Time types. Valid values are: hour, minute, second, millisecond, microsecond", + granularity_str + ); + } + match unit { + Millisecond => { + let arr = + as_primitive_array::(array)?; + let result: PrimitiveArray = + arr.unary(|v| truncate_time_millis(v, granularity)); + ColumnarValue::Array(Arc::new(result)) + } + Second => { + let arr = as_primitive_array::(array)?; + let result: PrimitiveArray = + arr.unary(|v| truncate_time_secs(v, granularity)); + ColumnarValue::Array(Arc::new(result)) + } + _ => { + return exec_err!("Unsupported Time32 unit: {:?}", unit); + } + } + } + _ => { + return exec_err!( + "second argument of `date_trunc` is an unsupported array type: {array_type}" + ); } - } else { - return exec_err!( - "second argument of `date_trunc` is an unsupported array type: {array_type}" - ); } } _ => { return exec_err!( - "second argument of `date_trunc` must be timestamp scalar or array" + "second argument of `date_trunc` must be timestamp, time scalar or array" ); } }) @@ -374,6 +502,76 @@ impl ScalarUDFImpl for DateTruncFunc { } } +const NANOS_PER_MICROSECOND: i64 = NANOSECONDS / MICROSECONDS; +const NANOS_PER_MILLISECOND: i64 = NANOSECONDS / MILLISECONDS; +const NANOS_PER_SECOND: i64 = NANOSECONDS; +const NANOS_PER_MINUTE: i64 = 60 * NANOS_PER_SECOND; +const NANOS_PER_HOUR: i64 = 60 * NANOS_PER_MINUTE; + +const MICROS_PER_MILLISECOND: i64 = MICROSECONDS / MILLISECONDS; +const MICROS_PER_SECOND: i64 = MICROSECONDS; +const MICROS_PER_MINUTE: i64 = 60 * MICROS_PER_SECOND; +const MICROS_PER_HOUR: i64 = 60 * MICROS_PER_MINUTE; + +const MILLIS_PER_SECOND: i32 = MILLISECONDS as i32; +const MILLIS_PER_MINUTE: i32 = 60 * MILLIS_PER_SECOND; +const MILLIS_PER_HOUR: i32 = 60 * MILLIS_PER_MINUTE; + +const SECS_PER_MINUTE: i32 = 60; +const SECS_PER_HOUR: i32 = 60 * SECS_PER_MINUTE; + +/// Truncate time in nanoseconds to the specified granularity +fn truncate_time_nanos(value: i64, granularity: DateTruncGranularity) -> i64 { + match granularity { + DateTruncGranularity::Hour => value - (value % NANOS_PER_HOUR), + DateTruncGranularity::Minute => value - (value % NANOS_PER_MINUTE), + DateTruncGranularity::Second => value - (value % NANOS_PER_SECOND), + DateTruncGranularity::Millisecond => value - (value % NANOS_PER_MILLISECOND), + DateTruncGranularity::Microsecond => value - (value % NANOS_PER_MICROSECOND), + // Other granularities are not valid for time - should be caught earlier + _ => value, + } +} + +/// Truncate time in microseconds to the specified granularity +fn truncate_time_micros(value: i64, granularity: DateTruncGranularity) -> i64 { + match granularity { + DateTruncGranularity::Hour => value - (value % MICROS_PER_HOUR), + DateTruncGranularity::Minute => value - (value % MICROS_PER_MINUTE), + DateTruncGranularity::Second => value - (value % MICROS_PER_SECOND), + DateTruncGranularity::Millisecond => value - (value % MICROS_PER_MILLISECOND), + DateTruncGranularity::Microsecond => value, // Already at microsecond precision + // Other granularities are not valid for time + _ => value, + } +} + +/// Truncate time in milliseconds to the specified granularity +fn truncate_time_millis(value: i32, granularity: DateTruncGranularity) -> i32 { + match granularity { + DateTruncGranularity::Hour => value - (value % MILLIS_PER_HOUR), + DateTruncGranularity::Minute => value - (value % MILLIS_PER_MINUTE), + DateTruncGranularity::Second => value - (value % MILLIS_PER_SECOND), + DateTruncGranularity::Millisecond => value, // Already at millisecond precision + DateTruncGranularity::Microsecond => value, // Can't truncate to finer precision + // Other granularities are not valid for time + _ => value, + } +} + +/// Truncate time in seconds to the specified granularity +fn truncate_time_secs(value: i32, granularity: DateTruncGranularity) -> i32 { + match granularity { + DateTruncGranularity::Hour => value - (value % SECS_PER_HOUR), + DateTruncGranularity::Minute => value - (value % SECS_PER_MINUTE), + DateTruncGranularity::Second => value, // Already at second precision + DateTruncGranularity::Millisecond => value, // Can't truncate to finer precision + DateTruncGranularity::Microsecond => value, // Can't truncate to finer precision + // Other granularities are not valid for time + _ => value, + } +} + fn _date_trunc_coarse( granularity: DateTruncGranularity, value: Option, diff --git a/datafusion/sqllogictest/test_files/datetime/timestamps.slt b/datafusion/sqllogictest/test_files/datetime/timestamps.slt index 5749b1c53d852..2df4c07e3a720 100644 --- a/datafusion/sqllogictest/test_files/datetime/timestamps.slt +++ b/datafusion/sqllogictest/test_files/datetime/timestamps.slt @@ -2374,6 +2374,59 @@ select arrow_typeof(date_trunc('microsecond', to_timestamp(61))) ---- Timestamp(ns) +########## +## date_trunc with Time types +########## + +# Truncate time to hour +query D +SELECT date_trunc('hour', TIME '14:30:45'); +---- +14:00:00 + +# Truncate time to minute +query D +SELECT date_trunc('minute', TIME '14:30:45'); +---- +14:30:00 + +# Truncate time to second (removes fractional seconds) +query D +SELECT date_trunc('second', TIME '14:30:45.123456789'); +---- +14:30:45 + +# Truncate time to millisecond +query D +SELECT date_trunc('millisecond', TIME '14:30:45.123456789'); +---- +14:30:45.123 + +# Truncate time to microsecond +query D +SELECT date_trunc('microsecond', TIME '14:30:45.123456789'); +---- +14:30:45.123456 + +# Return type should be Time64(ns) +query T +SELECT arrow_typeof(date_trunc('hour', TIME '14:30:45')); +---- +Time64(ns) + +# Error for granularities not valid for Time types +query error date_trunc does not support 'day' granularity for Time types +SELECT date_trunc('day', TIME '14:30:45'); + +query error date_trunc does not support 'week' granularity for Time types +SELECT date_trunc('week', TIME '14:30:45'); + +query error date_trunc does not support 'month' granularity for Time types +SELECT date_trunc('month', TIME '14:30:45'); + +query error date_trunc does not support 'year' granularity for Time types +SELECT date_trunc('year', TIME '14:30:45'); + # check date_bin query P SELECT date_bin(INTERVAL '1 day', time, '1970-01-01T00:00:00+05:00') FROM foo From c7b95507d47d789aaa97a74dc0ed6bc2ed2a7c65 Mon Sep 17 00:00:00 2001 From: Kumar Ujjawal Date: Mon, 5 Jan 2026 10:57:29 +0530 Subject: [PATCH 2/6] fix the schema and update docs --- .../test_files/information_schema.slt | 84 ++++++++++++------- .../source/user-guide/sql/scalar_functions.md | 14 +++- 2 files changed, 64 insertions(+), 34 deletions(-) diff --git a/datafusion/sqllogictest/test_files/information_schema.slt b/datafusion/sqllogictest/test_files/information_schema.slt index 18f72cb9f7798..c1b9650ee37a3 100644 --- a/datafusion/sqllogictest/test_files/information_schema.slt +++ b/datafusion/sqllogictest/test_files/information_schema.slt @@ -793,14 +793,18 @@ string_agg String AGGREGATE query TTTTTTTBTTTT rowsort select * from information_schema.routines where routine_name = 'date_trunc' OR routine_name = 'string_agg' OR routine_name = 'rank' ORDER BY routine_name ---- -datafusion public date_trunc datafusion public date_trunc FUNCTION true Timestamp(Microsecond, None) SCALAR Truncates a timestamp value to a specified precision. date_trunc(precision, expression) -datafusion public date_trunc datafusion public date_trunc FUNCTION true Timestamp(Microsecond, Some("+TZ")) SCALAR Truncates a timestamp value to a specified precision. date_trunc(precision, expression) -datafusion public date_trunc datafusion public date_trunc FUNCTION true Timestamp(Millisecond, None) SCALAR Truncates a timestamp value to a specified precision. date_trunc(precision, expression) -datafusion public date_trunc datafusion public date_trunc FUNCTION true Timestamp(Millisecond, Some("+TZ")) SCALAR Truncates a timestamp value to a specified precision. date_trunc(precision, expression) -datafusion public date_trunc datafusion public date_trunc FUNCTION true Timestamp(Nanosecond, None) SCALAR Truncates a timestamp value to a specified precision. date_trunc(precision, expression) -datafusion public date_trunc datafusion public date_trunc FUNCTION true Timestamp(Nanosecond, Some("+TZ")) SCALAR Truncates a timestamp value to a specified precision. date_trunc(precision, expression) -datafusion public date_trunc datafusion public date_trunc FUNCTION true Timestamp(Second, None) SCALAR Truncates a timestamp value to a specified precision. date_trunc(precision, expression) -datafusion public date_trunc datafusion public date_trunc FUNCTION true Timestamp(Second, Some("+TZ")) SCALAR Truncates a timestamp value to a specified precision. date_trunc(precision, expression) +datafusion public date_trunc datafusion public date_trunc FUNCTION true Time(Microsecond) SCALAR Truncates a timestamp or time value to a specified precision. date_trunc(precision, expression) +datafusion public date_trunc datafusion public date_trunc FUNCTION true Time(Millisecond) SCALAR Truncates a timestamp or time value to a specified precision. date_trunc(precision, expression) +datafusion public date_trunc datafusion public date_trunc FUNCTION true Time(Nanosecond) SCALAR Truncates a timestamp or time value to a specified precision. date_trunc(precision, expression) +datafusion public date_trunc datafusion public date_trunc FUNCTION true Time(Second) SCALAR Truncates a timestamp or time value to a specified precision. date_trunc(precision, expression) +datafusion public date_trunc datafusion public date_trunc FUNCTION true Timestamp(Microsecond, None) SCALAR Truncates a timestamp or time value to a specified precision. date_trunc(precision, expression) +datafusion public date_trunc datafusion public date_trunc FUNCTION true Timestamp(Microsecond, Some("+TZ")) SCALAR Truncates a timestamp or time value to a specified precision. date_trunc(precision, expression) +datafusion public date_trunc datafusion public date_trunc FUNCTION true Timestamp(Millisecond, None) SCALAR Truncates a timestamp or time value to a specified precision. date_trunc(precision, expression) +datafusion public date_trunc datafusion public date_trunc FUNCTION true Timestamp(Millisecond, Some("+TZ")) SCALAR Truncates a timestamp or time value to a specified precision. date_trunc(precision, expression) +datafusion public date_trunc datafusion public date_trunc FUNCTION true Timestamp(Nanosecond, None) SCALAR Truncates a timestamp or time value to a specified precision. date_trunc(precision, expression) +datafusion public date_trunc datafusion public date_trunc FUNCTION true Timestamp(Nanosecond, Some("+TZ")) SCALAR Truncates a timestamp or time value to a specified precision. date_trunc(precision, expression) +datafusion public date_trunc datafusion public date_trunc FUNCTION true Timestamp(Second, None) SCALAR Truncates a timestamp or time value to a specified precision. date_trunc(precision, expression) +datafusion public date_trunc datafusion public date_trunc FUNCTION true Timestamp(Second, Some("+TZ")) SCALAR Truncates a timestamp or time value to a specified precision. date_trunc(precision, expression) datafusion public rank datafusion public rank FUNCTION true NULL WINDOW Returns the rank of the current row within its partition, allowing gaps between ranks. This function provides a ranking similar to `row_number`, but skips ranks for identical values. rank() datafusion public string_agg datafusion public string_agg FUNCTION true String AGGREGATE Concatenates the values of string expressions and places separator values between them. If ordering is required, strings are concatenated in the specified order. This aggregation function can only mix DISTINCT and ORDER BY if the ordering expression is exactly the same as the first argument expression. string_agg([DISTINCT] expression, delimiter [ORDER BY expression]) @@ -814,29 +818,41 @@ query TTTITTTTBI select * from information_schema.parameters where specific_name = 'date_trunc' OR specific_name = 'string_agg' OR specific_name = 'rank' ORDER BY specific_name, rid, data_type; ---- datafusion public date_trunc 1 IN precision String NULL false 0 -datafusion public date_trunc 2 IN expression Timestamp(Microsecond, None) NULL false 0 -datafusion public date_trunc 1 OUT NULL Timestamp(Microsecond, None) NULL false 0 +datafusion public date_trunc 2 IN expression Time(Microsecond) NULL false 0 +datafusion public date_trunc 1 OUT NULL Time(Microsecond) NULL false 0 datafusion public date_trunc 1 IN precision String NULL false 1 -datafusion public date_trunc 2 IN expression Timestamp(Microsecond, Some("+TZ")) NULL false 1 -datafusion public date_trunc 1 OUT NULL Timestamp(Microsecond, Some("+TZ")) NULL false 1 +datafusion public date_trunc 2 IN expression Time(Millisecond) NULL false 1 +datafusion public date_trunc 1 OUT NULL Time(Millisecond) NULL false 1 datafusion public date_trunc 1 IN precision String NULL false 2 -datafusion public date_trunc 2 IN expression Timestamp(Millisecond, None) NULL false 2 -datafusion public date_trunc 1 OUT NULL Timestamp(Millisecond, None) NULL false 2 +datafusion public date_trunc 2 IN expression Time(Nanosecond) NULL false 2 +datafusion public date_trunc 1 OUT NULL Time(Nanosecond) NULL false 2 datafusion public date_trunc 1 IN precision String NULL false 3 -datafusion public date_trunc 2 IN expression Timestamp(Millisecond, Some("+TZ")) NULL false 3 -datafusion public date_trunc 1 OUT NULL Timestamp(Millisecond, Some("+TZ")) NULL false 3 +datafusion public date_trunc 2 IN expression Time(Second) NULL false 3 +datafusion public date_trunc 1 OUT NULL Time(Second) NULL false 3 datafusion public date_trunc 1 IN precision String NULL false 4 -datafusion public date_trunc 2 IN expression Timestamp(Nanosecond, None) NULL false 4 -datafusion public date_trunc 1 OUT NULL Timestamp(Nanosecond, None) NULL false 4 +datafusion public date_trunc 2 IN expression Timestamp(Microsecond, None) NULL false 4 +datafusion public date_trunc 1 OUT NULL Timestamp(Microsecond, None) NULL false 4 datafusion public date_trunc 1 IN precision String NULL false 5 -datafusion public date_trunc 2 IN expression Timestamp(Nanosecond, Some("+TZ")) NULL false 5 -datafusion public date_trunc 1 OUT NULL Timestamp(Nanosecond, Some("+TZ")) NULL false 5 +datafusion public date_trunc 2 IN expression Timestamp(Microsecond, Some("+TZ")) NULL false 5 +datafusion public date_trunc 1 OUT NULL Timestamp(Microsecond, Some("+TZ")) NULL false 5 datafusion public date_trunc 1 IN precision String NULL false 6 -datafusion public date_trunc 2 IN expression Timestamp(Second, None) NULL false 6 -datafusion public date_trunc 1 OUT NULL Timestamp(Second, None) NULL false 6 +datafusion public date_trunc 1 OUT NULL Timestamp(Millisecond, None) NULL false 6 +datafusion public date_trunc 2 IN expression Timestamp(Millisecond, None) NULL false 6 datafusion public date_trunc 1 IN precision String NULL false 7 -datafusion public date_trunc 2 IN expression Timestamp(Second, Some("+TZ")) NULL false 7 -datafusion public date_trunc 1 OUT NULL Timestamp(Second, Some("+TZ")) NULL false 7 +datafusion public date_trunc 2 IN expression Timestamp(Millisecond, Some("+TZ")) NULL false 7 +datafusion public date_trunc 1 OUT NULL Timestamp(Millisecond, Some("+TZ")) NULL false 7 +datafusion public date_trunc 1 IN precision String NULL false 8 +datafusion public date_trunc 2 IN expression Timestamp(Nanosecond, None) NULL false 8 +datafusion public date_trunc 1 OUT NULL Timestamp(Nanosecond, None) NULL false 8 +datafusion public date_trunc 1 IN precision String NULL false 9 +datafusion public date_trunc 2 IN expression Timestamp(Nanosecond, Some("+TZ")) NULL false 9 +datafusion public date_trunc 1 OUT NULL Timestamp(Nanosecond, Some("+TZ")) NULL false 9 +datafusion public date_trunc 1 IN precision String NULL false 10 +datafusion public date_trunc 2 IN expression Timestamp(Second, None) NULL false 10 +datafusion public date_trunc 1 OUT NULL Timestamp(Second, None) NULL false 10 +datafusion public date_trunc 1 IN precision String NULL false 11 +datafusion public date_trunc 2 IN expression Timestamp(Second, Some("+TZ")) NULL false 11 +datafusion public date_trunc 1 OUT NULL Timestamp(Second, Some("+TZ")) NULL false 11 datafusion public string_agg 2 IN delimiter Null NULL false 0 datafusion public string_agg 1 IN expression String NULL false 0 datafusion public string_agg 1 OUT NULL String NULL false 0 @@ -862,14 +878,18 @@ repeat String 1 OUT 0 query TT??TTT rowsort show functions like 'date_trunc'; ---- -date_trunc Timestamp(Microsecond, None) [precision, expression] [String, Timestamp(Microsecond, None)] SCALAR Truncates a timestamp value to a specified precision. date_trunc(precision, expression) -date_trunc Timestamp(Microsecond, Some("+TZ")) [precision, expression] [String, Timestamp(Microsecond, Some("+TZ"))] SCALAR Truncates a timestamp value to a specified precision. date_trunc(precision, expression) -date_trunc Timestamp(Millisecond, None) [precision, expression] [String, Timestamp(Millisecond, None)] SCALAR Truncates a timestamp value to a specified precision. date_trunc(precision, expression) -date_trunc Timestamp(Millisecond, Some("+TZ")) [precision, expression] [String, Timestamp(Millisecond, Some("+TZ"))] SCALAR Truncates a timestamp value to a specified precision. date_trunc(precision, expression) -date_trunc Timestamp(Nanosecond, None) [precision, expression] [String, Timestamp(Nanosecond, None)] SCALAR Truncates a timestamp value to a specified precision. date_trunc(precision, expression) -date_trunc Timestamp(Nanosecond, Some("+TZ")) [precision, expression] [String, Timestamp(Nanosecond, Some("+TZ"))] SCALAR Truncates a timestamp value to a specified precision. date_trunc(precision, expression) -date_trunc Timestamp(Second, None) [precision, expression] [String, Timestamp(Second, None)] SCALAR Truncates a timestamp value to a specified precision. date_trunc(precision, expression) -date_trunc Timestamp(Second, Some("+TZ")) [precision, expression] [String, Timestamp(Second, Some("+TZ"))] SCALAR Truncates a timestamp value to a specified precision. date_trunc(precision, expression) +date_trunc Time(Microsecond) [precision, expression] [String, Time(Microsecond)] SCALAR Truncates a timestamp or time value to a specified precision. date_trunc(precision, expression) +date_trunc Time(Millisecond) [precision, expression] [String, Time(Millisecond)] SCALAR Truncates a timestamp or time value to a specified precision. date_trunc(precision, expression) +date_trunc Time(Nanosecond) [precision, expression] [String, Time(Nanosecond)] SCALAR Truncates a timestamp or time value to a specified precision. date_trunc(precision, expression) +date_trunc Time(Second) [precision, expression] [String, Time(Second)] SCALAR Truncates a timestamp or time value to a specified precision. date_trunc(precision, expression) +date_trunc Timestamp(Microsecond, None) [precision, expression] [String, Timestamp(Microsecond, None)] SCALAR Truncates a timestamp or time value to a specified precision. date_trunc(precision, expression) +date_trunc Timestamp(Microsecond, Some("+TZ")) [precision, expression] [String, Timestamp(Microsecond, Some("+TZ"))] SCALAR Truncates a timestamp or time value to a specified precision. date_trunc(precision, expression) +date_trunc Timestamp(Millisecond, None) [precision, expression] [String, Timestamp(Millisecond, None)] SCALAR Truncates a timestamp or time value to a specified precision. date_trunc(precision, expression) +date_trunc Timestamp(Millisecond, Some("+TZ")) [precision, expression] [String, Timestamp(Millisecond, Some("+TZ"))] SCALAR Truncates a timestamp or time value to a specified precision. date_trunc(precision, expression) +date_trunc Timestamp(Nanosecond, None) [precision, expression] [String, Timestamp(Nanosecond, None)] SCALAR Truncates a timestamp or time value to a specified precision. date_trunc(precision, expression) +date_trunc Timestamp(Nanosecond, Some("+TZ")) [precision, expression] [String, Timestamp(Nanosecond, Some("+TZ"))] SCALAR Truncates a timestamp or time value to a specified precision. date_trunc(precision, expression) +date_trunc Timestamp(Second, None) [precision, expression] [String, Timestamp(Second, None)] SCALAR Truncates a timestamp or time value to a specified precision. date_trunc(precision, expression) +date_trunc Timestamp(Second, Some("+TZ")) [precision, expression] [String, Timestamp(Second, Some("+TZ"))] SCALAR Truncates a timestamp or time value to a specified precision. date_trunc(precision, expression) statement ok show functions diff --git a/docs/source/user-guide/sql/scalar_functions.md b/docs/source/user-guide/sql/scalar_functions.md index 360311552f025..ecd557a821b72 100644 --- a/docs/source/user-guide/sql/scalar_functions.md +++ b/docs/source/user-guide/sql/scalar_functions.md @@ -2548,7 +2548,7 @@ extract(field FROM source) ### `date_trunc` -Truncates a timestamp value to a specified precision. +Truncates a timestamp or time value to a specified precision. ```sql date_trunc(precision, expression) @@ -2558,6 +2558,8 @@ date_trunc(precision, expression) - **precision**: Time precision to truncate to. The following precisions are supported: + For Timestamp types: + - year / YEAR - quarter / QUARTER - month / MONTH @@ -2569,7 +2571,15 @@ date_trunc(precision, expression) - millisecond / MILLISECOND - microsecond / MICROSECOND -- **expression**: Time expression to operate on. Can be a constant, column, or function. + For Time types (hour, minute, second, millisecond, microsecond only): + + - hour / HOUR + - minute / MINUTE + - second / SECOND + - millisecond / MILLISECOND + - microsecond / MICROSECOND + +- **expression**: Timestamp or Time expression to operate on. Can be a constant, column, or function. #### Aliases From fb575bb0509eadccac9a97d4870d7b43b53395f2 Mon Sep 17 00:00:00 2001 From: Kumar Ujjawal Date: Mon, 5 Jan 2026 14:16:03 +0530 Subject: [PATCH 3/6] suggestion from Jefffrey Co-authored-by: Jeffrey Vo --- datafusion/functions/src/datetime/date_trunc.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/datafusion/functions/src/datetime/date_trunc.rs b/datafusion/functions/src/datetime/date_trunc.rs index 9b0db09c88900..a3a5caa7c8e49 100644 --- a/datafusion/functions/src/datetime/date_trunc.rs +++ b/datafusion/functions/src/datetime/date_trunc.rs @@ -163,7 +163,7 @@ impl DateTruncGranularity { ), argument( name = "expression", - description = "Timestamp or Time expression to operate on. Can be a constant, column, or function." + description = "Timestamp or time expression to operate on. Can be a constant, column, or function." ) )] #[derive(Debug, PartialEq, Eq, Hash)] From 776fdc13b7ca6de1d8dc4cace06ae18862164fe8 Mon Sep 17 00:00:00 2001 From: Kumar Ujjawal Date: Mon, 5 Jan 2026 15:10:36 +0530 Subject: [PATCH 4/6] refactor for coercible --- .../functions/src/datetime/date_trunc.rs | 244 ++++++------------ .../test_files/information_schema.slt | 59 +---- 2 files changed, 95 insertions(+), 208 deletions(-) diff --git a/datafusion/functions/src/datetime/date_trunc.rs b/datafusion/functions/src/datetime/date_trunc.rs index a3a5caa7c8e49..dd6cd3a19f34e 100644 --- a/datafusion/functions/src/datetime/date_trunc.rs +++ b/datafusion/functions/src/datetime/date_trunc.rs @@ -32,17 +32,18 @@ use arrow::array::types::{ TimestampNanosecondType, TimestampSecondType, }; use arrow::array::{Array, ArrayRef, PrimitiveArray}; -use arrow::datatypes::DataType::{self, Null, Time32, Time64, Timestamp, Utf8, Utf8View}; +use arrow::datatypes::DataType::{self, Time32, Time64, Timestamp}; use arrow::datatypes::TimeUnit::{self, Microsecond, Millisecond, Nanosecond, Second}; use datafusion_common::cast::as_primitive_array; +use datafusion_common::types::{NativeType, logical_date, logical_string}; use datafusion_common::{ - DataFusionError, Result, ScalarValue, exec_datafusion_err, exec_err, plan_err, + DataFusionError, Result, ScalarValue, exec_datafusion_err, exec_err, }; -use datafusion_expr::TypeSignature::Exact; use datafusion_expr::sort_properties::{ExprProperties, SortProperties}; use datafusion_expr::{ - ColumnarValue, Documentation, ScalarUDFImpl, Signature, TIMEZONE_WILDCARD, Volatility, + ColumnarValue, Documentation, ScalarUDFImpl, Signature, TypeSignature, Volatility, }; +use datafusion_expr_common::signature::{Coercion, TypeSignatureClass}; use datafusion_macros::user_doc; use chrono::{ @@ -183,55 +184,22 @@ impl DateTruncFunc { Self { signature: Signature::one_of( vec![ - // Timestamp signatures - Exact(vec![Utf8, Timestamp(Nanosecond, None)]), - Exact(vec![Utf8View, Timestamp(Nanosecond, None)]), - Exact(vec![ - Utf8, - Timestamp(Nanosecond, Some(TIMEZONE_WILDCARD.into())), + TypeSignature::Coercible(vec![ + Coercion::new_exact(TypeSignatureClass::Native(logical_string())), + Coercion::new_implicit( + TypeSignatureClass::Timestamp, + // Allow implicit cast from string and date to timestamp for backward compatibility + vec![ + TypeSignatureClass::Native(logical_string()), + TypeSignatureClass::Native(logical_date()), + ], + NativeType::Timestamp(Nanosecond, None), + ), ]), - Exact(vec![ - Utf8View, - Timestamp(Nanosecond, Some(TIMEZONE_WILDCARD.into())), + TypeSignature::Coercible(vec![ + Coercion::new_exact(TypeSignatureClass::Native(logical_string())), + Coercion::new_exact(TypeSignatureClass::Time), ]), - Exact(vec![Utf8, Timestamp(Microsecond, None)]), - Exact(vec![Utf8View, Timestamp(Microsecond, None)]), - Exact(vec![ - Utf8, - Timestamp(Microsecond, Some(TIMEZONE_WILDCARD.into())), - ]), - Exact(vec![ - Utf8View, - Timestamp(Microsecond, Some(TIMEZONE_WILDCARD.into())), - ]), - Exact(vec![Utf8, Timestamp(Millisecond, None)]), - Exact(vec![Utf8View, Timestamp(Millisecond, None)]), - Exact(vec![ - Utf8, - Timestamp(Millisecond, Some(TIMEZONE_WILDCARD.into())), - ]), - Exact(vec![ - Utf8View, - Timestamp(Millisecond, Some(TIMEZONE_WILDCARD.into())), - ]), - Exact(vec![Utf8, Timestamp(Second, None)]), - Exact(vec![Utf8View, Timestamp(Second, None)]), - Exact(vec![ - Utf8, - Timestamp(Second, Some(TIMEZONE_WILDCARD.into())), - ]), - Exact(vec![ - Utf8View, - Timestamp(Second, Some(TIMEZONE_WILDCARD.into())), - ]), - Exact(vec![Utf8, Time64(Nanosecond)]), - Exact(vec![Utf8View, Time64(Nanosecond)]), - Exact(vec![Utf8, Time64(Microsecond)]), - Exact(vec![Utf8View, Time64(Microsecond)]), - Exact(vec![Utf8, Time32(Millisecond)]), - Exact(vec![Utf8View, Time32(Millisecond)]), - Exact(vec![Utf8, Time32(Second)]), - Exact(vec![Utf8View, Time32(Second)]), ], Volatility::Immutable, ), @@ -254,19 +222,10 @@ impl ScalarUDFImpl for DateTruncFunc { } fn return_type(&self, arg_types: &[DataType]) -> Result { - match &arg_types[1] { - Timestamp(Nanosecond, None) | Utf8 | DataType::Date32 | Null => { - Ok(Timestamp(Nanosecond, None)) - } - Timestamp(Nanosecond, tz_opt) => Ok(Timestamp(Nanosecond, tz_opt.clone())), - Timestamp(Microsecond, tz_opt) => Ok(Timestamp(Microsecond, tz_opt.clone())), - Timestamp(Millisecond, tz_opt) => Ok(Timestamp(Millisecond, tz_opt.clone())), - Timestamp(Second, tz_opt) => Ok(Timestamp(Second, tz_opt.clone())), - Time64(tu) => Ok(Time64(*tu)), - Time32(tu) => Ok(Time32(*tu)), - _ => plan_err!( - "The date_trunc function can only accept timestamp or time as the second arg." - ), + if arg_types[1].is_null() { + Ok(Timestamp(Nanosecond, None)) + } else { + Ok(arg_types[1].clone()) } } @@ -290,6 +249,22 @@ impl ScalarUDFImpl for DateTruncFunc { let granularity = DateTruncGranularity::from_str(&granularity_str)?; + // Check upfront if granularity is valid for Time types + let is_time_type = match array { + ColumnarValue::Scalar(ScalarValue::Time64Nanosecond(_)) + | ColumnarValue::Scalar(ScalarValue::Time64Microsecond(_)) + | ColumnarValue::Scalar(ScalarValue::Time32Millisecond(_)) + | ColumnarValue::Scalar(ScalarValue::Time32Second(_)) => true, + ColumnarValue::Array(arr) => matches!(arr.data_type(), Time64(_) | Time32(_)), + _ => false, + }; + if is_time_type && !granularity.valid_for_time() { + return exec_err!( + "date_trunc does not support '{}' granularity for Time types. Valid values are: hour, minute, second, millisecond, microsecond", + granularity_str + ); + } + fn process_array( array: &dyn Array, granularity: DateTruncGranularity, @@ -337,6 +312,10 @@ impl ScalarUDFImpl for DateTruncFunc { } Ok(match array { + ColumnarValue::Scalar(ScalarValue::Null) => { + // NULL input returns NULL timestamp + ColumnarValue::Scalar(ScalarValue::TimestampNanosecond(None, None)) + } ColumnarValue::Scalar(ScalarValue::TimestampNanosecond(v, tz_opt)) => { process_scalar::(v, granularity, tz_opt)? } @@ -350,122 +329,65 @@ impl ScalarUDFImpl for DateTruncFunc { process_scalar::(v, granularity, tz_opt)? } ColumnarValue::Scalar(ScalarValue::Time64Nanosecond(v)) => { - if !granularity.valid_for_time() { - return exec_err!( - "date_trunc does not support '{}' granularity for Time types. Valid values are: hour, minute, second, millisecond, microsecond", - granularity_str - ); - } let truncated = v.map(|val| truncate_time_nanos(val, granularity)); ColumnarValue::Scalar(ScalarValue::Time64Nanosecond(truncated)) } ColumnarValue::Scalar(ScalarValue::Time64Microsecond(v)) => { - if !granularity.valid_for_time() { - return exec_err!( - "date_trunc does not support '{}' granularity for Time types. Valid values are: hour, minute, second, millisecond, microsecond", - granularity_str - ); - } let truncated = v.map(|val| truncate_time_micros(val, granularity)); ColumnarValue::Scalar(ScalarValue::Time64Microsecond(truncated)) } ColumnarValue::Scalar(ScalarValue::Time32Millisecond(v)) => { - if !granularity.valid_for_time() { - return exec_err!( - "date_trunc does not support '{}' granularity for Time types. Valid values are: hour, minute, second, millisecond, microsecond", - granularity_str - ); - } let truncated = v.map(|val| truncate_time_millis(val, granularity)); ColumnarValue::Scalar(ScalarValue::Time32Millisecond(truncated)) } ColumnarValue::Scalar(ScalarValue::Time32Second(v)) => { - if !granularity.valid_for_time() { - return exec_err!( - "date_trunc does not support '{}' granularity for Time types. Valid values are: hour, minute, second, millisecond, microsecond", - granularity_str - ); - } let truncated = v.map(|val| truncate_time_secs(val, granularity)); ColumnarValue::Scalar(ScalarValue::Time32Second(truncated)) } ColumnarValue::Array(array) => { let array_type = array.data_type(); match array_type { - Timestamp(unit, tz_opt) => match unit { - Second => process_array::( - array, - granularity, - tz_opt, - )?, - Millisecond => process_array::( - array, - granularity, - tz_opt, - )?, - Microsecond => process_array::( - array, - granularity, - tz_opt, - )?, - Nanosecond => process_array::( - array, - granularity, - tz_opt, - )?, - }, - Time64(unit) => { - if !granularity.valid_for_time() { - return exec_err!( - "date_trunc does not support '{}' granularity for Time types. Valid values are: hour, minute, second, millisecond, microsecond", - granularity_str - ); - } - match unit { - Nanosecond => { - let arr = - as_primitive_array::(array)?; - let result: PrimitiveArray = - arr.unary(|v| truncate_time_nanos(v, granularity)); - ColumnarValue::Array(Arc::new(result)) - } - Microsecond => { - let arr = - as_primitive_array::(array)?; - let result: PrimitiveArray = - arr.unary(|v| truncate_time_micros(v, granularity)); - ColumnarValue::Array(Arc::new(result)) - } - _ => { - return exec_err!("Unsupported Time64 unit: {:?}", unit); - } - } + Timestamp(Second, tz_opt) => { + process_array::(array, granularity, tz_opt)? + } + Timestamp(Millisecond, tz_opt) => process_array::< + TimestampMillisecondType, + >( + array, granularity, tz_opt + )?, + Timestamp(Microsecond, tz_opt) => process_array::< + TimestampMicrosecondType, + >( + array, granularity, tz_opt + )?, + Timestamp(Nanosecond, tz_opt) => process_array::< + TimestampNanosecondType, + >( + array, granularity, tz_opt + )?, + Time64(Nanosecond) => { + let arr = as_primitive_array::(array)?; + let result: PrimitiveArray = + arr.unary(|v| truncate_time_nanos(v, granularity)); + ColumnarValue::Array(Arc::new(result)) + } + Time64(Microsecond) => { + let arr = as_primitive_array::(array)?; + let result: PrimitiveArray = + arr.unary(|v| truncate_time_micros(v, granularity)); + ColumnarValue::Array(Arc::new(result)) + } + Time32(Millisecond) => { + let arr = as_primitive_array::(array)?; + let result: PrimitiveArray = + arr.unary(|v| truncate_time_millis(v, granularity)); + ColumnarValue::Array(Arc::new(result)) } - Time32(unit) => { - if !granularity.valid_for_time() { - return exec_err!( - "date_trunc does not support '{}' granularity for Time types. Valid values are: hour, minute, second, millisecond, microsecond", - granularity_str - ); - } - match unit { - Millisecond => { - let arr = - as_primitive_array::(array)?; - let result: PrimitiveArray = - arr.unary(|v| truncate_time_millis(v, granularity)); - ColumnarValue::Array(Arc::new(result)) - } - Second => { - let arr = as_primitive_array::(array)?; - let result: PrimitiveArray = - arr.unary(|v| truncate_time_secs(v, granularity)); - ColumnarValue::Array(Arc::new(result)) - } - _ => { - return exec_err!("Unsupported Time32 unit: {:?}", unit); - } - } + Time32(Second) => { + let arr = as_primitive_array::(array)?; + let result: PrimitiveArray = + arr.unary(|v| truncate_time_secs(v, granularity)); + ColumnarValue::Array(Arc::new(result)) } _ => { return exec_err!( diff --git a/datafusion/sqllogictest/test_files/information_schema.slt b/datafusion/sqllogictest/test_files/information_schema.slt index c1b9650ee37a3..646cc3dfd5370 100644 --- a/datafusion/sqllogictest/test_files/information_schema.slt +++ b/datafusion/sqllogictest/test_files/information_schema.slt @@ -793,18 +793,11 @@ string_agg String AGGREGATE query TTTTTTTBTTTT rowsort select * from information_schema.routines where routine_name = 'date_trunc' OR routine_name = 'string_agg' OR routine_name = 'rank' ORDER BY routine_name ---- -datafusion public date_trunc datafusion public date_trunc FUNCTION true Time(Microsecond) SCALAR Truncates a timestamp or time value to a specified precision. date_trunc(precision, expression) -datafusion public date_trunc datafusion public date_trunc FUNCTION true Time(Millisecond) SCALAR Truncates a timestamp or time value to a specified precision. date_trunc(precision, expression) +datafusion public date_trunc datafusion public date_trunc FUNCTION true Date SCALAR Truncates a timestamp or time value to a specified precision. date_trunc(precision, expression) +datafusion public date_trunc datafusion public date_trunc FUNCTION true String SCALAR Truncates a timestamp or time value to a specified precision. date_trunc(precision, expression) datafusion public date_trunc datafusion public date_trunc FUNCTION true Time(Nanosecond) SCALAR Truncates a timestamp or time value to a specified precision. date_trunc(precision, expression) -datafusion public date_trunc datafusion public date_trunc FUNCTION true Time(Second) SCALAR Truncates a timestamp or time value to a specified precision. date_trunc(precision, expression) -datafusion public date_trunc datafusion public date_trunc FUNCTION true Timestamp(Microsecond, None) SCALAR Truncates a timestamp or time value to a specified precision. date_trunc(precision, expression) -datafusion public date_trunc datafusion public date_trunc FUNCTION true Timestamp(Microsecond, Some("+TZ")) SCALAR Truncates a timestamp or time value to a specified precision. date_trunc(precision, expression) -datafusion public date_trunc datafusion public date_trunc FUNCTION true Timestamp(Millisecond, None) SCALAR Truncates a timestamp or time value to a specified precision. date_trunc(precision, expression) -datafusion public date_trunc datafusion public date_trunc FUNCTION true Timestamp(Millisecond, Some("+TZ")) SCALAR Truncates a timestamp or time value to a specified precision. date_trunc(precision, expression) datafusion public date_trunc datafusion public date_trunc FUNCTION true Timestamp(Nanosecond, None) SCALAR Truncates a timestamp or time value to a specified precision. date_trunc(precision, expression) datafusion public date_trunc datafusion public date_trunc FUNCTION true Timestamp(Nanosecond, Some("+TZ")) SCALAR Truncates a timestamp or time value to a specified precision. date_trunc(precision, expression) -datafusion public date_trunc datafusion public date_trunc FUNCTION true Timestamp(Second, None) SCALAR Truncates a timestamp or time value to a specified precision. date_trunc(precision, expression) -datafusion public date_trunc datafusion public date_trunc FUNCTION true Timestamp(Second, Some("+TZ")) SCALAR Truncates a timestamp or time value to a specified precision. date_trunc(precision, expression) datafusion public rank datafusion public rank FUNCTION true NULL WINDOW Returns the rank of the current row within its partition, allowing gaps between ranks. This function provides a ranking similar to `row_number`, but skips ranks for identical values. rank() datafusion public string_agg datafusion public string_agg FUNCTION true String AGGREGATE Concatenates the values of string expressions and places separator values between them. If ordering is required, strings are concatenated in the specified order. This aggregation function can only mix DISTINCT and ORDER BY if the ordering expression is exactly the same as the first argument expression. string_agg([DISTINCT] expression, delimiter [ORDER BY expression]) @@ -817,42 +810,21 @@ false query TTTITTTTBI select * from information_schema.parameters where specific_name = 'date_trunc' OR specific_name = 'string_agg' OR specific_name = 'rank' ORDER BY specific_name, rid, data_type; ---- +datafusion public date_trunc 1 OUT NULL Date NULL false 0 +datafusion public date_trunc 2 IN expression Date NULL false 0 datafusion public date_trunc 1 IN precision String NULL false 0 -datafusion public date_trunc 2 IN expression Time(Microsecond) NULL false 0 -datafusion public date_trunc 1 OUT NULL Time(Microsecond) NULL false 0 datafusion public date_trunc 1 IN precision String NULL false 1 -datafusion public date_trunc 2 IN expression Time(Millisecond) NULL false 1 -datafusion public date_trunc 1 OUT NULL Time(Millisecond) NULL false 1 +datafusion public date_trunc 2 IN expression String NULL false 1 +datafusion public date_trunc 1 OUT NULL String NULL false 1 datafusion public date_trunc 1 IN precision String NULL false 2 datafusion public date_trunc 2 IN expression Time(Nanosecond) NULL false 2 datafusion public date_trunc 1 OUT NULL Time(Nanosecond) NULL false 2 datafusion public date_trunc 1 IN precision String NULL false 3 -datafusion public date_trunc 2 IN expression Time(Second) NULL false 3 -datafusion public date_trunc 1 OUT NULL Time(Second) NULL false 3 +datafusion public date_trunc 2 IN expression Timestamp(Nanosecond, None) NULL false 3 +datafusion public date_trunc 1 OUT NULL Timestamp(Nanosecond, None) NULL false 3 datafusion public date_trunc 1 IN precision String NULL false 4 -datafusion public date_trunc 2 IN expression Timestamp(Microsecond, None) NULL false 4 -datafusion public date_trunc 1 OUT NULL Timestamp(Microsecond, None) NULL false 4 -datafusion public date_trunc 1 IN precision String NULL false 5 -datafusion public date_trunc 2 IN expression Timestamp(Microsecond, Some("+TZ")) NULL false 5 -datafusion public date_trunc 1 OUT NULL Timestamp(Microsecond, Some("+TZ")) NULL false 5 -datafusion public date_trunc 1 IN precision String NULL false 6 -datafusion public date_trunc 1 OUT NULL Timestamp(Millisecond, None) NULL false 6 -datafusion public date_trunc 2 IN expression Timestamp(Millisecond, None) NULL false 6 -datafusion public date_trunc 1 IN precision String NULL false 7 -datafusion public date_trunc 2 IN expression Timestamp(Millisecond, Some("+TZ")) NULL false 7 -datafusion public date_trunc 1 OUT NULL Timestamp(Millisecond, Some("+TZ")) NULL false 7 -datafusion public date_trunc 1 IN precision String NULL false 8 -datafusion public date_trunc 2 IN expression Timestamp(Nanosecond, None) NULL false 8 -datafusion public date_trunc 1 OUT NULL Timestamp(Nanosecond, None) NULL false 8 -datafusion public date_trunc 1 IN precision String NULL false 9 -datafusion public date_trunc 2 IN expression Timestamp(Nanosecond, Some("+TZ")) NULL false 9 -datafusion public date_trunc 1 OUT NULL Timestamp(Nanosecond, Some("+TZ")) NULL false 9 -datafusion public date_trunc 1 IN precision String NULL false 10 -datafusion public date_trunc 2 IN expression Timestamp(Second, None) NULL false 10 -datafusion public date_trunc 1 OUT NULL Timestamp(Second, None) NULL false 10 -datafusion public date_trunc 1 IN precision String NULL false 11 -datafusion public date_trunc 2 IN expression Timestamp(Second, Some("+TZ")) NULL false 11 -datafusion public date_trunc 1 OUT NULL Timestamp(Second, Some("+TZ")) NULL false 11 +datafusion public date_trunc 2 IN expression Timestamp(Nanosecond, Some("+TZ")) NULL false 4 +datafusion public date_trunc 1 OUT NULL Timestamp(Nanosecond, Some("+TZ")) NULL false 4 datafusion public string_agg 2 IN delimiter Null NULL false 0 datafusion public string_agg 1 IN expression String NULL false 0 datafusion public string_agg 1 OUT NULL String NULL false 0 @@ -878,18 +850,11 @@ repeat String 1 OUT 0 query TT??TTT rowsort show functions like 'date_trunc'; ---- -date_trunc Time(Microsecond) [precision, expression] [String, Time(Microsecond)] SCALAR Truncates a timestamp or time value to a specified precision. date_trunc(precision, expression) -date_trunc Time(Millisecond) [precision, expression] [String, Time(Millisecond)] SCALAR Truncates a timestamp or time value to a specified precision. date_trunc(precision, expression) +date_trunc Date [precision, expression] [String, Date] SCALAR Truncates a timestamp or time value to a specified precision. date_trunc(precision, expression) +date_trunc String [precision, expression] [String, String] SCALAR Truncates a timestamp or time value to a specified precision. date_trunc(precision, expression) date_trunc Time(Nanosecond) [precision, expression] [String, Time(Nanosecond)] SCALAR Truncates a timestamp or time value to a specified precision. date_trunc(precision, expression) -date_trunc Time(Second) [precision, expression] [String, Time(Second)] SCALAR Truncates a timestamp or time value to a specified precision. date_trunc(precision, expression) -date_trunc Timestamp(Microsecond, None) [precision, expression] [String, Timestamp(Microsecond, None)] SCALAR Truncates a timestamp or time value to a specified precision. date_trunc(precision, expression) -date_trunc Timestamp(Microsecond, Some("+TZ")) [precision, expression] [String, Timestamp(Microsecond, Some("+TZ"))] SCALAR Truncates a timestamp or time value to a specified precision. date_trunc(precision, expression) -date_trunc Timestamp(Millisecond, None) [precision, expression] [String, Timestamp(Millisecond, None)] SCALAR Truncates a timestamp or time value to a specified precision. date_trunc(precision, expression) -date_trunc Timestamp(Millisecond, Some("+TZ")) [precision, expression] [String, Timestamp(Millisecond, Some("+TZ"))] SCALAR Truncates a timestamp or time value to a specified precision. date_trunc(precision, expression) date_trunc Timestamp(Nanosecond, None) [precision, expression] [String, Timestamp(Nanosecond, None)] SCALAR Truncates a timestamp or time value to a specified precision. date_trunc(precision, expression) date_trunc Timestamp(Nanosecond, Some("+TZ")) [precision, expression] [String, Timestamp(Nanosecond, Some("+TZ"))] SCALAR Truncates a timestamp or time value to a specified precision. date_trunc(precision, expression) -date_trunc Timestamp(Second, None) [precision, expression] [String, Timestamp(Second, None)] SCALAR Truncates a timestamp or time value to a specified precision. date_trunc(precision, expression) -date_trunc Timestamp(Second, Some("+TZ")) [precision, expression] [String, Timestamp(Second, Some("+TZ"))] SCALAR Truncates a timestamp or time value to a specified precision. date_trunc(precision, expression) statement ok show functions From 720e5c8ea169d7c2d97456bab75646d003f65767 Mon Sep 17 00:00:00 2001 From: Kumar Ujjawal Date: Mon, 5 Jan 2026 15:33:37 +0530 Subject: [PATCH 5/6] regenerate docs --- docs/source/user-guide/sql/scalar_functions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/user-guide/sql/scalar_functions.md b/docs/source/user-guide/sql/scalar_functions.md index ecd557a821b72..4bec2ce015b7d 100644 --- a/docs/source/user-guide/sql/scalar_functions.md +++ b/docs/source/user-guide/sql/scalar_functions.md @@ -2579,7 +2579,7 @@ date_trunc(precision, expression) - millisecond / MILLISECOND - microsecond / MICROSECOND -- **expression**: Timestamp or Time expression to operate on. Can be a constant, column, or function. +- **expression**: Timestamp or time expression to operate on. Can be a constant, column, or function. #### Aliases From d8d026b0766748e46e1b660fc3c7fdc4d21bb6de Mon Sep 17 00:00:00 2001 From: Kumar Ujjawal Date: Mon, 5 Jan 2026 17:17:51 +0530 Subject: [PATCH 6/6] largeutf8 suport and use columnar data type --- datafusion/functions/src/datetime/date_trunc.rs | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/datafusion/functions/src/datetime/date_trunc.rs b/datafusion/functions/src/datetime/date_trunc.rs index dd6cd3a19f34e..8c8a4a1c1b771 100644 --- a/datafusion/functions/src/datetime/date_trunc.rs +++ b/datafusion/functions/src/datetime/date_trunc.rs @@ -241,6 +241,9 @@ impl ScalarUDFImpl for DateTruncFunc { { v.to_lowercase() } else if let ColumnarValue::Scalar(ScalarValue::Utf8View(Some(v))) = granularity + { + v.to_lowercase() + } else if let ColumnarValue::Scalar(ScalarValue::LargeUtf8(Some(v))) = granularity { v.to_lowercase() } else { @@ -250,14 +253,7 @@ impl ScalarUDFImpl for DateTruncFunc { let granularity = DateTruncGranularity::from_str(&granularity_str)?; // Check upfront if granularity is valid for Time types - let is_time_type = match array { - ColumnarValue::Scalar(ScalarValue::Time64Nanosecond(_)) - | ColumnarValue::Scalar(ScalarValue::Time64Microsecond(_)) - | ColumnarValue::Scalar(ScalarValue::Time32Millisecond(_)) - | ColumnarValue::Scalar(ScalarValue::Time32Second(_)) => true, - ColumnarValue::Array(arr) => matches!(arr.data_type(), Time64(_) | Time32(_)), - _ => false, - }; + let is_time_type = matches!(array.data_type(), Time64(_) | Time32(_)); if is_time_type && !granularity.valid_for_time() { return exec_err!( "date_trunc does not support '{}' granularity for Time types. Valid values are: hour, minute, second, millisecond, microsecond",