This is an automated email from the ASF dual-hosted git repository. github-merge-queue[bot] pushed a commit to branch gh-readonly-queue/main/pr-22303-070d0135330a1d084c3b4d510c782079d2cf60f5 in repository https://gitbox.apache.org/repos/asf/datafusion.git
commit 11a79a6217891cff67c8f963265f5673e6f63267 Author: Sean Kenneth Doherty <[email protected]> AuthorDate: Wed May 27 17:06:07 2026 -0500 Guard date_trunc lower-bound truncation (#22303) ## Which issue does this PR close? - Closes #22214. ## Rationale for this change Truncating a near-lower-bound nanosecond timestamp to a coarser calendar unit can produce a timestamp outside Arrow's nanosecond range. `date_trunc_coarse` already used fallible timestamp conversion, but it unwrapped the final conversion back to nanoseconds and could panic. ## What changes are included in this PR? - Convert the final out-of-range truncation case into a DataFusion execution error. - Add a small string helper for user-facing granularity names in the error. - Add a unit regression and a sqllogictest regression for lower-bound nanosecond truncation. ## Are these changes tested? Yes: - `cargo fmt --check` - `git diff --check` - `CARGO_TARGET_DIR=/home/sean/Projects/datafusion-runtime-set-nonascii/target CARGO_BUILD_JOBS=2 cargo test -p datafusion-functions --lib date_trunc_out_of_range_lower_bound_returns_error` - `CARGO_TARGET_DIR=/home/sean/Projects/datafusion-runtime-set-nonascii/target CARGO_BUILD_JOBS=2 cargo test -p datafusion-sqllogictest --test sqllogictests -- date_trunc_boundaries.slt` - `CARGO_TARGET_DIR=/home/sean/Projects/datafusion-runtime-set-nonascii/target CARGO_BUILD_JOBS=2 cargo clippy -p datafusion-functions --lib -- -D warnings` ## Are there any user-facing changes? Instead of panicking, out-of-range `date_trunc` results now return an error. --- datafusion/functions/src/datetime/date_trunc.rs | 40 ++++++++++++++++++++-- .../test_files/datetime/timestamps.slt | 7 ++++ 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/datafusion/functions/src/datetime/date_trunc.rs b/datafusion/functions/src/datetime/date_trunc.rs index 784f593c25..a4b244405c 100644 --- a/datafusion/functions/src/datetime/date_trunc.rs +++ b/datafusion/functions/src/datetime/date_trunc.rs @@ -15,6 +15,7 @@ // specific language governing permissions and limitations // under the License. +use std::fmt; use std::num::NonZeroI64; use std::ops::{Add, Sub}; use std::str::FromStr; @@ -135,6 +136,24 @@ impl DateTruncGranularity { } } +impl fmt::Display for DateTruncGranularity { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let value = match self { + Self::Microsecond => "microsecond", + Self::Millisecond => "millisecond", + Self::Second => "second", + Self::Minute => "minute", + Self::Hour => "hour", + Self::Day => "day", + Self::Week => "week", + Self::Month => "month", + Self::Quarter => "quarter", + Self::Year => "year", + }; + f.write_str(value) + } +} + #[user_doc( doc_section(label = "Time and Date Functions"), description = "Truncates a timestamp or time value to a specified precision.", @@ -629,6 +648,7 @@ fn date_trunc_coarse( value: i64, tz: Option<Tz>, ) -> Result<i64> { + let input = value; let value = match tz { Some(tz) => { // Use chrono DateTime<Tz> to clear the various fields because need to clear per timezone, @@ -645,8 +665,11 @@ fn date_trunc_coarse( } }?; - // `with_x(0)` are infallible because `0` are always a valid - Ok(value.unwrap()) + value.ok_or_else(|| { + exec_datafusion_err!( + "Timestamp {input} out of range after truncating to {granularity}" + ) + }) } /// Fast path for fine granularities (hour and smaller) that can be handled @@ -879,6 +902,19 @@ mod tests { }); } + #[test] + fn date_trunc_out_of_range_lower_bound_returns_error() { + let timestamp = string_to_timestamp_nanos("1677-09-22T00:00:00Z").unwrap(); + let err = date_trunc_coarse(DateTruncGranularity::Year, timestamp, None) + .unwrap_err() + .to_string(); + + assert!( + err.contains("out of range after truncating to year"), + "{err}" + ); + } + #[test] fn test_date_trunc_timezones() { let cases = [ diff --git a/datafusion/sqllogictest/test_files/datetime/timestamps.slt b/datafusion/sqllogictest/test_files/datetime/timestamps.slt index e045abc0f2..958ff86b4f 100644 --- a/datafusion/sqllogictest/test_files/datetime/timestamps.slt +++ b/datafusion/sqllogictest/test_files/datetime/timestamps.slt @@ -5345,6 +5345,13 @@ SELECT to_timestamp_millis(arrow_cast(-1.9, 'Float64')); ---- 1969-12-31T23:59:59.999 +# Regression test for https://github.com/apache/datafusion/issues/22214 +query error .*out of range after truncating to year +SELECT date_trunc( + 'year', + arrow_cast(TIMESTAMP '1677-09-22 00:00:00', 'Timestamp(Nanosecond, None)') +); + ########## ## Common timestamp data --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
