Skip to content
Open
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
86 changes: 78 additions & 8 deletions arrow-arith/src/temporal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@ pub enum DatePart {
DayOfWeekSunday0,
/// Day of the week, in range `0..=6`, where Monday is `0`
DayOfWeekMonday0,
/// Day of the week, in range `1..=7`, where Sunday is `1`
DayOfWeekSunday1,
/// ISO day of the week, in range `1..=7`, where Monday is `1`
DayOfWeekMonday1,
/// Day of year, in range `1..=366`
DayOfYear,
/// Hour of the day, in range `0..=23`
Expand Down Expand Up @@ -100,11 +104,6 @@ impl std::fmt::Display for DatePart {
/// - `century`, `decade`, `millennium` — no matching [`DatePart`] variant.
/// - `timezone`, `timezone_hour`, `timezone_minute` — not modeled by
/// [`DatePart`].
/// - `isodow` — PostgreSQL's `isodow` returns `1..=7` (Mon=1), but the
/// closest variant ([`DatePart::DayOfWeekMonday0`]) returns `0..=6`.
/// Accepting it here would silently shift the value range; callers that
/// need the PostgreSQL semantic should map the alias themselves and
/// add `1` to the extracted value.
impl FromStr for DatePart {
type Err = ArrowError;

Expand All @@ -118,6 +117,8 @@ impl FromStr for DatePart {
"isoweek" => Self::WeekISO,
"d" | "day" | "days" => Self::Day,
"dow" | "dayofweek" => Self::DayOfWeekSunday0,
"dayofweek1" => Self::DayOfWeekSunday1,
Copy link
Copy Markdown
Contributor

@Jefffrey Jefffrey May 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dayofweek1 seems non-standard here?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I'll take a look at similar enums in other projects. The reason to add it here is for upstream DataFusion Spark temporal function support.

sparkdow?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MySQL/BigQuery/SQL Server/Oracle have dow Sunday = 1 as well

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I'll take a look at similar enums in other projects. The reason to add it here is for upstream DataFusion Spark temporal function support.

sparkdow?

How would this look like downstream? As in, would DataFusion need to provide sparkdow or dayofweek1 as a string to be parsed here? Is there no way to provide the enum variant directly, for example?

"isodow" => Self::DayOfWeekMonday1,
"doy" | "dayofyear" => Self::DayOfYear,
"h" | "hr" | "hrs" | "hour" | "hours" => Self::Hour,
"m" | "min" | "mins" | "minute" | "minutes" => Self::Minute,
Expand Down Expand Up @@ -158,6 +159,8 @@ where
DatePart::Day => |d| d.day() as i32,
DatePart::DayOfWeekSunday0 => |d| d.num_days_from_sunday(),
DatePart::DayOfWeekMonday0 => |d| d.num_days_from_monday(),
DatePart::DayOfWeekSunday1 => |d| d.num_days_from_sunday() + 1,
DatePart::DayOfWeekMonday1 => |d| d.num_days_from_monday() + 1,
DatePart::DayOfYear => |d| d.ordinal() as i32,
DatePart::Hour => |d| d.hour() as i32,
DatePart::Minute => |d| d.minute() as i32,
Expand Down Expand Up @@ -500,6 +503,8 @@ impl ExtractDatePartExt for PrimitiveArray<IntervalYearMonthType> {
| DatePart::Day
| DatePart::DayOfWeekSunday0
| DatePart::DayOfWeekMonday0
| DatePart::DayOfWeekSunday1
| DatePart::DayOfWeekMonday1
| DatePart::DayOfYear
| DatePart::Hour
| DatePart::Minute
Expand Down Expand Up @@ -536,6 +541,8 @@ impl ExtractDatePartExt for PrimitiveArray<IntervalDayTimeType> {
| DatePart::Month
| DatePart::DayOfWeekSunday0
| DatePart::DayOfWeekMonday0
| DatePart::DayOfWeekSunday1
| DatePart::DayOfWeekMonday1
| DatePart::DayOfYear => {
return_compute_error_with!(format!("{part} does not support"), self.data_type())
}
Expand Down Expand Up @@ -578,6 +585,8 @@ impl ExtractDatePartExt for PrimitiveArray<IntervalMonthDayNanoType> {
| DatePart::YearISO
| DatePart::DayOfWeekSunday0
| DatePart::DayOfWeekMonday0
| DatePart::DayOfWeekSunday1
| DatePart::DayOfWeekMonday1
| DatePart::DayOfYear => {
return_compute_error_with!(format!("{part} does not support"), self.data_type())
}
Expand Down Expand Up @@ -610,6 +619,8 @@ impl ExtractDatePartExt for PrimitiveArray<DurationSecondType> {
| DatePart::Month
| DatePart::DayOfWeekSunday0
| DatePart::DayOfWeekMonday0
| DatePart::DayOfWeekSunday1
| DatePart::DayOfWeekMonday1
| DatePart::DayOfYear => {
return_compute_error_with!(format!("{part} does not support"), self.data_type())
}
Expand Down Expand Up @@ -642,6 +653,8 @@ impl ExtractDatePartExt for PrimitiveArray<DurationMillisecondType> {
| DatePart::Month
| DatePart::DayOfWeekSunday0
| DatePart::DayOfWeekMonday0
| DatePart::DayOfWeekSunday1
| DatePart::DayOfWeekMonday1
| DatePart::DayOfYear => {
return_compute_error_with!(format!("{part} does not support"), self.data_type())
}
Expand Down Expand Up @@ -674,6 +687,8 @@ impl ExtractDatePartExt for PrimitiveArray<DurationMicrosecondType> {
| DatePart::Month
| DatePart::DayOfWeekSunday0
| DatePart::DayOfWeekMonday0
| DatePart::DayOfWeekSunday1
| DatePart::DayOfWeekMonday1
| DatePart::DayOfYear => {
return_compute_error_with!(format!("{part} does not support"), self.data_type())
}
Expand Down Expand Up @@ -706,6 +721,8 @@ impl ExtractDatePartExt for PrimitiveArray<DurationNanosecondType> {
| DatePart::Month
| DatePart::DayOfWeekSunday0
| DatePart::DayOfWeekMonday0
| DatePart::DayOfWeekSunday1
| DatePart::DayOfWeekMonday1
| DatePart::DayOfYear => {
return_compute_error_with!(format!("{part} does not support"), self.data_type())
}
Expand Down Expand Up @@ -985,6 +1002,46 @@ mod tests {
assert_eq!(2, b.value(2));
}

#[test]
fn test_temporal_array_date64_dayofweek1() {
//1514764800000 -> 2018-01-01 (Monday)
//1550636625000 -> 2019-02-20 (Wednesday)
//1483228800000 -> 2017-01-01 (Sunday)
let a: PrimitiveArray<Date64Type> = vec![
Some(1514764800000),
None,
Some(1550636625000),
Some(1483228800000),
]
.into();

let b = date_part_primitive(&a, DatePart::DayOfWeekSunday1).unwrap();
assert_eq!(2, b.value(0));
assert!(!b.is_valid(1));
assert_eq!(4, b.value(2));
assert_eq!(1, b.value(3));
}

#[test]
fn test_temporal_array_date64_isodow() {
//1514764800000 -> 2018-01-01 (Monday)
//1550636625000 -> 2019-02-20 (Wednesday)
//1483228800000 -> 2017-01-01 (Sunday)
let a: PrimitiveArray<Date64Type> = vec![
Some(1514764800000),
None,
Some(1550636625000),
Some(1483228800000),
]
.into();

let b = date_part_primitive(&a, DatePart::DayOfWeekMonday1).unwrap();
assert_eq!(1, b.value(0));
assert!(!b.is_valid(1));
assert_eq!(3, b.value(2));
assert_eq!(7, b.value(3));
}

#[test]
fn test_temporal_array_date64_weekday0() {
//1483228800000 -> 2017-01-01 (Sunday)
Expand Down Expand Up @@ -1579,11 +1636,15 @@ mod tests {
let invalid_parts = [
DatePart::Quarter,
DatePart::Year,
DatePart::YearISO,
DatePart::Month,
DatePart::Week,
DatePart::WeekISO,
DatePart::Day,
DatePart::DayOfWeekSunday0,
DatePart::DayOfWeekMonday0,
DatePart::DayOfWeekSunday1,
DatePart::DayOfWeekMonday1,
DatePart::DayOfYear,
];

Expand Down Expand Up @@ -1813,8 +1874,12 @@ mod tests {
fn ensure_returns_error(array: &dyn Array) {
let invalid_parts = [
DatePart::Quarter,
DatePart::YearISO,
DatePart::WeekISO,
DatePart::DayOfWeekSunday0,
DatePart::DayOfWeekMonday0,
DatePart::DayOfWeekSunday1,
DatePart::DayOfWeekMonday1,
DatePart::DayOfYear,
];

Expand Down Expand Up @@ -1956,10 +2021,14 @@ mod tests {
fn ensure_returns_error(array: &dyn Array) {
let invalid_parts = [
DatePart::Year,
DatePart::YearISO,
DatePart::Quarter,
DatePart::Month,
DatePart::WeekISO,
DatePart::DayOfWeekSunday0,
DatePart::DayOfWeekMonday0,
DatePart::DayOfWeekSunday1,
DatePart::DayOfWeekMonday1,
DatePart::DayOfYear,
];

Expand Down Expand Up @@ -2157,6 +2226,10 @@ mod tests {
("day", DatePart::Day),
("dow", DatePart::DayOfWeekSunday0),
("DayOfWeek", DatePart::DayOfWeekSunday0),
("dayofweek1", DatePart::DayOfWeekSunday1),
("DAYOFWEEK1", DatePart::DayOfWeekSunday1),
("isodow", DatePart::DayOfWeekMonday1),
("ISODOW", DatePart::DayOfWeekMonday1),
("doy", DatePart::DayOfYear),
("DayOfYear", DatePart::DayOfYear),
("h", DatePart::Hour),
Expand Down Expand Up @@ -2195,8 +2268,6 @@ mod tests {
#[test]
fn test_date_part_from_str_unknown() {
// Names intentionally rejected — see the FromStr doc comment for why.
// `isodow` is here because mapping it to `DayOfWeekMonday0` would
// silently shift the value range vs. PostgreSQL's `isodow` (1..=7).
let unknown = [
"epoch",
"century",
Expand All @@ -2205,7 +2276,6 @@ mod tests {
"timezone",
"timezone_hour",
"timezone_minute",
"isodow",
// Whitespace is not trimmed — pin this so the behavior doesn't
// change silently. Callers should trim before parsing.
" year ",
Expand Down
Loading