ironcalc_base 0.7.1

Open source spreadsheet engine
Documentation
#![allow(clippy::unwrap_used)]

use crate::test::util::new_empty_model;

// Test data: Jan 1-10, 2023 week
const JAN_1_2023: i32 = 44927; // Sunday
const JAN_2_2023: i32 = 44928; // Monday
const JAN_6_2023: i32 = 44932; // Friday
const JAN_9_2023: i32 = 44935; // Monday
const JAN_10_2023: i32 = 44936; // Tuesday

#[test]
fn networkdays_calculates_weekdays_excluding_weekends() {
    let mut model = new_empty_model();

    model._set("A1", &format!("=NETWORKDAYS({JAN_1_2023},{JAN_10_2023})"));
    model.evaluate();

    assert_eq!(
        model._get_text("A1"),
        "7",
        "Should count 7 weekdays in 10-day span"
    );
}

#[test]
fn networkdays_handles_reverse_date_order() {
    let mut model = new_empty_model();

    model._set("A1", &format!("=NETWORKDAYS({JAN_10_2023},{JAN_1_2023})"));
    model.evaluate();

    assert_eq!(
        model._get_text("A1"),
        "-7",
        "Reversed dates should return negative count"
    );
}

#[test]
fn networkdays_excludes_holidays_from_weekdays() {
    let mut model = new_empty_model();

    model._set(
        "A1",
        &format!("=NETWORKDAYS({JAN_1_2023},{JAN_10_2023},{JAN_9_2023})"),
    );
    model.evaluate();

    assert_eq!(
        model._get_text("A1"),
        "6",
        "Should exclude Monday holiday from 7 weekdays"
    );
}

#[test]
fn networkdays_handles_same_start_end_date() {
    let mut model = new_empty_model();

    model._set("A1", &format!("=NETWORKDAYS({JAN_9_2023},{JAN_9_2023})"));
    model.evaluate();

    assert_eq!(
        model._get_text("A1"),
        "1",
        "Same weekday date should count as 1 workday"
    );
}

#[test]
fn networkdays_accepts_holiday_ranges() {
    let mut model = new_empty_model();

    model._set("B1", &JAN_2_2023.to_string());
    model._set("B2", &JAN_6_2023.to_string());
    model._set(
        "A1",
        &format!("=NETWORKDAYS({JAN_1_2023},{JAN_10_2023},B1:B2)"),
    );
    model.evaluate();

    assert_eq!(
        model._get_text("A1"),
        "5",
        "Should exclude 2 holidays from 7 weekdays"
    );
}

#[test]
fn networkdays_intl_uses_standard_weekend_by_default() {
    let mut model = new_empty_model();

    model._set(
        "A1",
        &format!("=NETWORKDAYS.INTL({JAN_1_2023},{JAN_10_2023})"),
    );
    model.evaluate();

    assert_eq!(
        model._get_text("A1"),
        "7",
        "Default should be Saturday-Sunday weekend"
    );
}

#[test]
fn networkdays_intl_supports_numeric_weekend_patterns() {
    let mut model = new_empty_model();

    // Pattern 2 = Sunday-Monday weekend
    model._set(
        "A1",
        &format!("=NETWORKDAYS.INTL({JAN_1_2023},{JAN_10_2023},2)"),
    );
    model.evaluate();

    assert_eq!(
        model._get_text("A1"),
        "6",
        "Sunday-Monday weekend should give 6 workdays"
    );
}

#[test]
fn networkdays_intl_supports_single_day_weekends() {
    let mut model = new_empty_model();

    // Pattern 11 = Sunday only weekend
    model._set(
        "A1",
        &format!("=NETWORKDAYS.INTL({JAN_1_2023},{JAN_10_2023},11)"),
    );
    model.evaluate();

    assert_eq!(
        model._get_text("A1"),
        "8",
        "Sunday-only weekend should give 8 workdays"
    );
}

#[test]
fn networkdays_intl_supports_string_weekend_patterns() {
    let mut model = new_empty_model();

    // "0000110" = Friday-Saturday weekend
    model._set(
        "A1",
        &format!("=NETWORKDAYS.INTL({JAN_1_2023},{JAN_10_2023},\"0000110\")"),
    );
    model.evaluate();

    assert_eq!(
        model._get_text("A1"),
        "8",
        "Friday-Saturday weekend should give 8 workdays"
    );
}

#[test]
fn networkdays_intl_no_weekends_counts_all_days() {
    let mut model = new_empty_model();

    model._set(
        "A1",
        &format!("=NETWORKDAYS.INTL({JAN_1_2023},{JAN_10_2023},\"0000000\")"),
    );
    model.evaluate();

    assert_eq!(
        model._get_text("A1"),
        "10",
        "No weekends should count all 10 days"
    );
}

