use std::collections::HashSet; use super::funejson::Value; use super::dates_add_days::{epoch_day_from_iso, iso_from_epoch_day}; use super::dates_day_of_week::day_of_week; /// The `days`-th working day after `iso` (before it when negative), skipping /// Saturdays, Sundays and the listed holidays. The start date is never counted, /// so Friday + 1 and Saturday + 1 are both Monday, as in spreadsheet WORKDAY. /// Zero returns `iso` unchanged. /// /// # Panics /// Panics if the date or any holiday is malformed or impossible, or the result /// leaves 0001-9999. pub fn add_business_days(iso: &str, days: i64, holidays: &[String]) -> String { let mut day = epoch_day_from_iso(iso); // Every holiday is validated even when the move never reaches it, so a typo // in a calendar fails on the next run rather than in the year it matters. let excluded: HashSet = holidays.iter().map(|h| epoch_day_from_iso(h)).collect(); let step: i64 = if days < 0 { -1 } else { 1 }; let mut weekday = day_of_week(iso); let mut remaining = days.abs(); while remaining > 0 { day += step; weekday = (weekday - 1 + step).rem_euclid(7) + 1; if weekday <= 5 && !excluded.contains(&day) { remaining -= 1; } } iso_from_epoch_day(day) } pub fn fune_vector(args: &[Value]) -> Value { // Refuse what the typed signature cannot hold, with the wording TypeScript // and Python use, rather than let the conversion below quietly change it. if let Value::Float(f) = args[1] { if f.fract() != 0.0 { panic!("days must be an integer, received {}", f); } } let holidays: Vec = args[2].as_arr().iter().map(|v| v.as_str().to_string()).collect(); Value::str(&add_business_days(args[0].as_str(), args[1].as_i64(), &holidays)) }