From 3f9b73b92707a218a44b55fa5194a8ca0fadcab8 Mon Sep 17 00:00:00 2001 From: midasmatterhorn <248507632+midasmatterhorn@users.noreply.github.com> Date: Thu, 15 Oct 2026 17:12:41 +0300 Subject: [PATCH] functions: NETWORKDAYS.INTL accepts the all-weekend string "1111111" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit weekend_mask rejected the all-weekend string outright for both NETWORKDAYS.INTL and WORKDAY.INTL, but Microsoft documents them as differing: NETWORKDAYS.INTL "Using 1111111 will always return 0", WORKDAY.INTL "1111111 is an invalid string". The crate already implements that split — workday_core's per_week == 0 guard — which was unreachable dead code while the extra clause stood. Drop the clause: NETWORKDAYS.INTL counts zero workdays as documented and WORKDAY.INTL errors through the guard it already has. The existing test asserted the old behaviour and is updated. Regression: working_days — on main the NETWORKDAYS.INTL assertion fails (#VALUE! where Excel returns 0); with the clause dropped it passes and WORKDAY.INTL is asserted to error. --- crates/functions/src/date.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/crates/functions/src/date.rs b/crates/functions/src/date.rs index 67c56999e..543c3aafd 100644 --- a/crates/functions/src/date.rs +++ b/crates/functions/src/date.rs @@ -286,7 +286,10 @@ fn weekend_mask(v: Option<&Arg>) -> R<[bool; 7]> { Value::Empty if !arg.from_ref => Ok([true, false, false, false, false, false, true]), Value::Text(t) => { let b = t.as_bytes(); - if b.len() != 7 || !b.iter().all(|c| *c == b'0' || *c == b'1') || b.iter().all(|c| *c == b'1') { + // All seven days marked as the weekend is a valid string: Microsoft documents + // NETWORKDAYS.INTL as "Using 1111111 will always return 0" and WORKDAY.INTL as + // rejecting it, which `workday_core`'s `per_week == 0` guard already implements. + if b.len() != 7 || !b.iter().all(|c| *c == b'0' || *c == b'1') { return Err(CellError::Value); } // String order is Monday..Sunday. @@ -766,7 +769,10 @@ mod tests { close(ev("NETWORKDAYS.INTL", vec![n(d(2006.0, 1.0, 1.0)), n(d(2006.0, 2.0, 1.0)), n(7.0)]), 24.0); close(ev("NETWORKDAYS.INTL", vec![n(d(2006.0, 1.0, 1.0)), n(d(2006.0, 2.0, 1.0)), t("0000110")]), 24.0); close(ev("NETWORKDAYS.INTL", vec![n(d(2006.0, 1.0, 1.0)), n(d(2006.0, 1.0, 31.0)), n(11.0)]), 26.0); - is_err(ev("NETWORKDAYS.INTL", vec![n(1.0), n(10.0), t("1111111")]), CellError::Value); + // An all-weekend string is valid: no day is a workday, so the count is 0. + close(ev("NETWORKDAYS.INTL", vec![n(1.0), n(10.0), t("1111111")]), 0.0); + // WORKDAY.INTL rejects it (there is no working day to move to). + is_err(ev("WORKDAY.INTL", vec![n(1.0), n(10.0), t("1111111")]), CellError::Value); is_err(ev("NETWORKDAYS.INTL", vec![n(1.0), n(10.0), n(8.0)]), CellError::Num); // WORKDAY close(ev("WORKDAY", vec![n(d(2008.0, 10.0, 1.0)), n(151.0)]), d(2009.0, 4.0, 30.0));