diff --git a/crates/engine/src/cmd/data.rs b/crates/engine/src/cmd/data.rs index 94733071c..7372380a4 100644 --- a/crates/engine/src/cmd/data.rs +++ b/crates/engine/src/cmd/data.rs @@ -691,8 +691,11 @@ fn remove_duplicates(s: &mut Session, p: &Json) -> Result { let Some(sh) = cx.wb.sheet(sheet) else { return Ok(()) }; let mut seen = std::collections::HashSet::new(); let mut keep: Vec = Vec::new(); + // Cells are compared by the text they show, as in Excel: the same date in two formats + // isn't a duplicate, while 1.01 and 1.02 shown as 1.0 are. + let shown = |c: CellRef| crate::display::format(&sh.value(c), cx.wb.styles.get(sh.style_id(c)).num_fmt.as_str(), &cx.wb).text; for row in start..=r.end.row { - let key: Vec = cols.iter().map(|c| sh.value(CellRef::new(row, *c)).display().to_lowercase()).collect(); + let key: Vec = cols.iter().map(|c| shown(CellRef::new(row, *c)).to_lowercase()).collect(); if seen.insert(key) { keep.push(row); } else { diff --git a/crates/engine/src/tests.rs b/crates/engine/src/tests.rs index 92ca38665..139a24985 100644 --- a/crates/engine/src/tests.rs +++ b/crates/engine/src/tests.rs @@ -615,6 +615,18 @@ fn case_sensitive_sort_puts_lowercase_first() { assert_eq!(col, ["a", "A", "aB", "Ab", "b", "B"].map(Value::from)); } +#[test] +fn remove_duplicates_compares_shown_text() { + let mut s = s(); + s.execute("range.setValues", json!({"range": "A1", "values": [["Date", "Price"], ["2006-03-08", 1.01], ["2006-03-08", 1.02]]})).unwrap(); + s.execute("home.numberFormat", json!({"range": "A3", "code": "mmm d, yyyy"})).unwrap(); + s.execute("home.numberFormat", json!({"range": "B2:B3", "code": "0.0"})).unwrap(); + let r = s.execute("data.removeDuplicates", json!({"range": "A1:A3", "header": true})).unwrap(); + assert_eq!(r["removed"], 0); + let r = s.execute("data.removeDuplicates", json!({"range": "B1:B3", "header": true})).unwrap(); + assert_eq!(r["removed"], 1); +} + #[test] fn formatting_and_styles() { let mut s = s();