formualizer-eval 0.6.0

High-performance Arrow-backed Excel formula engine with dependency graph and incremental recalculation
Documentation
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) -> NaiveDate {
    NaiveDate::from_ymd_opt(y, m, d).unwrap()
}

fn assert_date_like(v: Option<LiteralValue>, expected: NaiveDate) {
    match v {
        Some(LiteralValue::Date(d)) => assert_eq!(d, expected),
        Some(LiteralValue::DateTime(dt)) => assert_eq!(dt.date(), expected),
        Some(LiteralValue::Number(n)) => {
            let got = NaiveDate::from_ymd_opt(1899, 12, 30).unwrap()
                + chrono::Duration::days(n.trunc() as i64);
            assert_eq!(got, expected, "serial {n} did not map to {expected}");
        }
        other => panic!("expected date-like {expected:?}, got {other:?}"),
    }
}

fn run_edate(start: NaiveDate, months: i64) -> Option<LiteralValue> {
    let mut engine = Engine::new(TestWorkbook::new(), EvalConfig::default());
    engine
        .set_cell_value("Sheet1", 1, 1, LiteralValue::Date(start))
        .unwrap();
    engine
        .set_cell_formula(
            "Sheet1",
            1,
            2,
            parse(format!("=EDATE($A$1, {months})")).unwrap(),
        )
        .unwrap();
    engine.evaluate_all().unwrap();
    engine.get_cell_value("Sheet1", 1, 2)
}

#[test]
fn edate_with_date_cell_adds_months_preserving_day() {
    assert_date_like(run_edate(date(2025, 2, 4), 15), date(2026, 5, 4));
}

#[test]
fn edate_with_date_cell_clamps_to_month_end() {
    assert_date_like(run_edate(date(2025, 1, 31), 1), date(2025, 2, 28));
}

#[test]
fn edate_with_date_cell_handles_negative_months() {
    assert_date_like(run_edate(date(2025, 3, 15), -3), date(2024, 12, 15));
}

#[test]
fn eomonth_with_date_cell_returns_month_end() {
    let mut engine = Engine::new(TestWorkbook::new(), EvalConfig::default());
    engine
        .set_cell_value("Sheet1", 1, 1, LiteralValue::Date(date(2025, 2, 4)))
        .unwrap();
    engine
        .set_cell_formula("Sheet1", 1, 2, parse("=EOMONTH($A$1, 0)").unwrap())
        .unwrap();
    engine.evaluate_all().unwrap();
    assert_date_like(engine.get_cell_value("Sheet1", 1, 2), date(2025, 2, 28));
}