#[test]
fn networkdays_intl_combines_custom_weekends_with_holidays() {
    let mut model = new_empty_model();

    model._set(
        "A1",
        &format!("=NETWORKDAYS.INTL({JAN_1_2023},{JAN_10_2023},\"0000110\",{JAN_9_2023})"),
    );
    model.evaluate();

    assert_eq!(
        model._get_text("A1"),
        "7",
        "Should exclude both weekend and holiday"
    );
}

#[test]
fn networkdays_validates_argument_count() {
    let mut model = new_empty_model();

    model._set("A1", "=NETWORKDAYS()");
    model._set("A2", "=NETWORKDAYS(1,2,3,4)");
    model._set("A3", "=NETWORKDAYS.INTL()");
    model._set("A4", "=NETWORKDAYS.INTL(1,2,3,4,5)");

    model.evaluate();

    assert_eq!(model._get_text("A1"), "#ERROR!");
    assert_eq!(model._get_text("A2"), "#ERROR!");
    assert_eq!(model._get_text("A3"), "#ERROR!");
    assert_eq!(model._get_text("A4"), "#ERROR!");
}

#[test]
fn networkdays_rejects_invalid_dates() {
    let mut model = new_empty_model();

    model._set("A1", "=NETWORKDAYS(-1,100)");
    model._set("A2", "=NETWORKDAYS(1,3000000)");
    model._set("A3", "=NETWORKDAYS(\"text\",100)");

    model.evaluate();

    assert_eq!(model._get_text("A1"), "#NUM!");
    assert_eq!(model._get_text("A2"), "#NUM!");
    assert_eq!(model._get_text("A3"), "#VALUE!");
}

#[test]
fn networkdays_intl_rejects_invalid_weekend_patterns() {
    let mut model = new_empty_model();

    model._set("A1", "=NETWORKDAYS.INTL(1,10,99)");
    model._set("A2", "=NETWORKDAYS.INTL(1,10,\"111110\")");
    model._set("A3", "=NETWORKDAYS.INTL(1,10,\"11111000\")");
    model._set("A4", "=NETWORKDAYS.INTL(1,10,\"1111102\")");

    model.evaluate();

    assert_eq!(model._get_text("A1"), "#NUM!");
    assert_eq!(model._get_text("A2"), "#VALUE!");
    assert_eq!(model._get_text("A3"), "#VALUE!");
    assert_eq!(model._get_text("A4"), "#VALUE!");
}

#[test]
fn networkdays_rejects_invalid_holidays() {
    let mut model = new_empty_model();

    model._set("B1", "invalid");
    model._set(
        "A1",
        &format!("=NETWORKDAYS({JAN_1_2023},{JAN_10_2023},B1)"),
    );
    model._set(
        "A2",
        &format!("=NETWORKDAYS({JAN_1_2023},{JAN_10_2023},-1)"),
    );

    model.evaluate();

    assert_eq!(
        model._get_text("A1"),
        "#VALUE!",
        "Should reject non-numeric holidays"
    );
    assert_eq!(
        model._get_text("A2"),
        "#NUM!",
        "Should reject out-of-range holidays"
    );
}

#[test]
fn networkdays_handles_weekend_only_periods() {
    let mut model = new_empty_model();

    let saturday = JAN_1_2023 - 1;
    model._set("A1", &format!("=NETWORKDAYS({saturday},{JAN_1_2023})"));
    model.evaluate();

    assert_eq!(
        model._get_text("A1"),
        "0",
        "Weekend-only period should count 0 workdays"
    );
}

#[test]
fn networkdays_ignores_holidays_outside_date_range() {
    let mut model = new_empty_model();

    let future_holiday = JAN_10_2023 + 100;
    model._set(
        "A1",
        &format!("=NETWORKDAYS({JAN_1_2023},{JAN_10_2023},{future_holiday})"),
    );
    model.evaluate();

    assert_eq!(
        model._get_text("A1"),
        "7",
        "Out-of-range holidays should be ignored"
    );
}

#[test]
fn networkdays_handles_empty_holiday_ranges() {
    let mut model = new_empty_model();

    model._set(
        "A1",
        &format!("=NETWORKDAYS({JAN_1_2023},{JAN_10_2023},B1:B3)"),
    );
    model.evaluate();

    assert_eq!(
        model._get_text("A1"),
        "7",
        "Empty holiday range should be treated as no holidays"
    );
}

#[test]
fn networkdays_handles_minimum_valid_dates() {
    let mut model = new_empty_model();

    model._set("A1", "=NETWORKDAYS(1,7)");
    model.evaluate();

    assert_eq!(
        model._get_text("A1"),
        "5",
        "Should handle earliest Excel dates correctly"
    );
}

#[test]
fn networkdays_handles_large_date_ranges_efficiently() {
    let mut model = new_empty_model();

    model._set("A1", "=NETWORKDAYS(1,365)");
    model.evaluate();

    assert!(
        !model._get_text("A1").starts_with('#'),
        "Large ranges should not error"
    );
}