* merge networkdays, networkdays.intl #33 * merge time, timevalue, hour, minute, second #35 * merge datedif, datevalue #36 * merge days, days360, weekday, weeknum, workday, workday.intl, yearfrac, isoweeknum #41 * from excel helper * fix build * date time macros * de-dupe weekend * serial helper * de-dupe now today * weekend pattern enum * remove unused clippy wrong self * fix docs * add test coverage * fix build * fix cursor comment * PR coments + xlsx date time
348 lines
7.9 KiB
Rust
348 lines
7.9 KiB
Rust
#![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"
|
|
);
|
|
}
|