Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 25 additions & 6 deletions datafusion/spark/src/function/datetime/next_day.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ use std::sync::Arc;

use arrow::array::{ArrayRef, AsArray, Date32Array, StringArrayType};
use arrow::datatypes::{DataType, Date32Type, Field, FieldRef};
use chrono::{Datelike, Duration, Weekday};
use chrono::{Datelike, Weekday};
use datafusion_common::{Result, ScalarValue, exec_err, internal_err};
use datafusion_expr::{
ColumnarValue, ReturnFieldArgs, ScalarFunctionArgs, ScalarUDFImpl, Signature,
Expand Down Expand Up @@ -229,11 +229,17 @@ fn spark_next_day(days: i32, day_of_week: &str) -> Option<i32> {
if let Some(day_of_week) = day_of_week {
let day_of_week = day_of_week.parse::<Weekday>();
match day_of_week {
Ok(day_of_week) => Some(Date32Type::from_naive_date(
date + Duration::days(
(7 - date.weekday().days_since(day_of_week)) as i64,
),
)),
Ok(day_of_week) => {
// Advance 1..=7 days from `days` to the next occurrence of
// `day_of_week`. Compute the result on the epoch day directly
// instead of constructing a `NaiveDate`: the result can land
// past `NaiveDate::MAX` (epoch day 95026236), and building that
// date panics (`NaiveDate + TimeDelta overflowed`). Spark's
// `DateTimeUtils.getNextDateForDayOfWeek` is pure `Int`
// arithmetic and keeps producing a value up to `Int.MaxValue`.
let delta = 7 - date.weekday().days_since(day_of_week) as i32;
days.checked_add(delta)
}
Err(_) => {
// TODO: if spark.sql.ansi.enabled is false,
// returns NULL instead of an error for a malformed dayOfWeek.
Expand Down Expand Up @@ -285,4 +291,17 @@ mod tests {
let monday = 19723; // 2024-01-01
assert_eq!(spark_next_day(monday, " MO "), None);
}

#[test]
fn next_day_handles_far_future_start_dates() {
// Regression for #23891: for start dates near the end of the
// representable `Date32` range, the next occurrence can land past
// `chrono::NaiveDate::MAX` (epoch day 95026236). Computing the result
// on the epoch day directly (as Spark does) must return a value rather
// than panicking with `NaiveDate + TimeDelta overflowed`.
//
// 95026236 is a Monday, so `next_day(.., "Mon")` advances a full week.
assert_eq!(spark_next_day(95026236, "Mon"), Some(95026243));
assert_eq!(spark_next_day(95026230, "Tue"), Some(95026237));
}
}
15 changes: 15 additions & 0 deletions datafusion/sqllogictest/test_files/spark/datetime/next_day.slt
Original file line number Diff line number Diff line change
Expand Up @@ -85,3 +85,18 @@ FROM VALUES
NULL
NULL
NULL

# https://github.com/apache/datafusion/issues/23891
# Far-future start dates whose next occurrence lands past chrono::NaiveDate::MAX
# (epoch day 95026236) must still return a value, matching Spark's integer
# arithmetic, rather than panicking. Cast the Date32 result to Int32 to assert
# the epoch day directly (these dates are past the printable range).
query I
SELECT arrow_cast(next_day(arrow_cast(95026236, 'Date32'), 'Mon'::string), 'Int32');
----
95026243

query I
SELECT arrow_cast(next_day(arrow_cast(95026230, 'Date32'), 'Tue'::string), 'Int32');
----
95026237
Loading