From 554eece9628233156082449fc04a7700c1720cb3 Mon Sep 17 00:00:00 2001 From: "Dylan M. Taylor" Date: Tue, 3 Nov 2026 13:56:59 +0000 Subject: [PATCH] engine: weekday fill series moves by whole weeks edit.fillSeries with dateUnit "weekday" stepped one calendar day at a time, so a large step cost work proportional to its size for every cell. It now uses add_weekdays, which moves whole weeks arithmetically. --- crates/engine/src/fill.rs | 17 +++-------------- crates/engine/src/tests.rs | 18 ++++++++++++++++++ 2 files changed, 21 insertions(+), 14 deletions(-) diff --git a/crates/engine/src/fill.rs b/crates/engine/src/fill.rs index c63d7c983..efa09310a 100644 --- a/crates/engine/src/fill.rs +++ b/crates/engine/src/fill.rs @@ -575,20 +575,9 @@ pub fn series(cx: &mut Ctx, sheet: usize, options: SeriesOptions<'_>) -> Result< match (unit, d) { ("month", Some(_)) => (step as i64).checked_mul(i as i64).and_then(|m| add_months_clamped(sys, start, m)).unwrap_or(v), ("year", Some(_)) => (step as i64).checked_mul(i as i64 * 12).and_then(|m| add_months_clamped(sys, start, m)).unwrap_or(v), - ("weekday", Some(_)) => { - let mut n = v; - let mut left = step.abs() as i64; - while left > 0 { - n += step.signum(); - let Some(day) = datetime_from_serial(sys, n) else { - return Err(crate::EngineError::Other("The weekday series runs past the supported date range.".into())); - }; - if day.weekday != 0 && day.weekday != 6 { - left -= 1; - } - } - n - } + // Whole weeks at a time: a huge step costs no more than a small one. + ("weekday", Some(_)) => add_weekdays(sys, v, step as i64) + .ok_or_else(|| crate::EngineError::Other("The weekday series runs past the supported date range.".into()))?, _ => v + step, } } diff --git a/crates/engine/src/tests.rs b/crates/engine/src/tests.rs index 94a80f824..48e484d70 100644 --- a/crates/engine/src/tests.rs +++ b/crates/engine/src/tests.rs @@ -3975,6 +3975,24 @@ fn fill_series_weekday_out_of_range_step_errors() { assert_eq!(v(&s, "A2"), Value::Empty); } +/// A step of a million weekdays moves whole weeks at a time instead of visiting every day, and +/// lands where counting day by day would. +#[test] +fn fill_series_weekday_large_step_is_arithmetic() { + let mut s = s(); + // 45005 is Monday 20 March 2023; five weekdays on from a Monday is the next Monday. + s.execute("cell.set", json!({"cell": "A1", "input": "45005"})).unwrap(); + s.execute("edit.fillSeries", json!({"range": "A1:A3", "direction": "columns", "type": "date", "dateUnit": "weekday", "step": 1_000_000})) + .unwrap(); + assert_eq!(v(&s, "A2"), Value::Number(45005.0 + 1_400_000.0)); + assert_eq!(v(&s, "A3"), Value::Number(45005.0 + 2_800_000.0)); + s.execute("cell.set", json!({"cell": "B1", "input": "45009"})).unwrap(); + s.execute("edit.fillSeries", json!({"range": "B1:B3", "direction": "columns", "type": "date", "dateUnit": "weekday", "step": -1})).unwrap(); + // Friday 24 March 2023 back one weekday at a time: Thursday, Wednesday. + assert_eq!(v(&s, "B2"), Value::Number(45008.0)); + assert_eq!(v(&s, "B3"), Value::Number(45007.0)); +} + #[test] fn fill_series_month_leap_year() { let s = series_date("2028-01-31", "A1:A3", "month");