Functional Weave
Code in TypeScript

dates.add-business-days@1.0.0

impl/rust.rs

1,836 bytes · the Rust implementation · view raw

Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.

use std::collections::HashSet;

use super::funejson::Value;  ← the fune runtime: the JSON value the test vectors use; fune build keeps it only where a signature takes one
use super::dates_add_days::{epoch_day_from_iso, iso_from_epoch_day};  ← from dates.add-days ^1.0.0 · built alongside by fune
use super::dates_day_of_week::day_of_week;  ← from dates.day-of-week ^1.0.0 · built alongside by fune

/// 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<i64> = 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<String> = 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))
}