From f42ee77e318a44c7c8e37f5938fc47feaf132ef9 Mon Sep 17 00:00:00 2001 From: One <43485962+c-git@users.noreply.github.com> Date: Wed, 5 Aug 2026 00:49:37 -0400 Subject: [PATCH 1/2] feat: add fallible version of date conversion --- src/helper/date.rs | 36 ++++++++++++++++++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/src/helper/date.rs b/src/helper/date.rs index 2e5d2136..dac01daa 100644 --- a/src/helper/date.rs +++ b/src/helper/date.rs @@ -157,6 +157,14 @@ pub fn excel_to_date_time_parts(excel_timestamp: f64) -> (jiff::civil::Date, i64 /// The integer part of the timestamp represents the number of days since a base /// date, while the fractional part represents the time of day. /// +/// The fallible version of this method is [`excel_to_date_time_jiff_checked`]. +/// +/// # Panics +/// +/// This panics when the input causes the number of days since the `base_date` +/// to be outside of the supported range for `jiff` spans. See +/// [`jiff::Span::days`] for relevant bounds. +/// /// # Parameters /// /// - `excel_timestamp: f64` The Excel timestamp to be converted and it is @@ -191,10 +199,25 @@ pub fn excel_to_date_time_parts(excel_timestamp: f64) -> (jiff::civil::Date, i64 /// ``` #[must_use] pub fn excel_to_date_time_jiff(excel_timestamp: f64) -> jiff::civil::DateTime { + excel_to_date_time_jiff_checked(excel_timestamp).expect("input was out of supported range") +} + +/// This is the fallible version of [`excel_to_date_time_jiff`]. See +/// documentation on that function. +/// +/// # Error +/// +/// This returns an error when the input causes the number of days since the +/// `base_date` to be outside of the supported range for `jiff` spans. See +/// [`jiff::Span::days`] for relevant bounds. +pub fn excel_to_date_time_jiff_checked( + excel_timestamp: f64, +) -> Result { let (base_date, days, time) = excel_to_date_time_parts(excel_timestamp); - let seconds: i64 = cast((time * (24.0 * 60.0 * 60.0)).round()).unwrap(); + let seconds: i64 = cast((time * (24.0 * 60.0 * 60.0)).round()) + .expect("this cast should never fail because `time` should be a value between 0 and 1"); - base_date.at(0, 0, 0, 0) + days.day().seconds(seconds) + Ok(base_date.at(0, 0, 0, 0) + jiff::Span::new().try_days(days)?.seconds(seconds)) } /// Converts an Excel timestamp to a [`jiff::civil::DateTime`] object with @@ -691,4 +714,13 @@ mod tests { "chrono conversion is incorrect" ); } + + #[test] + fn excel_to_date_time_checked() { + let actual = excel_to_date_time_jiff_checked(123_456_789.0); + assert!( + actual.is_err(), + "Value is out of range of jiff::Span days and should be rejected" + ); + } } From 6832970876204f584f3f13d8ae82f8a21e90276b Mon Sep 17 00:00:00 2001 From: One <43485962+c-git@users.noreply.github.com> Date: Wed, 5 Aug 2026 00:56:21 -0400 Subject: [PATCH 2/2] chore: address clippy lints --- src/helper/number_format/number_formater.rs | 8 ++++---- src/helper/number_format/percentage_formater.rs | 9 ++++++--- src/structs/data_bar.rs | 2 +- tests/streaming_writer_test.rs | 4 ++++ 4 files changed, 15 insertions(+), 8 deletions(-) diff --git a/src/helper/number_format/number_formater.rs b/src/helper/number_format/number_formater.rs index 29e20c25..c1912a67 100644 --- a/src/helper/number_format/number_formater.rs +++ b/src/helper/number_format/number_formater.rs @@ -344,7 +344,7 @@ mod tests { #[test] fn format_as_number_rounds_half_away_from_zero() { // Excel rounds display values; truncating turned 107310.6 into 107,310. - assert_eq!(format_as_number(107310.6, "#,##0"), "107,311"); + assert_eq!(format_as_number(107_310.6, "#,##0"), "107,311"); assert_eq!(format_as_number(12.5, "0"), "13"); assert_eq!(format_as_number(-12.5, "0"), "-13"); assert_eq!(format_as_number(99999.5, "0"), "100000"); @@ -371,13 +371,13 @@ mod tests { #[test] fn format_as_number_keeps_currency_prefix() { assert_eq!(format_as_number(39.1, "$0.00"), "$39.10"); - assert_eq!(format_as_number(107310.6, "$#,##0"), "$107,311"); + assert_eq!(format_as_number(107_310.6, "$#,##0"), "$107,311"); } #[test] fn format_as_number_thousands_grouping_survives_rounding() { - assert_eq!(format_as_number(999999.5, "#,##0"), "1,000,000"); - assert_eq!(format_as_number(1234567.891, "#,##0.00"), "1,234,567.89"); + assert_eq!(format_as_number(999_999.5, "#,##0"), "1,000,000"); + assert_eq!(format_as_number(1_234_567.891, "#,##0.00"), "1,234,567.89"); assert_eq!(format_as_number(-1234.5, "#,##0"), "-1,235"); } } diff --git a/src/helper/number_format/percentage_formater.rs b/src/helper/number_format/percentage_formater.rs index 4172d984..aa09a13a 100644 --- a/src/helper/number_format/percentage_formater.rs +++ b/src/helper/number_format/percentage_formater.rs @@ -51,12 +51,15 @@ mod tests { fn format_as_percentage_keeps_decimal_precision() { // Rounding to an integer before applying the decimal format code // turned 17.309...% into "17.0%". - assert_eq!(format_as_percentage(0.1730909090909091, "0.0%"), "17.3%"); + assert_eq!( + format_as_percentage(0.173_090_909_090_909_1, "0.0%"), + "17.3%" + ); } #[test] fn format_as_percentage_integer_format() { - assert_eq!(format_as_percentage(0.1730909090909091, "0%"), "17%"); + assert_eq!(format_as_percentage(0.173_090_909_090_909_1, "0%"), "17%"); } #[test] @@ -71,6 +74,6 @@ mod tests { // display value (106.5) away from zero. assert_eq!(format_as_percentage(1.065, "0%"), "107%"); assert_eq!(format_as_percentage(0.125, "0.0%"), "12.5%"); - assert_eq!(format_as_percentage(0.10649999999999999, "0%"), "11%"); + assert_eq!(format_as_percentage(0.106_499_999_999_999_99, "0%"), "11%"); } } diff --git a/src/structs/data_bar.rs b/src/structs/data_bar.rs index 40121dd8..a5705ac8 100644 --- a/src/structs/data_bar.rs +++ b/src/structs/data_bar.rs @@ -137,7 +137,7 @@ mod tests { let mut buf = Vec::new(); loop { match reader.read_event_into(&mut buf) { - Ok(Event::Start(ref e)) | Ok(Event::Empty(ref e)) + Ok(Event::Start(ref e) | Event::Empty(ref e)) if e.name().into_inner() == b"dataBar" => { let mut obj = DataBar::default(); diff --git a/tests/streaming_writer_test.rs b/tests/streaming_writer_test.rs index aae9a3de..a746be97 100644 --- a/tests/streaming_writer_test.rs +++ b/tests/streaming_writer_test.rs @@ -32,6 +32,10 @@ use umya_spreadsheet::{ /// intentionally non-ASCII to exercise UTF-8 handling in workbook.xml. const SHEETS: [&str; 3] = ["Sheet1", "Sheet2", "データ"]; +#[expect( + clippy::approx_constant, + reason = "used in a test and not worth changing" +)] /// Build a deterministic, multi-sheet workbook covering the common cell value /// kinds (string, number, bool, formula) plus a non-ASCII string value. fn build_book() -> Workbook {