use chrono::NaiveDate;
use crate::engine::{Engine, EvalConfig};
use crate::test_workbook::TestWorkbook;
use formualizer_common::LiteralValue;
use formualizer_parse::parser::parse;
fn date(y: i32, m: u32, d: u32) -> LiteralValue {
LiteralValue::Date(NaiveDate::from_ymd_opt(y, m, d).unwrap())
}
fn fixture() -> Engine<TestWorkbook> {
let mut engine = Engine::new(TestWorkbook::new(), EvalConfig::default());
let dates = [
(2024, 1, 1),
(2024, 4, 1),
(2024, 7, 1),
(2024, 10, 1),
(2025, 1, 1),
];
let serials = [45292, 45383, 45474, 45566, 45658];
for (i, (d, s)) in dates.iter().zip(serials.iter()).enumerate() {
let row = i as u32 + 1;
engine
.set_cell_value("Sheet1", row, 1, date(d.0, d.1, d.2))
.unwrap();
engine
.set_cell_value("Sheet1", row, 2, LiteralValue::Int(*s))
.unwrap();
}
for (i, v) in [10, 20, 30, 40, 50].iter().enumerate() {
let row = i as u32 + 1;
engine
.set_cell_value("Sheet1", row, 3, LiteralValue::Int(*v))
.unwrap();
engine
.set_cell_value("Sheet1", row, 4, LiteralValue::Int(*v))
.unwrap();
}
engine
.set_cell_value("Sheet1", 3, 3, date(2024, 7, 1))
.unwrap();
engine
.set_cell_value("Sheet1", 3, 4, LiteralValue::Int(45474))
.unwrap();
for (i, v) in [-1000, 200, 300, 400, 500].iter().enumerate() {
let row = i as u32 + 1;
engine
.set_cell_value("Sheet1", row, 5, LiteralValue::Int(*v))
.unwrap();
engine
.set_cell_value("Sheet1", row, 6, LiteralValue::Int(*v))
.unwrap();
}
engine
.set_cell_value("Sheet1", 4, 5, date(2024, 10, 1))
.unwrap();
engine
.set_cell_value("Sheet1", 4, 6, LiteralValue::Int(45566))
.unwrap();
engine
}
fn eval(engine: &mut Engine<TestWorkbook>, row: u32, formula: &str) -> LiteralValue {
engine
.set_cell_formula("Sheet1", row, 10, parse(formula).unwrap())
.unwrap();
engine.evaluate_all().unwrap();
engine
.get_cell_value("Sheet1", row, 10)
.unwrap_or(LiteralValue::Empty)
}
fn assert_agrees_with_serial_control(date_formula: &str, serial_formula: &str) {
let mut engine = fixture();
let got = eval(&mut engine, 1, date_formula);
let want = eval(&mut engine, 2, serial_formula);
match (&got, &want) {
(LiteralValue::Number(a), LiteralValue::Number(b)) => {
let tol = 1e-9 * b.abs().max(1.0);
assert!(
(a - b).abs() <= tol,
"{date_formula} = {a} but serial control {serial_formula} = {b}"
);
}
_ => panic!(
"{date_formula} => {got:?}, serial control {serial_formula} => {want:?} \
(both must be numbers)"
),
}
}
#[test]
fn controls_already_agreed() {
assert_agrees_with_serial_control("=SUM(C1:C5)", "=SUM(D1:D5)");
assert_agrees_with_serial_control("=AVERAGE(C1:C5)", "=AVERAGE(D1:D5)");
assert_agrees_with_serial_control("=COUNT(C1:C5)", "=COUNT(D1:D5)");
assert_agrees_with_serial_control("=MAX(C1:C5)", "=MAX(D1:D5)");
}
#[test]
fn statistical_collectors_include_date_cells() {
for (d, s) in [
("=MEDIAN(C1:C5)", "=MEDIAN(D1:D5)"),
("=STDEV(C1:C5)", "=STDEV(D1:D5)"),
("=STDEV.P(C1:C5)", "=STDEV.P(D1:D5)"),
("=VAR(C1:C5)", "=VAR(D1:D5)"),
("=LARGE(C1:C5,1)", "=LARGE(D1:D5,1)"),
("=SMALL(C1:C5,5)", "=SMALL(D1:D5,5)"),
("=PRODUCT(C1:C5)", "=PRODUCT(D1:D5)"),
("=DEVSQ(C1:C5)", "=DEVSQ(D1:D5)"),
("=AVEDEV(C1:C5)", "=AVEDEV(D1:D5)"),
("=PERCENTILE(C1:C5,0.5)", "=PERCENTILE(D1:D5,0.5)"),
("=QUARTILE(C1:C5,1)", "=QUARTILE(D1:D5,1)"),
] {
assert_agrees_with_serial_control(d, s);
}
}
#[test]
fn paired_collectors_include_date_cells() {
for (d, s) in [
("=CORREL(C1:C5,E1:E5)", "=CORREL(D1:D5,F1:F5)"),
("=SLOPE(C1:C5,E1:E5)", "=SLOPE(D1:D5,F1:F5)"),
("=RSQ(C1:C5,E1:E5)", "=RSQ(D1:D5,F1:F5)"),
("=COVARIANCE.P(C1:C5,E1:E5)", "=COVARIANCE.P(D1:D5,F1:F5)"),
] {
assert_agrees_with_serial_control(d, s);
}
}
#[test]
fn a_variant_collectors_include_date_cells() {
for (d, s) in [
("=MAXA(A1:A5)", "=MAXA(B1:B5)"),
("=MINA(A1:A5)", "=MINA(B1:B5)"),
("=AVERAGEA(A1:A5)", "=AVERAGEA(B1:B5)"),
("=STDEVA(C1:C5)", "=STDEVA(D1:D5)"),
("=VARA(C1:C5)", "=VARA(D1:D5)"),
] {
assert_agrees_with_serial_control(d, s);
}
}
#[test]
fn cashflow_functions_include_date_cells() {
for (d, s) in [
("=IRR(E1:E5)", "=IRR(F1:F5)"),
("=MIRR(E1:E5,0.1,0.12)", "=MIRR(F1:F5,0.1,0.12)"),
("=NPV(0.1,E1:E5)", "=NPV(0.1,F1:F5)"),
("=XNPV(0.1,E1:E5,B1:B5)", "=XNPV(0.1,F1:F5,B1:B5)"),
("=XIRR(E1:E5,B1:B5)", "=XIRR(F1:F5,B1:B5)"),
] {
assert_agrees_with_serial_control(d, s);
}
}
#[test]
fn bond_functions_accept_date_cells_as_date_arguments() {
for (d, s) in [
(
"=PRICE(A1,A5,0.05,0.06,100,2,0)",
"=PRICE(B1,B5,0.05,0.06,100,2,0)",
),
(
"=YIELD(A1,A5,0.05,95,100,2,0)",
"=YIELD(B1,B5,0.05,95,100,2,0)",
),
(
"=ACCRINTM(A1,A5,0.05,10000,0)",
"=ACCRINTM(B1,B5,0.05,10000,0)",
),
(
"=ACCRINT(A1,A2,A5,0.05,10000,2,0)",
"=ACCRINT(B1,B2,B5,0.05,10000,2,0)",
),
("=TBILLPRICE(A1,A4,0.05)", "=TBILLPRICE(B1,B4,0.05)"),
("=TBILLYIELD(A1,A4,98)", "=TBILLYIELD(B1,B4,98)"),
("=TBILLEQ(A1,A4,0.05)", "=TBILLEQ(B1,B4,0.05)"),
] {
assert_agrees_with_serial_control(d, s);
}
}
#[test]
fn financial_scalars_accept_date_cells() {
assert_agrees_with_serial_control("=SLN(A5,A1,10)", "=SLN(B5,B1,10)");
}