use super::funejson::Value; use super::dates_add_days::{add_days, parse_iso_date}; use super::dates_recurrence::{recurrence, RecurrenceRule}; /// 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 { 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()) }