diff --git a/crates/calc/src/eval.rs b/crates/calc/src/eval.rs index 47a5119..0fbfdfb 100644 --- a/crates/calc/src/eval.rs +++ b/crates/calc/src/eval.rs @@ -407,6 +407,7 @@ impl<'h> Evaluator<'h> { }; } let v = self.value(x); + let sys = self.host.workbook().date_system; Ev::V(map_unary( &v, &|n| match op { @@ -416,6 +417,7 @@ impl<'h> Evaluator<'h> { UnOp::At | UnOp::Spill | UnOp::Trim(_) => Value::number(n), }, op == UnOp::Plus, + sys, )) } @@ -502,7 +504,7 @@ impl<'h> Evaluator<'h> { let mut acc = self.value(left); while let Some((o, r)) = rest.pop() { let y = self.value(r); - acc = binary_values(o, &acc, &y); + acc = binary_values(o, &acc, &y, self.host.workbook().date_system); } Ev::V(acc) } @@ -1593,12 +1595,12 @@ impl Acc { // ---------------------------------------------------------------- operators -fn map_unary(v: &Value, f: &dyn Fn(f64) -> Value, plus: bool) -> Value { +fn map_unary(v: &Value, f: &dyn Fn(f64) -> Value, plus: bool, sys: gridcraft_core::DateSystem) -> Value { match v { - Value::Array(a) => Value::from(Array { rows: a.rows, cols: a.cols, data: a.data.iter().map(|x| map_unary(x, f, plus)).collect() }), + Value::Array(a) => Value::from(Array { rows: a.rows, cols: a.cols, data: a.data.iter().map(|x| map_unary(x, f, plus, sys)).collect() }), Value::Error(e) => Value::Error(*e), Value::Text(_) if plus => v.clone(), - other => match other.to_number() { + other => match other.to_number_in(sys) { Ok(n) => f(n), Err(e) => Value::Error(e), }, @@ -1651,18 +1653,18 @@ fn zip3(c: &Array, t: &Value, f: &Value, g: impl Fn(&Value, &Value, &Value) -> V Value::from(Array { rows, cols, data }) } -pub fn binary_values(op: BinOp, x: &Value, y: &Value) -> Value { - zip2(x, y, |a, b| binary_scalar(op, a, b)) +pub fn binary_values(op: BinOp, x: &Value, y: &Value, sys: gridcraft_core::DateSystem) -> Value { + zip2(x, y, |a, b| binary_scalar(op, a, b, sys)) } -fn binary_scalar(op: BinOp, a: &Value, b: &Value) -> Value { +fn binary_scalar(op: BinOp, a: &Value, b: &Value, sys: gridcraft_core::DateSystem) -> Value { if let Value::Error(e) = a { return Value::Error(*e); } if let Value::Error(e) = b { return Value::Error(*e); } - let num = |f: fn(f64, f64) -> Value| match (a.to_number(), b.to_number()) { + let num = |f: fn(f64, f64) -> Value| match (a.to_number_in(sys), b.to_number_in(sys)) { (Ok(x), Ok(y)) => f(x, y), (Err(e), _) | (_, Err(e)) => Value::Error(e), }; diff --git a/crates/calc/src/tests.rs b/crates/calc/src/tests.rs index a0c9fe2..1a26eb7 100644 --- a/crates/calc/src/tests.rs +++ b/crates/calc/src/tests.rs @@ -290,6 +290,26 @@ fn circular_references_without_iterative_calculation() { assert_eq!(t.get("A1"), Value::Error(CellError::Circ)); } +#[test] +fn date_text_coerces_in_the_workbook_date_system() { + for (sys, serial) in [(gridcraft_core::DateSystem::D1900, 1462.0), (gridcraft_core::DateSystem::D1904, 0.0)] { + let mut t = T::new(); + t.wb.date_system = sys; + t.set("A1", "'1/1/1904"); + t.set("B1", "=A1+0"); + t.set("C1", "=VALUE(A1)"); + t.set("D1", "=-A1"); + t.set("E1", "=DATEVALUE(A1)"); + t.set("F1", "=NUMBERVALUE(A1)"); + assert_eq!(t.get("A1"), Value::from("1/1/1904")); + assert_eq!(t.num("B1"), serial, "{sys:?}"); + assert_eq!(t.num("C1"), serial, "{sys:?}"); + assert_eq!(t.num("D1"), -serial, "{sys:?}"); + assert_eq!(t.num("E1"), serial, "{sys:?}"); + assert_eq!(t.num("F1"), serial, "{sys:?}"); + } +} + #[test] fn operators() { let mut t = T::new(); diff --git a/crates/core/src/parse.rs b/crates/core/src/parse.rs index a943103..b465bea 100644 --- a/crates/core/src/parse.rs +++ b/crates/core/src/parse.rs @@ -41,7 +41,12 @@ pub fn parse_input(s: &str, sys: DateSystem) -> Parsed { /// Numeric text as arithmetic coerces it ("1,000", "50%", "$3", "1/2/2020", "10:00"). pub fn parse_number_text(s: &str) -> Option { - parse_numeric(s.trim(), DateSystem::D1900).map(|(n, _)| n) + parse_number_text_in(s, DateSystem::D1900) +} + +/// Like [`parse_number_text`], with date text read in the given workbook date system. +pub fn parse_number_text_in(s: &str, sys: DateSystem) -> Option { + parse_numeric(s.trim(), sys).map(|(n, _)| n) } fn parse_numeric(t: &str, sys: DateSystem) -> Option<(f64, Option<&'static str>)> { diff --git a/crates/core/src/value.rs b/crates/core/src/value.rs index 390800f..cfef7ac 100644 --- a/crates/core/src/value.rs +++ b/crates/core/src/value.rs @@ -224,13 +224,17 @@ impl Value { /// Coercion to a number as arithmetic does it: empty = 0, TRUE = 1, numeric text parses /// (including dates, times, percentages and currency), other text = `#VALUE!`. pub fn to_number(&self) -> Result { + self.to_number_in(crate::DateSystem::D1900) + } + /// [`Value::to_number`] with date text read in the given workbook date system. + pub fn to_number_in(&self, sys: crate::DateSystem) -> Result { match self { Value::Empty => Ok(0.0), Value::Number(n) => Ok(*n), Value::Bool(b) => Ok(if *b { 1.0 } else { 0.0 }), - Value::Text(t) => crate::parse::parse_number_text(t).ok_or(CellError::Value), + Value::Text(t) => crate::parse::parse_number_text_in(t, sys).ok_or(CellError::Value), Value::Error(e) => Err(*e), - Value::Array(a) => a.data.first().map(|v| v.to_number()).unwrap_or(Ok(0.0)), + Value::Array(a) => a.data.first().map(|v| v.to_number_in(sys)).unwrap_or(Ok(0.0)), } } /// Coercion to text as `&` does it. diff --git a/crates/functions/src/text.rs b/crates/functions/src/text.rs index 2b77196..ab7f1fc 100644 --- a/crates/functions/src/text.rs +++ b/crates/functions/src/text.rs @@ -1,6 +1,6 @@ //! Text functions. -use gridcraft_core::parse::parse_number_text; +use gridcraft_core::parse::parse_number_text_in; use gridcraft_core::{CellError, Value, number_to_text}; use crate::criteria::wildcard_find; @@ -295,17 +295,17 @@ fn unicode(a: &[Arg], _c: &mut dyn Ctx) -> R { /// VALUE: a blank cell reads as 0, but empty or blank text is not a number (observed: /// `VALUE("")` is `#VALUE!`). -fn value(a: &[Arg], _c: &mut dyn Ctx) -> R { +fn value(a: &[Arg], c: &mut dyn Ctx) -> R { match scalar(arg(a, 0)?) { Value::Number(n) => num_val(n), Value::Empty => num_val(0.0), Value::Error(e) => Err(e), - Value::Text(t) => parse_number_text(&t).ok_or(CellError::Value).and_then(num_val), + Value::Text(t) => parse_number_text_in(&t, c.date_system()).ok_or(CellError::Value).and_then(num_val), _ => Err(CellError::Value), } } -fn numbervalue(a: &[Arg], _c: &mut dyn Ctx) -> R { +fn numbervalue(a: &[Arg], c: &mut dyn Ctx) -> R { let s = text(a, 0)?; let dec = if a.len() > 1 { text(a, 1)?.chars().next().ok_or(CellError::Value)? } else { '.' }; let grp = if a.len() > 2 { text(a, 2)?.chars().next().ok_or(CellError::Value)? } else { ',' }; @@ -338,7 +338,7 @@ fn numbervalue(a: &[Arg], _c: &mut dyn Ctx) -> R { out.push(c); } } - let mut n = parse_number_text(&out).ok_or(CellError::Value)?; + let mut n = parse_number_text_in(&out, c.date_system()).ok_or(CellError::Value)?; for _ in 0..pct { n /= 100.0; }