This is an automated email from the ASF dual-hosted git repository.

alamb pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/arrow-rs.git


The following commit(s) were added to refs/heads/main by this push:
     new ac640da5db Avoid panic on Date32 overflow (#9144)
ac640da5db is described below

commit ac640da5db0a749b895bf6617f323d1f57ae21e2
Author: cht42 <[email protected]>
AuthorDate: Sat Jan 17 20:23:18 2026 +0400

    Avoid panic on Date32 overflow (#9144)
    
    # Which issue does this PR close?
    
    
    This PR does the same thing as what was done in #7737 to handle overflow
    errors gracely instead of panicking.
    
    - Part of #4456.
    
    # Rationale for this change
    
    <!--
    Why are you proposing this change? If this is already explained clearly
    in the issue then this section is not needed.
    Explaining clearly why changes are proposed helps reviewers understand
    your changes and offer better suggestions for fixes.
    -->
    
    # What changes are included in this PR?
    
    <!--
    There is no need to duplicate the description in the issue here but it
    is sometimes worth providing a summary of the individual changes in this
    PR.
    -->
    
    # Are these changes tested?
    yes
    
    # Are there any user-facing changes?
    
    <!--
    If there are user-facing changes then we may require documentation to be
    updated before approving the PR.
    
    If there are any breaking changes to public APIs, please call them out.
    -->
---
 arrow-arith/src/numeric.rs                     | 582 +++++++++++++++----------
 arrow-array/src/delta.rs                       | 201 ++++-----
 arrow-array/src/types.rs                       | 203 +++++++--
 parquet-variant-compute/src/unshred_variant.rs |   4 +-
 parquet-variant-compute/src/variant_array.rs   |   2 +-
 5 files changed, 619 insertions(+), 373 deletions(-)

diff --git a/arrow-arith/src/numeric.rs b/arrow-arith/src/numeric.rs
index 6c6d4c8999..a57ba67544 100644
--- a/arrow-arith/src/numeric.rs
+++ b/arrow-arith/src/numeric.rs
@@ -548,94 +548,74 @@ trait DateOp: ArrowTemporalType {
     ) -> Result<Self::Native, ArrowError>;
 }
 
-impl DateOp for Date32Type {
-    fn add_year_month(left: Self::Native, right: i32) -> Result<Self::Native, 
ArrowError> {
-        // Date32Type functions don't have _opt variants and should be safe
-        Ok(Self::add_year_months(left, right))
-    }
+macro_rules! date {
+    ($t:ty) => {
+        impl DateOp for $t {
+            fn add_year_month(left: Self::Native, right: i32) -> 
Result<Self::Native, ArrowError> {
+                Self::add_year_months_opt(left, right).ok_or_else(|| {
+                    ArrowError::ComputeError(format!(
+                        "Date arithmetic overflow: {left} + {right} months"
+                    ))
+                })
+            }
 
-    fn add_day_time(
-        left: Self::Native,
-        right: IntervalDayTime,
-    ) -> Result<Self::Native, ArrowError> {
-        Ok(Self::add_day_time(left, right))
-    }
+            fn add_day_time(
+                left: Self::Native,
+                right: IntervalDayTime,
+            ) -> Result<Self::Native, ArrowError> {
+                Self::add_day_time_opt(left, right).ok_or_else(|| {
+                    ArrowError::ComputeError(format!(
+                        "Date arithmetic overflow: {left} + {right:?}"
+                    ))
+                })
+            }
 
-    fn add_month_day_nano(
-        left: Self::Native,
-        right: IntervalMonthDayNano,
-    ) -> Result<Self::Native, ArrowError> {
-        Ok(Self::add_month_day_nano(left, right))
-    }
+            fn add_month_day_nano(
+                left: Self::Native,
+                right: IntervalMonthDayNano,
+            ) -> Result<Self::Native, ArrowError> {
+                Self::add_month_day_nano_opt(left, right).ok_or_else(|| {
+                    ArrowError::ComputeError(format!(
+                        "Date arithmetic overflow: {left} + {right:?}"
+                    ))
+                })
+            }
 
-    fn sub_year_month(left: Self::Native, right: i32) -> Result<Self::Native, 
ArrowError> {
-        Ok(Self::subtract_year_months(left, right))
-    }
+            fn sub_year_month(left: Self::Native, right: i32) -> 
Result<Self::Native, ArrowError> {
+                Self::subtract_year_months_opt(left, right).ok_or_else(|| {
+                    ArrowError::ComputeError(format!(
+                        "Date arithmetic overflow: {left} - {right} months"
+                    ))
+                })
+            }
 
-    fn sub_day_time(
-        left: Self::Native,
-        right: IntervalDayTime,
-    ) -> Result<Self::Native, ArrowError> {
-        Ok(Self::subtract_day_time(left, right))
-    }
+            fn sub_day_time(
+                left: Self::Native,
+                right: IntervalDayTime,
+            ) -> Result<Self::Native, ArrowError> {
+                Self::subtract_day_time_opt(left, right).ok_or_else(|| {
+                    ArrowError::ComputeError(format!(
+                        "Date arithmetic overflow: {left} - {right:?}"
+                    ))
+                })
+            }
 
-    fn sub_month_day_nano(
-        left: Self::Native,
-        right: IntervalMonthDayNano,
-    ) -> Result<Self::Native, ArrowError> {
-        Ok(Self::subtract_month_day_nano(left, right))
-    }
+            fn sub_month_day_nano(
+                left: Self::Native,
+                right: IntervalMonthDayNano,
+            ) -> Result<Self::Native, ArrowError> {
+                Self::subtract_month_day_nano_opt(left, right).ok_or_else(|| {
+                    ArrowError::ComputeError(format!(
+                        "Date arithmetic overflow: {left} - {right:?}"
+                    ))
+                })
+            }
+        }
+    };
 }
 
-impl DateOp for Date64Type {
-    fn add_year_month(left: Self::Native, right: i32) -> Result<Self::Native, 
ArrowError> {
-        Self::add_year_months_opt(left, right).ok_or_else(|| {
-            ArrowError::ComputeError(format!("Date arithmetic overflow: {left} 
+ {right} months",))
-        })
-    }
-
-    fn add_day_time(
-        left: Self::Native,
-        right: IntervalDayTime,
-    ) -> Result<Self::Native, ArrowError> {
-        Self::add_day_time_opt(left, right).ok_or_else(|| {
-            ArrowError::ComputeError(format!("Date arithmetic overflow: {left} 
+ {right:?}"))
-        })
-    }
-
-    fn add_month_day_nano(
-        left: Self::Native,
-        right: IntervalMonthDayNano,
-    ) -> Result<Self::Native, ArrowError> {
-        Self::add_month_day_nano_opt(left, right).ok_or_else(|| {
-            ArrowError::ComputeError(format!("Date arithmetic overflow: {left} 
+ {right:?}"))
-        })
-    }
-
-    fn sub_year_month(left: Self::Native, right: i32) -> Result<Self::Native, 
ArrowError> {
-        Self::subtract_year_months_opt(left, right).ok_or_else(|| {
-            ArrowError::ComputeError(format!("Date arithmetic overflow: {left} 
- {right} months",))
-        })
-    }
-
-    fn sub_day_time(
-        left: Self::Native,
-        right: IntervalDayTime,
-    ) -> Result<Self::Native, ArrowError> {
-        Self::subtract_day_time_opt(left, right).ok_or_else(|| {
-            ArrowError::ComputeError(format!("Date arithmetic overflow: {left} 
- {right:?}"))
-        })
-    }
-
-    fn sub_month_day_nano(
-        left: Self::Native,
-        right: IntervalMonthDayNano,
-    ) -> Result<Self::Native, ArrowError> {
-        Self::subtract_month_day_nano_opt(left, right).ok_or_else(|| {
-            ArrowError::ComputeError(format!("Date arithmetic overflow: {left} 
- {right:?}"))
-        })
-    }
-}
+date!(Date32Type);
+date!(Date64Type);
 
 /// Arithmetic trait for interval arrays
 trait IntervalOp: ArrowPrimitiveType {
@@ -947,20 +927,21 @@ mod tests {
     // The valid date range of NaiveDate is from January 1, -262143 to 
December 31, 262142 (Gregorian calendar).
     const MAX_VALID_DATE: NaiveDate = NaiveDate::from_ymd_opt(262142, 12, 
31).unwrap();
     const MIN_VALID_DATE: NaiveDate = NaiveDate::from_ymd_opt(-262143, 1, 
1).unwrap();
-    const MAX_VALID_MILLIS: i64 = MAX_VALID_DATE
-        .signed_duration_since(EPOCH)
-        .num_milliseconds();
-    const MIN_VALID_MILLIS: i64 = MIN_VALID_DATE
-        .signed_duration_since(EPOCH)
-        .num_milliseconds();
+    const MAX_VALID_MILLIS: i64 = date_to_millis(MAX_VALID_DATE);
+    const MIN_VALID_MILLIS: i64 = date_to_millis(MIN_VALID_DATE);
+    const MAX_VALID_DAYS: i32 = date_to_days(MAX_VALID_DATE);
+    const MIN_VALID_DAYS: i32 = date_to_days(MIN_VALID_DATE);
     const EPOCH: NaiveDate = NaiveDate::from_ymd_opt(1970, 1, 1).unwrap();
-    const YEAR_2000_MILLIS: i64 = date_to_millis(2000, 1, 1);
+    const YEAR_2000: NaiveDate = NaiveDate::from_ymd_opt(2000, 1, 1).unwrap();
 
-    const fn date_to_millis(year: i32, month: u32, day: u32) -> i64 {
-        let date = NaiveDate::from_ymd_opt(year, month, day).unwrap();
+    const fn date_to_millis(date: NaiveDate) -> i64 {
         date.signed_duration_since(EPOCH).num_milliseconds()
     }
 
+    const fn date_to_days(date: NaiveDate) -> i32 {
+        date.signed_duration_since(EPOCH).num_days() as i32
+    }
+
     fn test_neg_primitive<T: ArrowPrimitiveType>(
         input: &[T::Native],
         out: Result<&[T::Native], &str>,
@@ -1662,9 +1643,29 @@ mod tests {
     }
 
     #[test]
-    fn test_date64_to_naive_date_opt_boundaries() {
-        use arrow_array::types::Date64Type;
+    fn test_date32_to_naive_date_opt_boundaries() {
+        assert_eq!(MAX_VALID_DAYS, 95026236);
+        assert_eq!(MIN_VALID_DAYS, -96465292);
+
+        // Valid boundary dates work
+        assert!(Date32Type::to_naive_date_opt(MAX_VALID_DAYS).is_some());
+        assert!(Date32Type::to_naive_date_opt(MIN_VALID_DAYS).is_some());
+
+        // Beyond boundaries fail
+        assert!(Date32Type::to_naive_date_opt(MAX_VALID_DAYS + 1).is_none());
+        assert!(Date32Type::to_naive_date_opt(MIN_VALID_DAYS - 1).is_none());
+
+        // Extreme values fail
+        assert!(Date32Type::to_naive_date_opt(i32::MAX).is_none());
+        assert!(Date32Type::to_naive_date_opt(i32::MIN).is_none());
 
+        // Common values work
+        assert!(Date32Type::to_naive_date_opt(0).is_some());
+        
assert!(Date32Type::to_naive_date_opt(date_to_days(YEAR_2000)).is_some());
+    }
+
+    #[test]
+    fn test_date64_to_naive_date_opt_boundaries() {
         const MS_PER_DAY: i64 = 24 * 60 * 60 * 1000;
 
         // Verify boundary millisecond values
@@ -1685,180 +1686,303 @@ mod tests {
 
         // Common values work
         assert!(Date64Type::to_naive_date_opt(0).is_some());
-        assert!(Date64Type::to_naive_date_opt(YEAR_2000_MILLIS).is_some());
+        
assert!(Date64Type::to_naive_date_opt(date_to_millis(YEAR_2000)).is_some());
     }
 
-    fn test_year_month_op<F>(op: F, op_name: &str)
-    where
-        F: Fn(i64, i32) -> Option<i64>,
-    {
-        // Normal operations succeed
-        assert!(op(YEAR_2000_MILLIS, 120).is_some(), "{op_name}: normal add");
-        assert!(
-            op(YEAR_2000_MILLIS, 0).is_some(),
-            "{op_name}: zero interval"
-        );
+    macro_rules! test_year_month_ops {
+        ($type:ty, $date_fn:expr) => {{
+            let date = $date_fn(YEAR_2000);
 
-        // Large but valid years work
-        let large_year = date_to_millis(5000, 1, 1);
-        let neg_year = date_to_millis(-5000, 12, 31);
-        assert!(op(large_year, 12).is_some(), "{op_name}: large year");
-        assert!(op(neg_year, -12).is_some(), "{op_name}: negative year");
+            // Normal operations succeed
+            assert!(
+                <$type>::add_year_months_opt(date, 120).is_some(),
+                "add_year_months: normal add"
+            );
+            assert!(
+                <$type>::add_year_months_opt(date, 0).is_some(),
+                "add_year_months: zero interval"
+            );
+            assert!(
+                <$type>::subtract_year_months_opt(date, 120).is_some(),
+                "subtract_year_months: normal subtract"
+            );
+            assert!(
+                <$type>::subtract_year_months_opt(date, 0).is_some(),
+                "subtract_year_months: zero interval"
+            );
+
+            // Large but valid years work
+            let large_year = $date_fn(NaiveDate::from_ymd_opt(5000, 1, 
1).unwrap());
+            let neg_year = $date_fn(NaiveDate::from_ymd_opt(-5000, 12, 
31).unwrap());
+            assert!(
+                <$type>::add_year_months_opt(large_year, 12).is_some(),
+                "add_year_months: large year"
+            );
+            assert!(
+                <$type>::add_year_months_opt(neg_year, -12).is_some(),
+                "add_year_months: negative year"
+            );
+            assert!(
+                <$type>::subtract_year_months_opt(large_year, 12).is_some(),
+                "subtract_year_months: large year"
+            );
+            assert!(
+                <$type>::subtract_year_months_opt(neg_year, -12).is_some(),
+                "subtract_year_months: negative year"
+            );
+
+            // Overflow handling
+            assert!(
+                <$type>::subtract_year_months_opt($date_fn(MIN_VALID_DATE), 
1).is_none(),
+                "subtract_year_months: overflow days from min"
+            );
+            assert!(
+                <$type>::subtract_year_months_opt($date_fn(MAX_VALID_DATE), 
-1).is_none(),
+                "subtract_year_months: overflow neg days from max"
+            );
+            assert!(
+                <$type>::add_year_months_opt($date_fn(MAX_VALID_DATE), 
1).is_none(),
+                "add_year_months: overflow days"
+            );
+            assert!(
+                <$type>::add_year_months_opt($date_fn(MIN_VALID_DATE), 
-1).is_none(),
+                "add_year_months: overflow neg days"
+            );
+        }};
     }
 
     #[test]
-    fn test_date64_year_month_operations() {
-        use arrow_array::types::Date64Type;
-
-        test_year_month_op(Date64Type::add_year_months_opt, "add_year_months");
-        test_year_month_op(Date64Type::subtract_year_months_opt, 
"subtract_year_months");
+    fn test_date_year_month_operations() {
+        test_year_month_ops!(Date32Type, date_to_days);
+        test_year_month_ops!(Date64Type, date_to_millis);
     }
 
-    fn test_day_time_op<F>(op: F, op_name: &str, is_subtract: bool)
-    where
-        F: Fn(i64, IntervalDayTime) -> Option<i64>,
-    {
-        // Moderate intervals succeed
-        assert!(
-            op(YEAR_2000_MILLIS, IntervalDayTime::new(30, 0)).is_some(),
-            "{op_name}: +30 days"
-        );
-        assert!(
-            op(YEAR_2000_MILLIS, IntervalDayTime::new(-30, 0)).is_some(),
-            "{op_name}: -30 days"
-        );
-        assert!(
-            op(YEAR_2000_MILLIS, IntervalDayTime::new(1000, 12345)).is_some(),
-            "{op_name}: normal"
-        );
+    macro_rules! test_day_time_ops {
+        ($type:ty, $date_fn:expr) => {{
+            let date = $date_fn(YEAR_2000);
 
-        // Overflow handling
-        if is_subtract {
+            // Moderate intervals succeed
             assert!(
-                op(MIN_VALID_MILLIS, IntervalDayTime::new(1, 0)).is_none(),
-                "{op_name}: overflow days from min"
+                <$type>::add_day_time_opt(date, IntervalDayTime::new(30, 
0)).is_some(),
+                "add_day_time: +30 days"
             );
             assert!(
-                op(MAX_VALID_MILLIS, IntervalDayTime::new(-1, 0)).is_none(),
-                "{op_name}: overflow neg days from max"
+                <$type>::add_day_time_opt(date, IntervalDayTime::new(-30, 
0)).is_some(),
+                "add_day_time: -30 days"
             );
-        } else {
             assert!(
-                op(MAX_VALID_MILLIS, IntervalDayTime::new(1, 0)).is_none(),
-                "{op_name}: overflow days"
+                <$type>::add_day_time_opt(date, IntervalDayTime::new(1000, 
12345)).is_some(),
+                "add_day_time: normal"
             );
             assert!(
-                op(MIN_VALID_MILLIS, IntervalDayTime::new(-1, 0)).is_none(),
-                "{op_name}: overflow neg days"
+                <$type>::subtract_day_time_opt(date, IntervalDayTime::new(30, 
0)).is_some(),
+                "subtract_day_time: +30 days"
+            );
+            assert!(
+                <$type>::subtract_day_time_opt(date, IntervalDayTime::new(-30, 
0)).is_some(),
+                "subtract_day_time: -30 days"
+            );
+            assert!(
+                <$type>::subtract_day_time_opt(date, 
IntervalDayTime::new(1000, 12345)).is_some(),
+                "subtract_day_time: normal"
             );
-        }
 
-        // Extreme intervals fail
-        assert!(
-            op(0, IntervalDayTime::new(i32::MAX, i32::MAX)).is_none(),
-            "{op_name}: max interval"
-        );
-        assert!(
-            op(0, IntervalDayTime::new(i32::MIN, i32::MIN)).is_none(),
-            "{op_name}: min interval"
-        );
+            // Overflow handling - subtract
+            assert!(
+                <$type>::subtract_day_time_opt(
+                    $date_fn(MIN_VALID_DATE),
+                    IntervalDayTime::new(1, 0)
+                )
+                .is_none(),
+                "subtract_day_time: overflow days from min"
+            );
+            assert!(
+                <$type>::subtract_day_time_opt(
+                    $date_fn(MAX_VALID_DATE),
+                    IntervalDayTime::new(-1, 0)
+                )
+                .is_none(),
+                "subtract_day_time: overflow neg days from max"
+            );
 
-        // Large ms within valid range succeed
-        assert!(
-            op(YEAR_2000_MILLIS, IntervalDayTime::new(0, i32::MAX)).is_some(),
-            "{op_name}: large ms"
-        );
+            // Overflow handling - add
+            assert!(
+                <$type>::add_day_time_opt($date_fn(MAX_VALID_DATE), 
IntervalDayTime::new(1, 0))
+                    .is_none(),
+                "add_day_time: overflow days"
+            );
+            assert!(
+                <$type>::add_day_time_opt($date_fn(MIN_VALID_DATE), 
IntervalDayTime::new(-1, 0))
+                    .is_none(),
+                "add_day_time: overflow neg days"
+            );
+
+            // Extreme intervals fail
+            assert!(
+                <$type>::add_day_time_opt(
+                    $date_fn(EPOCH),
+                    IntervalDayTime::new(i32::MAX, i32::MAX)
+                )
+                .is_none(),
+                "add_day_time: max interval"
+            );
+            assert!(
+                <$type>::add_day_time_opt(
+                    $date_fn(EPOCH),
+                    IntervalDayTime::new(i32::MIN, i32::MIN)
+                )
+                .is_none(),
+                "add_day_time: min interval"
+            );
+            assert!(
+                <$type>::subtract_day_time_opt(
+                    $date_fn(EPOCH),
+                    IntervalDayTime::new(i32::MAX, i32::MAX)
+                )
+                .is_none(),
+                "subtract_day_time: max interval"
+            );
+            assert!(
+                <$type>::subtract_day_time_opt(
+                    $date_fn(EPOCH),
+                    IntervalDayTime::new(i32::MIN, i32::MIN)
+                )
+                .is_none(),
+                "subtract_day_time: min interval"
+            );
+        }};
     }
 
     #[test]
-    fn test_date64_day_time_operations() {
-        use arrow_array::types::Date64Type;
-
-        test_day_time_op(Date64Type::add_day_time_opt, "add_day_time", false);
-        test_day_time_op(Date64Type::subtract_day_time_opt, 
"subtract_day_time", true);
+    fn test_date_day_time_operations() {
+        test_day_time_ops!(Date32Type, date_to_days);
+        test_day_time_ops!(Date64Type, date_to_millis);
     }
 
-    fn test_month_day_nano_op<F>(op: F, op_name: &str, is_subtract: bool)
-    where
-        F: Fn(i64, IntervalMonthDayNano) -> Option<i64>,
-    {
-        let zero = IntervalMonthDayNano::new(0, 0, 0);
+    macro_rules! test_month_day_nano_ops {
+        ($type:ty, $date_fn:expr) => {{
+            let date = $date_fn(YEAR_2000);
+            let zero = IntervalMonthDayNano::new(0, 0, 0);
 
-        assert!(
-            op(YEAR_2000_MILLIS, IntervalMonthDayNano::new(1, 30, 
0)).is_some(),
-            "{op_name}: +1mo +30d"
-        );
-        assert!(
-            op(YEAR_2000_MILLIS, IntervalMonthDayNano::new(-1, -30, 
0)).is_some(),
-            "{op_name}: -1mo -30d"
-        );
-        assert!(
-            op(YEAR_2000_MILLIS, zero).is_some(),
-            "{op_name}: zero interval"
-        );
-        assert!(
-            op(
-                YEAR_2000_MILLIS,
-                IntervalMonthDayNano::new(2, 10, 123_456_789_000)
-            )
-            .is_some(),
-            "{op_name}: normal"
-        );
+            // Normal operations succeed
+            assert!(
+                <$type>::add_month_day_nano_opt(date, 
IntervalMonthDayNano::new(1, 30, 0))
+                    .is_some(),
+                "add_month_day_nano: +1mo +30d"
+            );
+            assert!(
+                <$type>::add_month_day_nano_opt(date, 
IntervalMonthDayNano::new(-1, -30, 0))
+                    .is_some(),
+                "add_month_day_nano: -1mo -30d"
+            );
+            assert!(
+                <$type>::add_month_day_nano_opt(date, zero).is_some(),
+                "add_month_day_nano: zero interval"
+            );
+            assert!(
+                <$type>::add_month_day_nano_opt(
+                    date,
+                    IntervalMonthDayNano::new(2, 10, 123_456_789_000)
+                )
+                .is_some(),
+                "add_month_day_nano: normal"
+            );
+            assert!(
+                <$type>::subtract_month_day_nano_opt(date, 
IntervalMonthDayNano::new(1, 30, 0))
+                    .is_some(),
+                "subtract_month_day_nano: +1mo +30d"
+            );
+            assert!(
+                <$type>::subtract_month_day_nano_opt(date, 
IntervalMonthDayNano::new(-1, -30, 0))
+                    .is_some(),
+                "subtract_month_day_nano: -1mo -30d"
+            );
+            assert!(
+                <$type>::subtract_month_day_nano_opt(date, zero).is_some(),
+                "subtract_month_day_nano: zero interval"
+            );
+            assert!(
+                <$type>::subtract_month_day_nano_opt(
+                    date,
+                    IntervalMonthDayNano::new(2, 10, 123_456_789_000)
+                )
+                .is_some(),
+                "subtract_month_day_nano: normal"
+            );
 
-        // overflow handling
-        if is_subtract {
+            // Overflow handling - subtract
             assert!(
-                op(MIN_VALID_MILLIS, IntervalMonthDayNano::new(0, 1, 
0)).is_none(),
-                "{op_name}: overflow days from min"
+                <$type>::subtract_month_day_nano_opt(
+                    $date_fn(MIN_VALID_DATE),
+                    IntervalMonthDayNano::new(0, 1, 0)
+                )
+                .is_none(),
+                "subtract_month_day_nano: overflow days from min"
             );
             assert!(
-                op(MAX_VALID_MILLIS, IntervalMonthDayNano::new(0, -1, 
0)).is_none(),
-                "{op_name}: overflow neg days from max"
+                <$type>::subtract_month_day_nano_opt(
+                    $date_fn(MAX_VALID_DATE),
+                    IntervalMonthDayNano::new(0, -1, 0)
+                )
+                .is_none(),
+                "subtract_month_day_nano: overflow neg days from max"
             );
-        } else {
+
+            // Overflow handling - add
             assert!(
-                op(MAX_VALID_MILLIS, IntervalMonthDayNano::new(0, 1, 
0)).is_none(),
-                "{op_name}: overflow days"
+                <$type>::add_month_day_nano_opt(
+                    $date_fn(MAX_VALID_DATE),
+                    IntervalMonthDayNano::new(0, 1, 0)
+                )
+                .is_none(),
+                "add_month_day_nano: overflow days"
             );
             assert!(
-                op(MIN_VALID_MILLIS, IntervalMonthDayNano::new(0, -1, 
0)).is_none(),
-                "{op_name}: overflow neg days"
+                <$type>::add_month_day_nano_opt(
+                    $date_fn(MIN_VALID_DATE),
+                    IntervalMonthDayNano::new(0, -1, 0)
+                )
+                .is_none(),
+                "add_month_day_nano: overflow neg days"
             );
-        }
 
-        // Nanosecond precision works
-        assert!(
-            op(
-                YEAR_2000_MILLIS,
-                IntervalMonthDayNano::new(0, 0, 999_999_999)
-            )
-            .is_some(),
-            "{op_name}: nanos"
-        );
-        // 1 day in nanos
-        assert!(
-            op(
-                YEAR_2000_MILLIS,
-                IntervalMonthDayNano::new(0, 0, 86_400_000_000_000)
-            )
-            .is_some(),
-            "{op_name}: 1 day nanos"
-        );
+            // Nanosecond precision works
+            assert!(
+                <$type>::add_month_day_nano_opt(date, 
IntervalMonthDayNano::new(0, 0, 999_999_999))
+                    .is_some(),
+                "add_month_day_nano: nanos"
+            );
+            assert!(
+                <$type>::subtract_month_day_nano_opt(
+                    date,
+                    IntervalMonthDayNano::new(0, 0, 999_999_999)
+                )
+                .is_some(),
+                "subtract_month_day_nano: nanos"
+            );
+            // 1 day in nanos
+            assert!(
+                <$type>::add_month_day_nano_opt(
+                    date,
+                    IntervalMonthDayNano::new(0, 0, 86_400_000_000_000)
+                )
+                .is_some(),
+                "add_month_day_nano: 1 day nanos"
+            );
+            assert!(
+                <$type>::subtract_month_day_nano_opt(
+                    date,
+                    IntervalMonthDayNano::new(0, 0, 86_400_000_000_000)
+                )
+                .is_some(),
+                "subtract_month_day_nano: 1 day nanos"
+            );
+        }};
     }
 
     #[test]
-    fn test_date64_month_day_nano_operations() {
-        use arrow_array::types::Date64Type;
-
-        test_month_day_nano_op(
-            Date64Type::add_month_day_nano_opt,
-            "add_month_day_nano",
-            false,
-        );
-        test_month_day_nano_op(
-            Date64Type::subtract_month_day_nano_opt,
-            "subtract_month_day_nano",
-            true,
-        );
+    fn test_date_month_day_nano_operations() {
+        test_month_day_nano_ops!(Date32Type, date_to_days);
+        test_month_day_nano_ops!(Date64Type, date_to_millis);
     }
 }
diff --git a/arrow-array/src/delta.rs b/arrow-array/src/delta.rs
index d9aa4aa6de..7c1a74071f 100644
--- a/arrow-array/src/delta.rs
+++ b/arrow-array/src/delta.rs
@@ -23,18 +23,17 @@
 // Copied from chronoutil crate
 
 //! Contains utility functions for shifting Date objects.
-use chrono::{DateTime, Datelike, Days, Months, TimeZone};
+use chrono::{DateTime, Days, Months, NaiveDate, TimeZone};
 use std::cmp::Ordering;
 
-/// Shift a date by the given number of months.
-pub(crate) fn shift_months<D>(date: D, months: i32) -> D
-where
-    D: Datelike + std::ops::Add<Months, Output = D> + std::ops::Sub<Months, 
Output = D>,
-{
+/// Add the given number of months to the given date.
+///
+/// Returns `None` when it will result in overflow.
+pub(crate) fn add_months_date(date: NaiveDate, months: i32) -> 
Option<NaiveDate> {
     match months.cmp(&0) {
-        Ordering::Equal => date,
-        Ordering::Greater => date + Months::new(months as u32),
-        Ordering::Less => date - Months::new(months.unsigned_abs()),
+        Ordering::Equal => Some(date),
+        Ordering::Greater => date.checked_add_months(Months::new(months as 
u32)),
+        Ordering::Less => 
date.checked_sub_months(Months::new(months.unsigned_abs())),
     }
 }
 
@@ -91,195 +90,175 @@ pub(crate) fn sub_days_datetime<Tz: TimeZone>(dt: 
DateTime<Tz>, days: i32) -> Op
 #[cfg(test)]
 mod tests {
 
-    use chrono::naive::{NaiveDate, NaiveDateTime, NaiveTime};
+    use chrono::naive::NaiveDate;
 
     use super::*;
 
     #[test]
-    fn test_shift_months() {
+    fn test_add_monts_months() {
         let base = NaiveDate::from_ymd_opt(2020, 1, 31).unwrap();
 
         assert_eq!(
-            shift_months(base, 0),
-            NaiveDate::from_ymd_opt(2020, 1, 31).unwrap()
+            add_months_date(base, 0),
+            NaiveDate::from_ymd_opt(2020, 1, 31)
         );
         assert_eq!(
-            shift_months(base, 1),
-            NaiveDate::from_ymd_opt(2020, 2, 29).unwrap()
+            add_months_date(base, 1),
+            NaiveDate::from_ymd_opt(2020, 2, 29)
         );
         assert_eq!(
-            shift_months(base, 2),
-            NaiveDate::from_ymd_opt(2020, 3, 31).unwrap()
+            add_months_date(base, 2),
+            NaiveDate::from_ymd_opt(2020, 3, 31)
         );
         assert_eq!(
-            shift_months(base, 3),
-            NaiveDate::from_ymd_opt(2020, 4, 30).unwrap()
+            add_months_date(base, 3),
+            NaiveDate::from_ymd_opt(2020, 4, 30)
         );
         assert_eq!(
-            shift_months(base, 4),
-            NaiveDate::from_ymd_opt(2020, 5, 31).unwrap()
+            add_months_date(base, 4),
+            NaiveDate::from_ymd_opt(2020, 5, 31)
         );
         assert_eq!(
-            shift_months(base, 5),
-            NaiveDate::from_ymd_opt(2020, 6, 30).unwrap()
+            add_months_date(base, 5),
+            NaiveDate::from_ymd_opt(2020, 6, 30)
         );
         assert_eq!(
-            shift_months(base, 6),
-            NaiveDate::from_ymd_opt(2020, 7, 31).unwrap()
+            add_months_date(base, 6),
+            NaiveDate::from_ymd_opt(2020, 7, 31)
         );
         assert_eq!(
-            shift_months(base, 7),
-            NaiveDate::from_ymd_opt(2020, 8, 31).unwrap()
+            add_months_date(base, 7),
+            NaiveDate::from_ymd_opt(2020, 8, 31)
         );
         assert_eq!(
-            shift_months(base, 8),
-            NaiveDate::from_ymd_opt(2020, 9, 30).unwrap()
+            add_months_date(base, 8),
+            NaiveDate::from_ymd_opt(2020, 9, 30)
         );
         assert_eq!(
-            shift_months(base, 9),
-            NaiveDate::from_ymd_opt(2020, 10, 31).unwrap()
+            add_months_date(base, 9),
+            NaiveDate::from_ymd_opt(2020, 10, 31)
         );
         assert_eq!(
-            shift_months(base, 10),
-            NaiveDate::from_ymd_opt(2020, 11, 30).unwrap()
+            add_months_date(base, 10),
+            NaiveDate::from_ymd_opt(2020, 11, 30)
         );
         assert_eq!(
-            shift_months(base, 11),
-            NaiveDate::from_ymd_opt(2020, 12, 31).unwrap()
+            add_months_date(base, 11),
+            NaiveDate::from_ymd_opt(2020, 12, 31)
         );
         assert_eq!(
-            shift_months(base, 12),
-            NaiveDate::from_ymd_opt(2021, 1, 31).unwrap()
+            add_months_date(base, 12),
+            NaiveDate::from_ymd_opt(2021, 1, 31)
         );
         assert_eq!(
-            shift_months(base, 13),
-            NaiveDate::from_ymd_opt(2021, 2, 28).unwrap()
+            add_months_date(base, 13),
+            NaiveDate::from_ymd_opt(2021, 2, 28)
         );
 
         assert_eq!(
-            shift_months(base, -1),
-            NaiveDate::from_ymd_opt(2019, 12, 31).unwrap()
+            add_months_date(base, -1),
+            NaiveDate::from_ymd_opt(2019, 12, 31)
         );
         assert_eq!(
-            shift_months(base, -2),
-            NaiveDate::from_ymd_opt(2019, 11, 30).unwrap()
+            add_months_date(base, -2),
+            NaiveDate::from_ymd_opt(2019, 11, 30)
         );
         assert_eq!(
-            shift_months(base, -3),
-            NaiveDate::from_ymd_opt(2019, 10, 31).unwrap()
+            add_months_date(base, -3),
+            NaiveDate::from_ymd_opt(2019, 10, 31)
         );
         assert_eq!(
-            shift_months(base, -4),
-            NaiveDate::from_ymd_opt(2019, 9, 30).unwrap()
+            add_months_date(base, -4),
+            NaiveDate::from_ymd_opt(2019, 9, 30)
         );
         assert_eq!(
-            shift_months(base, -5),
-            NaiveDate::from_ymd_opt(2019, 8, 31).unwrap()
+            add_months_date(base, -5),
+            NaiveDate::from_ymd_opt(2019, 8, 31)
         );
         assert_eq!(
-            shift_months(base, -6),
-            NaiveDate::from_ymd_opt(2019, 7, 31).unwrap()
+            add_months_date(base, -6),
+            NaiveDate::from_ymd_opt(2019, 7, 31)
         );
         assert_eq!(
-            shift_months(base, -7),
-            NaiveDate::from_ymd_opt(2019, 6, 30).unwrap()
+            add_months_date(base, -7),
+            NaiveDate::from_ymd_opt(2019, 6, 30)
         );
         assert_eq!(
-            shift_months(base, -8),
-            NaiveDate::from_ymd_opt(2019, 5, 31).unwrap()
+            add_months_date(base, -8),
+            NaiveDate::from_ymd_opt(2019, 5, 31)
         );
         assert_eq!(
-            shift_months(base, -9),
-            NaiveDate::from_ymd_opt(2019, 4, 30).unwrap()
+            add_months_date(base, -9),
+            NaiveDate::from_ymd_opt(2019, 4, 30)
         );
         assert_eq!(
-            shift_months(base, -10),
-            NaiveDate::from_ymd_opt(2019, 3, 31).unwrap()
+            add_months_date(base, -10),
+            NaiveDate::from_ymd_opt(2019, 3, 31)
         );
         assert_eq!(
-            shift_months(base, -11),
-            NaiveDate::from_ymd_opt(2019, 2, 28).unwrap()
+            add_months_date(base, -11),
+            NaiveDate::from_ymd_opt(2019, 2, 28)
         );
         assert_eq!(
-            shift_months(base, -12),
-            NaiveDate::from_ymd_opt(2019, 1, 31).unwrap()
+            add_months_date(base, -12),
+            NaiveDate::from_ymd_opt(2019, 1, 31)
         );
         assert_eq!(
-            shift_months(base, -13),
-            NaiveDate::from_ymd_opt(2018, 12, 31).unwrap()
+            add_months_date(base, -13),
+            NaiveDate::from_ymd_opt(2018, 12, 31)
         );
 
         assert_eq!(
-            shift_months(base, 1265),
-            NaiveDate::from_ymd_opt(2125, 6, 30).unwrap()
+            add_months_date(base, 1265),
+            NaiveDate::from_ymd_opt(2125, 6, 30)
         );
+
+        // overflow handling
+        assert_eq!(add_months_date(base, i32::MAX), None);
+        assert_eq!(add_months_date(base, i32::MIN), None);
     }
 
     #[test]
-    fn test_shift_months_with_overflow() {
+    fn test_add_months_date_with_overflow() {
         let base = NaiveDate::from_ymd_opt(2020, 12, 31).unwrap();
 
-        assert_eq!(shift_months(base, 0), base);
+        assert_eq!(add_months_date(base, 0), Some(base));
         assert_eq!(
-            shift_months(base, 1),
-            NaiveDate::from_ymd_opt(2021, 1, 31).unwrap()
+            add_months_date(base, 1),
+            NaiveDate::from_ymd_opt(2021, 1, 31)
         );
         assert_eq!(
-            shift_months(base, 2),
-            NaiveDate::from_ymd_opt(2021, 2, 28).unwrap()
+            add_months_date(base, 2),
+            NaiveDate::from_ymd_opt(2021, 2, 28)
         );
         assert_eq!(
-            shift_months(base, 12),
-            NaiveDate::from_ymd_opt(2021, 12, 31).unwrap()
+            add_months_date(base, 12),
+            NaiveDate::from_ymd_opt(2021, 12, 31)
         );
         assert_eq!(
-            shift_months(base, 18),
-            NaiveDate::from_ymd_opt(2022, 6, 30).unwrap()
+            add_months_date(base, 18),
+            NaiveDate::from_ymd_opt(2022, 6, 30)
         );
 
         assert_eq!(
-            shift_months(base, -1),
-            NaiveDate::from_ymd_opt(2020, 11, 30).unwrap()
-        );
-        assert_eq!(
-            shift_months(base, -2),
-            NaiveDate::from_ymd_opt(2020, 10, 31).unwrap()
-        );
-        assert_eq!(
-            shift_months(base, -10),
-            NaiveDate::from_ymd_opt(2020, 2, 29).unwrap()
+            add_months_date(base, -1),
+            NaiveDate::from_ymd_opt(2020, 11, 30)
         );
         assert_eq!(
-            shift_months(base, -12),
-            NaiveDate::from_ymd_opt(2019, 12, 31).unwrap()
+            add_months_date(base, -2),
+            NaiveDate::from_ymd_opt(2020, 10, 31)
         );
         assert_eq!(
-            shift_months(base, -18),
-            NaiveDate::from_ymd_opt(2019, 6, 30).unwrap()
-        );
-    }
-
-    #[test]
-    fn test_shift_months_datetime() {
-        let date = NaiveDate::from_ymd_opt(2020, 1, 31).unwrap();
-        let o_clock = NaiveTime::from_hms_opt(1, 2, 3).unwrap();
-
-        let base = NaiveDateTime::new(date, o_clock);
-
-        assert_eq!(
-            shift_months(base, 0).date(),
-            NaiveDate::from_ymd_opt(2020, 1, 31).unwrap()
+            add_months_date(base, -10),
+            NaiveDate::from_ymd_opt(2020, 2, 29)
         );
         assert_eq!(
-            shift_months(base, 1).date(),
-            NaiveDate::from_ymd_opt(2020, 2, 29).unwrap()
+            add_months_date(base, -12),
+            NaiveDate::from_ymd_opt(2019, 12, 31)
         );
         assert_eq!(
-            shift_months(base, 2).date(),
-            NaiveDate::from_ymd_opt(2020, 3, 31).unwrap()
+            add_months_date(base, -18),
+            NaiveDate::from_ymd_opt(2019, 6, 30)
         );
-        assert_eq!(shift_months(base, 0).time(), o_clock);
-        assert_eq!(shift_months(base, 1).time(), o_clock);
-        assert_eq!(shift_months(base, 2).time(), o_clock);
     }
 }
diff --git a/arrow-array/src/types.rs b/arrow-array/src/types.rs
index fcd2d6958f..ba1a4b2b1b 100644
--- a/arrow-array/src/types.rs
+++ b/arrow-array/src/types.rs
@@ -18,7 +18,7 @@
 //! Zero-sized types used to parameterize generic array implementations
 
 use crate::delta::{
-    add_days_datetime, add_months_datetime, shift_months, sub_days_datetime, 
sub_months_datetime,
+    add_days_datetime, add_months_date, add_months_datetime, 
sub_days_datetime, sub_months_datetime,
 };
 use crate::temporal_conversions::as_datetime_with_timezone;
 use crate::timezone::Tz;
@@ -40,7 +40,7 @@ use chrono::{Duration, NaiveDate, NaiveDateTime};
 use half::f16;
 use std::fmt::Debug;
 use std::marker::PhantomData;
-use std::ops::{Add, Sub};
+use std::ops::Sub;
 
 // re-export types so that they can be used without importing arrow_buffer 
explicitly
 pub use arrow_buffer::{IntervalDayTime, IntervalMonthDayNano};
@@ -904,9 +904,22 @@ impl Date32Type {
     /// # Arguments
     ///
     /// * `i` - The Date32Type to convert
+    #[deprecated(since = "58.0.0", note = "Use to_naive_date_opt instead.")]
     pub fn to_naive_date(i: <Date32Type as ArrowPrimitiveType>::Native) -> 
NaiveDate {
+        Self::to_naive_date_opt(i)
+            .unwrap_or_else(|| panic!("Date32Type::to_naive_date overflowed 
for date: {i}",))
+    }
+
+    /// Converts an arrow Date32Type into a chrono::NaiveDate
+    ///
+    /// # Arguments
+    ///
+    /// * `i` - The Date32Type to convert
+    ///
+    /// Returns `Some(NaiveDate)` if it fits, `None` otherwise.
+    pub fn to_naive_date_opt(i: <Date32Type as ArrowPrimitiveType>::Native) -> 
Option<NaiveDate> {
         let epoch = NaiveDate::from_ymd_opt(1970, 1, 1).unwrap();
-        epoch.add(Duration::try_days(i as i64).unwrap())
+        Duration::try_days(i as i64).and_then(|d| epoch.checked_add_signed(d))
     }
 
     /// Converts a chrono::NaiveDate into an arrow Date32Type
@@ -925,14 +938,35 @@ impl Date32Type {
     ///
     /// * `date` - The date on which to perform the operation
     /// * `delta` - The interval to add
+    #[deprecated(
+        since = "58.0.0",
+        note = "Use `add_year_months_opt` instead, which returns an Option to 
handle overflow."
+    )]
     pub fn add_year_months(
         date: <Date32Type as ArrowPrimitiveType>::Native,
         delta: <IntervalYearMonthType as ArrowPrimitiveType>::Native,
     ) -> <Date32Type as ArrowPrimitiveType>::Native {
-        let prior = Date32Type::to_naive_date(date);
+        Self::add_year_months_opt(date, delta).unwrap_or_else(|| {
+            panic!("Date32Type::add_year_months overflowed for date: {date}, 
delta: {delta}",)
+        })
+    }
+
+    /// Adds the given IntervalYearMonthType to an arrow Date32Type
+    ///
+    /// # Arguments
+    ///
+    /// * `date` - The date on which to perform the operation
+    /// * `delta` - The interval to add
+    ///
+    /// Returns `Some(Date32Type)` if it fits, `None` otherwise.
+    pub fn add_year_months_opt(
+        date: <Date32Type as ArrowPrimitiveType>::Native,
+        delta: <IntervalYearMonthType as ArrowPrimitiveType>::Native,
+    ) -> Option<<Date32Type as ArrowPrimitiveType>::Native> {
+        let prior = Date32Type::to_naive_date_opt(date)?;
         let months = IntervalYearMonthType::to_months(delta);
-        let posterior = shift_months(prior, months);
-        Date32Type::from_naive_date(posterior)
+        let posterior = add_months_date(prior, months)?;
+        Some(Date32Type::from_naive_date(posterior))
     }
 
     /// Adds the given IntervalDayTimeType to an arrow Date32Type
@@ -941,15 +975,36 @@ impl Date32Type {
     ///
     /// * `date` - The date on which to perform the operation
     /// * `delta` - The interval to add
+    #[deprecated(
+        since = "58.0.0",
+        note = "Use `add_day_time_opt` instead, which returns an Option to 
handle overflow."
+    )]
     pub fn add_day_time(
         date: <Date32Type as ArrowPrimitiveType>::Native,
         delta: <IntervalDayTimeType as ArrowPrimitiveType>::Native,
     ) -> <Date32Type as ArrowPrimitiveType>::Native {
+        Self::add_day_time_opt(date, delta).unwrap_or_else(|| {
+            panic!("Date32Type::add_day_time overflowed for date: {date}, 
delta: {delta:?}",)
+        })
+    }
+
+    /// Adds the given IntervalDayTimeType to an arrow Date32Type
+    ///
+    /// # Arguments
+    ///
+    /// * `date` - The date on which to perform the operation
+    /// * `delta` - The interval to add
+    ///
+    /// Returns `Some(Date32Type)` if it fits, `None` otherwise.
+    pub fn add_day_time_opt(
+        date: <Date32Type as ArrowPrimitiveType>::Native,
+        delta: <IntervalDayTimeType as ArrowPrimitiveType>::Native,
+    ) -> Option<<Date32Type as ArrowPrimitiveType>::Native> {
         let (days, ms) = IntervalDayTimeType::to_parts(delta);
-        let res = Date32Type::to_naive_date(date);
-        let res = res.add(Duration::try_days(days as i64).unwrap());
-        let res = res.add(Duration::try_milliseconds(ms as i64).unwrap());
-        Date32Type::from_naive_date(res)
+        let res = Date32Type::to_naive_date_opt(date)?;
+        let res = res.checked_add_signed(Duration::try_days(days as i64)?)?;
+        let res = res.checked_add_signed(Duration::try_milliseconds(ms as 
i64)?)?;
+        Some(Date32Type::from_naive_date(res))
     }
 
     /// Adds the given IntervalMonthDayNanoType to an arrow Date32Type
@@ -958,16 +1013,37 @@ impl Date32Type {
     ///
     /// * `date` - The date on which to perform the operation
     /// * `delta` - The interval to add
+    #[deprecated(
+        since = "58.0.0",
+        note = "Use `add_month_day_nano_opt` instead, which returns an Option 
to handle overflow."
+    )]
     pub fn add_month_day_nano(
         date: <Date32Type as ArrowPrimitiveType>::Native,
         delta: <IntervalMonthDayNanoType as ArrowPrimitiveType>::Native,
     ) -> <Date32Type as ArrowPrimitiveType>::Native {
+        Self::add_month_day_nano_opt(date, delta).unwrap_or_else(|| {
+            panic!("Date32Type::add_month_day_nano overflowed for date: 
{date}, delta: {delta:?}",)
+        })
+    }
+
+    /// Adds the given IntervalMonthDayNanoType to an arrow Date32Type
+    ///
+    /// # Arguments
+    ///
+    /// * `date` - The date on which to perform the operation
+    /// * `delta` - The interval to add
+    ///
+    /// Returns `Some(Date32Type)` if it fits, `None` otherwise.
+    pub fn add_month_day_nano_opt(
+        date: <Date32Type as ArrowPrimitiveType>::Native,
+        delta: <IntervalMonthDayNanoType as ArrowPrimitiveType>::Native,
+    ) -> Option<<Date32Type as ArrowPrimitiveType>::Native> {
         let (months, days, nanos) = IntervalMonthDayNanoType::to_parts(delta);
-        let res = Date32Type::to_naive_date(date);
-        let res = shift_months(res, months);
-        let res = res.add(Duration::try_days(days as i64).unwrap());
-        let res = res.add(Duration::nanoseconds(nanos));
-        Date32Type::from_naive_date(res)
+        let res = Date32Type::to_naive_date_opt(date)?;
+        let res = add_months_date(res, months)?;
+        let res = res.checked_add_signed(Duration::try_days(days as i64)?)?;
+        let res = res.checked_add_signed(Duration::nanoseconds(nanos))?;
+        Some(Date32Type::from_naive_date(res))
     }
 
     /// Subtract the given IntervalYearMonthType to an arrow Date32Type
@@ -976,14 +1052,35 @@ impl Date32Type {
     ///
     /// * `date` - The date on which to perform the operation
     /// * `delta` - The interval to subtract
+    #[deprecated(
+        since = "58.0.0",
+        note = "Use `subtract_year_months_opt` instead, which returns an 
Option to handle overflow."
+    )]
     pub fn subtract_year_months(
         date: <Date32Type as ArrowPrimitiveType>::Native,
         delta: <IntervalYearMonthType as ArrowPrimitiveType>::Native,
     ) -> <Date32Type as ArrowPrimitiveType>::Native {
-        let prior = Date32Type::to_naive_date(date);
+        Self::subtract_year_months_opt(date, delta).unwrap_or_else(|| {
+            panic!("Date32Type::subtract_year_months overflowed for date: 
{date}, delta: {delta}",)
+        })
+    }
+
+    /// Subtract the given IntervalYearMonthType to an arrow Date32Type
+    ///
+    /// # Arguments
+    ///
+    /// * `date` - The date on which to perform the operation
+    /// * `delta` - The interval to subtract
+    ///
+    /// Returns `Some(Date32Type)` if it fits, `None` otherwise.
+    pub fn subtract_year_months_opt(
+        date: <Date32Type as ArrowPrimitiveType>::Native,
+        delta: <IntervalYearMonthType as ArrowPrimitiveType>::Native,
+    ) -> Option<<Date32Type as ArrowPrimitiveType>::Native> {
+        let prior = Date32Type::to_naive_date_opt(date)?;
         let months = IntervalYearMonthType::to_months(-delta);
-        let posterior = shift_months(prior, months);
-        Date32Type::from_naive_date(posterior)
+        let posterior = add_months_date(prior, months)?;
+        Some(Date32Type::from_naive_date(posterior))
     }
 
     /// Subtract the given IntervalDayTimeType to an arrow Date32Type
@@ -992,15 +1089,36 @@ impl Date32Type {
     ///
     /// * `date` - The date on which to perform the operation
     /// * `delta` - The interval to subtract
+    #[deprecated(
+        since = "58.0.0",
+        note = "Use `subtract_day_time_opt` instead, which returns an Option 
to handle overflow."
+    )]
     pub fn subtract_day_time(
         date: <Date32Type as ArrowPrimitiveType>::Native,
         delta: <IntervalDayTimeType as ArrowPrimitiveType>::Native,
     ) -> <Date32Type as ArrowPrimitiveType>::Native {
+        Self::subtract_day_time_opt(date, delta).unwrap_or_else(|| {
+            panic!("Date32Type::subtract_day_time overflowed for date: {date}, 
delta: {delta:?}",)
+        })
+    }
+
+    /// Subtract the given IntervalDayTimeType to an arrow Date32Type
+    ///
+    /// # Arguments
+    ///
+    /// * `date` - The date on which to perform the operation
+    /// * `delta` - The interval to subtract
+    ///
+    /// Returns `Some(Date32Type)` if it fits, `None` otherwise.
+    pub fn subtract_day_time_opt(
+        date: <Date32Type as ArrowPrimitiveType>::Native,
+        delta: <IntervalDayTimeType as ArrowPrimitiveType>::Native,
+    ) -> Option<<Date32Type as ArrowPrimitiveType>::Native> {
         let (days, ms) = IntervalDayTimeType::to_parts(delta);
-        let res = Date32Type::to_naive_date(date);
-        let res = res.sub(Duration::try_days(days as i64).unwrap());
-        let res = res.sub(Duration::try_milliseconds(ms as i64).unwrap());
-        Date32Type::from_naive_date(res)
+        let res = Date32Type::to_naive_date_opt(date)?;
+        let res = res.checked_sub_signed(Duration::try_days(days as i64)?)?;
+        let res = res.checked_sub_signed(Duration::try_milliseconds(ms as 
i64)?)?;
+        Some(Date32Type::from_naive_date(res))
     }
 
     /// Subtract the given IntervalMonthDayNanoType to an arrow Date32Type
@@ -1009,16 +1127,39 @@ impl Date32Type {
     ///
     /// * `date` - The date on which to perform the operation
     /// * `delta` - The interval to subtract
+    #[deprecated(
+        since = "58.0.0",
+        note = "Use `subtract_month_day_nano_opt` instead, which returns an 
Option to handle overflow."
+    )]
     pub fn subtract_month_day_nano(
         date: <Date32Type as ArrowPrimitiveType>::Native,
         delta: <IntervalMonthDayNanoType as ArrowPrimitiveType>::Native,
     ) -> <Date32Type as ArrowPrimitiveType>::Native {
+        Self::subtract_month_day_nano_opt(date, delta).unwrap_or_else(|| {
+            panic!(
+                "Date32Type::subtract_month_day_nano overflowed for date: 
{date}, delta: {delta:?}",
+            )
+        })
+    }
+
+    /// Subtract the given IntervalMonthDayNanoType to an arrow Date32Type
+    ///
+    /// # Arguments
+    ///
+    /// * `date` - The date on which to perform the operation
+    /// * `delta` - The interval to subtract
+    ///
+    /// Returns `Some(Date32Type)` if it fits, `None` otherwise.
+    pub fn subtract_month_day_nano_opt(
+        date: <Date32Type as ArrowPrimitiveType>::Native,
+        delta: <IntervalMonthDayNanoType as ArrowPrimitiveType>::Native,
+    ) -> Option<<Date32Type as ArrowPrimitiveType>::Native> {
         let (months, days, nanos) = IntervalMonthDayNanoType::to_parts(delta);
-        let res = Date32Type::to_naive_date(date);
-        let res = shift_months(res, -months);
-        let res = res.sub(Duration::try_days(days as i64).unwrap());
-        let res = res.sub(Duration::nanoseconds(nanos));
-        Date32Type::from_naive_date(res)
+        let res = Date32Type::to_naive_date_opt(date)?;
+        let res = add_months_date(res, -months)?;
+        let res = res.checked_sub_signed(Duration::try_days(days as i64)?)?;
+        let res = res.checked_sub_signed(Duration::nanoseconds(nanos))?;
+        Some(Date32Type::from_naive_date(res))
     }
 }
 
@@ -1092,7 +1233,7 @@ impl Date64Type {
     ) -> Option<<Date64Type as ArrowPrimitiveType>::Native> {
         let prior = Date64Type::to_naive_date_opt(date)?;
         let months = IntervalYearMonthType::to_months(delta);
-        let posterior = shift_months(prior, months);
+        let posterior = add_months_date(prior, months)?;
         Some(Date64Type::from_naive_date(posterior))
     }
 
@@ -1167,7 +1308,7 @@ impl Date64Type {
     ) -> Option<<Date64Type as ArrowPrimitiveType>::Native> {
         let (months, days, nanos) = IntervalMonthDayNanoType::to_parts(delta);
         let res = Date64Type::to_naive_date_opt(date)?;
-        let res = shift_months(res, months);
+        let res = add_months_date(res, months)?;
         let res = res.checked_add_signed(Duration::try_days(days as i64)?)?;
         let res = res.checked_add_signed(Duration::nanoseconds(nanos))?;
         Some(Date64Type::from_naive_date(res))
@@ -1206,7 +1347,7 @@ impl Date64Type {
     ) -> Option<<Date64Type as ArrowPrimitiveType>::Native> {
         let prior = Date64Type::to_naive_date_opt(date)?;
         let months = IntervalYearMonthType::to_months(-delta);
-        let posterior = shift_months(prior, months);
+        let posterior = add_months_date(prior, months)?;
         Some(Date64Type::from_naive_date(posterior))
     }
 
@@ -1283,7 +1424,7 @@ impl Date64Type {
     ) -> Option<<Date64Type as ArrowPrimitiveType>::Native> {
         let (months, days, nanos) = IntervalMonthDayNanoType::to_parts(delta);
         let res = Date64Type::to_naive_date_opt(date)?;
-        let res = shift_months(res, -months);
+        let res = add_months_date(res, -months)?;
         let res = res.checked_sub_signed(Duration::try_days(days as i64)?)?;
         let res = res.checked_sub_signed(Duration::nanoseconds(nanos))?;
         Some(Date64Type::from_naive_date(res))
diff --git a/parquet-variant-compute/src/unshred_variant.rs 
b/parquet-variant-compute/src/unshred_variant.rs
index c20bb69790..37363fd9d0 100644
--- a/parquet-variant-compute/src/unshred_variant.rs
+++ b/parquet-variant-compute/src/unshred_variant.rs
@@ -414,7 +414,9 @@ 
impl_append_to_variant_builder!(PrimitiveArray<Float32Type>);
 impl_append_to_variant_builder!(PrimitiveArray<Float64Type>);
 
 impl_append_to_variant_builder!(PrimitiveArray<Date32Type>, |days_since_epoch| 
{
-    Date32Type::to_naive_date(days_since_epoch)
+    Date32Type::to_naive_date_opt(days_since_epoch).ok_or_else(|| {
+        ArrowError::InvalidArgumentError(format!("Invalid Date32 value: 
{days_since_epoch}"))
+    })?
 });
 
 impl_append_to_variant_builder!(
diff --git a/parquet-variant-compute/src/variant_array.rs 
b/parquet-variant-compute/src/variant_array.rs
index fb2a08d641..250852d021 100644
--- a/parquet-variant-compute/src/variant_array.rs
+++ b/parquet-variant-compute/src/variant_array.rs
@@ -1029,7 +1029,7 @@ fn typed_value_to_variant<'a>(
             generic_conversion_single_value!(
                 Date32Type,
                 as_primitive,
-                Date32Type::to_naive_date,
+                |v| Date32Type::to_naive_date_opt(v).unwrap(),
                 typed_value,
                 index
             )

Reply via email to