From f4f48f3b23efb56cc5e3d4e5fd726d73bc1b70d7 Mon Sep 17 00:00:00 2001 From: Harrison Booth <73434116+Harrison-Booth@users.noreply.github.com> Date: Mon, 26 Oct 2026 12:51:42 -0500 Subject: [PATCH] CSV export values: choose the date or time shape from the format section that formats the value --- crates/engine/src/io.rs | 2 +- crates/engine/src/tests.rs | 3 ++- crates/numfmt/src/lib.rs | 6 +++--- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/crates/engine/src/io.rs b/crates/engine/src/io.rs index 3b8bacfe4..ce5b7a871 100644 --- a/crates/engine/src/io.rs +++ b/crates/engine/src/io.rs @@ -301,7 +301,7 @@ pub fn csv_values_bytes(wb: &Workbook) -> Result> { let Some(sh) = wb.sheet(wb.active_sheet()) else { return Ok(Vec::new()) }; gridcraft_xlsx::write_csv(sh, wb.csv_delimiter.unwrap_or(b','), |c| match sh.value(c) { gridcraft_core::Value::Number(n) if n.is_finite() => { - let shape = crate::display::number_format(wb.styles.get(sh.style_id(c)).num_fmt.as_str()).date_shape(); + let shape = crate::display::number_format(wb.styles.get(sh.style_id(c)).num_fmt.as_str()).date_shape(n); match shape.zip(gridcraft_core::date::datetime_from_serial(wb.date_system, n)) { Some(((date, time), d)) => iso(&d, date, time), None => n.to_string(), diff --git a/crates/engine/src/tests.rs b/crates/engine/src/tests.rs index c76320592..c97f033d1 100644 --- a/crates/engine/src/tests.rs +++ b/crates/engine/src/tests.rs @@ -2479,6 +2479,7 @@ fn export_csv_values_writes_raw_values_and_the_default_stays_displayed_text() { ("J1", "1.5", "h:mm"), ("K1", "46023", "yyyy-mm-dd hh:mm"), ("L1", "1.5", "[h]:mm"), + ("M1", "2.5", "[>=3]yyyy-mm-dd;[<3]hh:mm"), ] { s.execute("cell.set", json!({"cell": cell, "input": input})).unwrap(); s.execute("home.numberFormat", json!({"range": cell, "code": code})).unwrap(); @@ -2501,7 +2502,7 @@ fn export_csv_values_writes_raw_values_and_the_default_stays_displayed_text() { assert!(default.starts_with("\"$1,234.50\",0.12,1-Jan-26,"), "{default}"); assert_eq!( std::fs::read_to_string(&raw).unwrap(), - "1234.5,0.123456789,2026-01-01,2026-01-01T12:00:00,18:00:00,TRUE,\"a,b\",#DIV/0!,00:00:00,12:00:00,2026-01-01T00:00:00,1.5\r\n" + "1234.5,0.123456789,2026-01-01,2026-01-01T12:00:00,18:00:00,TRUE,\"a,b\",#DIV/0!,00:00:00,12:00:00,2026-01-01T00:00:00,1.5,12:00:00\r\n" ); std::fs::remove_dir_all(dir).unwrap(); } diff --git a/crates/numfmt/src/lib.rs b/crates/numfmt/src/lib.rs index d5c4ef41b..52cada2c7 100644 --- a/crates/numfmt/src/lib.rs +++ b/crates/numfmt/src/lib.rs @@ -193,11 +193,11 @@ impl NumberFormat { self.num_sections().iter().any(|s| s.kind == SecKind::Date) } - /// For a date or time format, whether its first section shows calendar parts (year, month, + /// For a date or time format, whether the section that formats `value` shows calendar parts (year, month, /// day) and whether it shows time parts, as `(date, time)`. `None` for any other format and for /// elapsed-time codes like `[h]:mm`, whose value is a duration, not a moment. - pub fn date_shape(&self) -> Option<(bool, bool)> { - let s = self.num_sections().first().filter(|s| s.kind == SecKind::Date)?; + pub fn date_shape(&self, value: f64) -> Option<(bool, bool)> { + let s = self.select(value).map(|(s, _)| s).filter(|s| s.kind == SecKind::Date)?; let parts = || s.toks.iter().filter_map(|t| if let Tok::Date(d) = t { Some(*d) } else { None }); if parts().any(|d| matches!(d, DatePart::ElapsedH(_) | DatePart::ElapsedM(_) | DatePart::ElapsedS(_))) { return None;