Functional Weave
Code in Python

charity.regular-giving-schedule@1.0.0

impl/rust.rs

2,477 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 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::{add_days, parse_iso_date};  ← from dates.add-days ^1.0.0 · built alongside by fune
use super::dates_recurrence::{recurrence, RecurrenceRule};  ← from dates.recurrence ^1.0.0 · built alongside by fune

/// Collection dates for a regular gift. Each date is clamped from the chosen
/// day, not from the previous date (dates.recurrence), so a gift on the 31st
/// is taken on 28 February and on 31 March again.
///
/// # Panics
/// Panics on an unknown frequency, a day outside 1-31, negative lead days or
/// count, or a malformed date.
pub fn regular_giving_schedule(
    frequency: &str,
    collection_day: i64,
    sign_up_date: &str,
    lead_days: i64,
    end_date: Option<&str>,
    count: i64,
) -> Vec<String> {
    let interval = match frequency {
        "monthly" => 1,
        "quarterly" => 3,
        "annually" => 12,
        _ => panic!("unknown giving frequency \"{}\": expected monthly, quarterly or annually", frequency),
    };
    if !(1..=31).contains(&collection_day) {
        panic!("collectionDay must be 1-31, received {}", collection_day);
    }
    if lead_days < 0 {
        panic!("leadDays must not be negative, received {}", lead_days);
    }
    if count < 0 {
        panic!("count must be a non-negative integer, received {}", count);
    }
    if let Some(end) = end_date {
        parse_iso_date(end);
    }
    let first = add_days(sign_up_date, lead_days);
    if let Some(end) = end_date {
        if end < first.as_str() {
            return Vec::new();
        }
    }
    let rule = RecurrenceRule {
        kind: "monthly-on-day".to_string(),
        interval,
        day: Some(collection_day),
        weekday: None,
    };
    recurrence(&rule, &first, count)
        .into_iter()
        .filter(|d| end_date.map_or(true, |end| d.as_str() <= end))
        .collect()
}

fn int_arg(v: &Value, name: &str, message: &str) -> i64 {
    if let Value::Float(f) = v {
        if f.fract() != 0.0 {
            panic!("{} {}, received {}", name, message, f);
        }
    }
    v.as_i64()
}

pub fn fune_vector(args: &[Value]) -> Value {
    let day = int_arg(&args[1], "collectionDay", "must be 1-31");
    let lead = int_arg(&args[3], "leadDays", "must not be negative");
    let count = int_arg(&args[5], "count", "must be a non-negative integer");
    let end = if args[4].is_null() { None } else { Some(args[4].as_str()) };
    let dates = regular_giving_schedule(args[0].as_str(), day, args[2].as_str(), lead, end, count);
    Value::Arr(dates.iter().map(|d| Value::str(d)).collect())
}