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 1c380b8336 fix: Panic in pretty_format function when displaying 
DurationSecondsA… (#7534)
1c380b8336 is described below

commit 1c380b8336ec9be1cdbd3179f2d289bf6087f507
Author: Qi Zhu <[email protected]>
AuthorDate: Mon May 26 18:19:13 2025 +0800

    fix: Panic in pretty_format function when displaying DurationSecondsA… 
(#7534)
    
    * fix: Panic in pretty_format function when displaying DurationSecondsArray 
with i64::MIN / i64::MAX
    
    * Address comments
    
    * Avoid changes for infallible functions
    
    * Fix compilation
    
    * fix docs
    
    ---------
    
    Co-authored-by: Andrew Lamb <[email protected]>
---
 arrow-array/src/temporal_conversions.rs | 20 ++++++++++--
 arrow-cast/src/display.rs               | 41 ++++++++++++++++++++++---
 arrow-cast/src/pretty.rs                | 54 ++++++++++++++++++++++++++++++++-
 3 files changed, 107 insertions(+), 8 deletions(-)

diff --git a/arrow-array/src/temporal_conversions.rs 
b/arrow-array/src/temporal_conversions.rs
index 23f950d550..7a4c676029 100644
--- a/arrow-array/src/temporal_conversions.rs
+++ b/arrow-array/src/temporal_conversions.rs
@@ -217,14 +217,28 @@ pub(crate) fn split_second(v: i64, base: i64) -> (i64, 
u32) {
 
 /// converts a `i64` representing a `duration(s)` to [`Duration`]
 #[inline]
+#[deprecated(since = "55.2.0", note = "Use `try_duration_s_to_duration` 
instead")]
 pub fn duration_s_to_duration(v: i64) -> Duration {
     Duration::try_seconds(v).unwrap()
 }
 
+/// converts a `i64` representing a `duration(s)` to [`Option<Duration>`]
+#[inline]
+pub fn try_duration_s_to_duration(v: i64) -> Option<Duration> {
+    Duration::try_seconds(v)
+}
+
 /// converts a `i64` representing a `duration(ms)` to [`Duration`]
 #[inline]
+#[deprecated(since = "55.2.0", note = "Use `try_duration_ms_to_duration` 
instead")]
 pub fn duration_ms_to_duration(v: i64) -> Duration {
-    Duration::try_milliseconds(v).unwrap()
+    Duration::try_seconds(v).unwrap()
+}
+
+/// converts a `i64` representing a `duration(ms)` to [`Option<Duration>`]
+#[inline]
+pub fn try_duration_ms_to_duration(v: i64) -> Option<Duration> {
+    Duration::try_milliseconds(v)
 }
 
 /// converts a `i64` representing a `duration(us)` to [`Duration`]
@@ -296,8 +310,8 @@ pub fn as_time<T: ArrowPrimitiveType>(v: i64) -> 
Option<NaiveTime> {
 pub fn as_duration<T: ArrowPrimitiveType>(v: i64) -> Option<Duration> {
     match T::DATA_TYPE {
         DataType::Duration(unit) => match unit {
-            TimeUnit::Second => Some(duration_s_to_duration(v)),
-            TimeUnit::Millisecond => Some(duration_ms_to_duration(v)),
+            TimeUnit::Second => try_duration_s_to_duration(v),
+            TimeUnit::Millisecond => try_duration_ms_to_duration(v),
             TimeUnit::Microsecond => Some(duration_us_to_duration(v)),
             TimeUnit::Nanosecond => Some(duration_ns_to_duration(v)),
         },
diff --git a/arrow-cast/src/display.rs b/arrow-cast/src/display.rs
index 3c61797c96..6761ac22fa 100644
--- a/arrow-cast/src/display.rs
+++ b/arrow-cast/src/display.rs
@@ -590,6 +590,12 @@ temporal_display!(time32ms_to_time, time_format, 
Time32MillisecondType);
 temporal_display!(time64us_to_time, time_format, Time64MicrosecondType);
 temporal_display!(time64ns_to_time, time_format, Time64NanosecondType);
 
+/// Derive [`DisplayIndexState`] for `PrimitiveArray<$t>`
+///
+/// Arguments
+/// * `$convert` - function to convert the value to an `Duration`
+/// * `$t` - [`ArrowPrimitiveType`] of the array
+/// * `$scale` - scale of the duration (passed to `duration_fmt`)
 macro_rules! duration_display {
     ($convert:ident, $t:ty, $scale:tt) => {
         impl<'a> DisplayIndexState<'a> for &'a PrimitiveArray<$t> {
@@ -611,6 +617,34 @@ macro_rules! duration_display {
     };
 }
 
+/// Similar to [`duration_display`] but `$convert` returns an `Option`
+macro_rules! duration_option_display {
+    ($convert:ident, $t:ty, $scale:tt) => {
+        impl<'a> DisplayIndexState<'a> for &'a PrimitiveArray<$t> {
+            type State = DurationFormat;
+
+            fn prepare(&self, options: &FormatOptions<'a>) -> 
Result<Self::State, ArrowError> {
+                Ok(options.duration_format)
+            }
+
+            fn write(&self, fmt: &Self::State, idx: usize, f: &mut dyn Write) 
-> FormatResult {
+                let v = self.value(idx);
+                match fmt {
+                    DurationFormat::ISO8601 => match $convert(v) {
+                        Some(td) => write!(f, "{}", td)?,
+                        None => write!(f, "<invalid>")?,
+                    },
+                    DurationFormat::Pretty => match $convert(v) {
+                        Some(_) => duration_fmt!(f, v, $scale)?,
+                        None => write!(f, "<invalid>")?,
+                    },
+                }
+                Ok(())
+            }
+        }
+    };
+}
+
 macro_rules! duration_fmt {
     ($f:ident, $v:expr, 0) => {{
         let secs = $v;
@@ -657,8 +691,8 @@ macro_rules! duration_fmt {
     }};
 }
 
-duration_display!(duration_s_to_duration, DurationSecondType, 0);
-duration_display!(duration_ms_to_duration, DurationMillisecondType, 3);
+duration_option_display!(try_duration_s_to_duration, DurationSecondType, 0);
+duration_option_display!(try_duration_ms_to_duration, DurationMillisecondType, 
3);
 duration_display!(duration_us_to_duration, DurationMicrosecondType, 6);
 duration_display!(duration_ns_to_duration, DurationNanosecondType, 9);
 
@@ -1071,9 +1105,8 @@ pub fn lexical_to_string<N: lexical_core::ToLexical>(n: 
N) -> String {
 
 #[cfg(test)]
 mod tests {
-    use arrow_array::builder::StringRunBuilder;
-
     use super::*;
+    use arrow_array::builder::StringRunBuilder;
 
     /// Test to verify options can be constant. See #4580
     const TEST_CONST_OPTIONS: FormatOptions<'static> = FormatOptions::new()
diff --git a/arrow-cast/src/pretty.rs b/arrow-cast/src/pretty.rs
index 5223c9d7e1..1c0b7a5d44 100644
--- a/arrow-cast/src/pretty.rs
+++ b/arrow-cast/src/pretty.rs
@@ -221,7 +221,7 @@ mod tests {
     use arrow_buffer::{IntervalDayTime, IntervalMonthDayNano, ScalarBuffer};
     use arrow_schema::*;
 
-    use crate::display::array_value_to_string;
+    use crate::display::{array_value_to_string, DurationFormat};
 
     use super::*;
 
@@ -1186,4 +1186,56 @@ mod tests {
         let actual: Vec<&str> = batch.lines().collect();
         assert_eq!(expected_table, actual, "Actual result:\n{batch}");
     }
+
+    #[test]
+    fn duration_pretty_and_iso_extremes() {
+        // Build [MIN, MAX, 3661, NULL]
+        let arr = DurationSecondArray::from(vec![Some(i64::MIN), 
Some(i64::MAX), Some(3661), None]);
+        let array: ArrayRef = Arc::new(arr);
+
+        // Pretty formatting
+        let opts = FormatOptions::default().with_null("null");
+        let opts = opts.with_duration_format(DurationFormat::Pretty);
+        let pretty = pretty_format_columns_with_options("pretty", 
&[array.clone()], &opts)
+            .unwrap()
+            .to_string();
+
+        // Expected output
+        let expected_pretty = vec![
+            "+------------------------------+",
+            "| pretty                       |",
+            "+------------------------------+",
+            "| <invalid>                    |",
+            "| <invalid>                    |",
+            "| 0 days 1 hours 1 mins 1 secs |",
+            "| null                         |",
+            "+------------------------------+",
+        ];
+
+        let actual: Vec<&str> = pretty.lines().collect();
+        assert_eq!(expected_pretty, actual, "Actual result:\n{pretty}");
+
+        // ISO8601 formatting
+        let opts_iso = FormatOptions::default()
+            .with_null("null")
+            .with_duration_format(DurationFormat::ISO8601);
+        let iso = pretty_format_columns_with_options("iso", &[array], 
&opts_iso)
+            .unwrap()
+            .to_string();
+
+        // Expected output
+        let expected_iso = vec![
+            "+-----------+",
+            "| iso       |",
+            "+-----------+",
+            "| <invalid> |",
+            "| <invalid> |",
+            "| PT3661S   |",
+            "| null      |",
+            "+-----------+",
+        ];
+
+        let actual: Vec<&str> = iso.lines().collect();
+        assert_eq!(expected_iso, actual, "Actual result:\n{iso}");
+    }
 }

Reply via email to