use super::funejson::Value; use super::dates_add_days::{add_days, parse_iso_date}; use super::dates_bank_holidays::bank_holidays; use super::dates_day_of_week::day_of_week; /// A deadline in days by CPR 2.8: clear days, short periods, court-office closures. /// /// # Panics /// Panics on a bad date, days below 1, an unknown rule, atCourtOffice outside a /// within period, closed days without atCourtOffice, or a year without /// published bank holidays when one is needed. pub fn court_deadline( trigger_date: &str, days: i64, rule: &str, at_court_office: bool, court_closed_days: &[String], ) -> CourtDeadline { parse_iso_date(trigger_date); if days < 1 { panic!("days must be an integer of at least 1, received {}", days); } if rule != "within" && rule != "at-least-after" && rule != "at-least-before" { panic!("unknown deadline rule \"{}\": expected within, at-least-after or at-least-before", rule); } if at_court_office && rule != "within" { panic!("atCourtOffice applies only to a within period (CPR 2.8(5))"); } if !at_court_office && !court_closed_days.is_empty() { panic!("courtClosedDays only apply when atCourtOffice is true"); } for d in court_closed_days { parse_iso_date(d); } // England and Wales holidays by year, fetched only for the years a count touches. let mut holidays: Vec<(i64, Vec)> = Vec::new(); let mut is_non_working = |iso: &str| -> bool { if day_of_week(iso) >= 6 { return true; } let year: i64 = iso[0..4].parse().unwrap(); if !holidays.iter().any(|(y, _)| *y == year) { let dates = bank_holidays("england-and-wales", year).into_iter().map(|h| h.date).collect(); holidays.push((year, dates)); } holidays.iter().find(|(y, _)| *y == year).unwrap().1.iter().any(|d| d == iso) }; let short_period = days <= 5; let step = if rule == "at-least-before" { -1 } else { 1 }; // Walk the clear days: the trigger day is never one, and in a short period // weekends and bank holidays are passed over without counting. let mut cursor = trigger_date.to_string(); let mut counted = 0; while counted < days { cursor = add_days(&cursor, step); if !short_period || !is_non_working(&cursor) { counted += 1; } } if rule != "within" { // The act or event falls on the day beyond the last clear day. return CourtDeadline { date: add_days(&cursor, step), short_period, extended: false }; } let mut date = cursor; let mut extended = false; if at_court_office { while is_non_working(&date) || court_closed_days.iter().any(|d| *d == date) { date = add_days(&date, 1); extended = true; } } CourtDeadline { date, short_period, extended } } pub fn court_deadline_to_value(d: &CourtDeadline) -> Value { Value::obj(vec![ ("date", Value::str(&d.date)), ("shortPeriod", Value::Bool(d.short_period)), ("extended", Value::Bool(d.extended)), ]) } pub fn fune_vector(args: &[Value]) -> Value { let closed: Vec = args[4].as_arr().iter().map(|v| v.as_str().to_string()).collect(); court_deadline_to_value(&court_deadline( args[0].as_str(), args[1].as_i64(), args[2].as_str(), args[3].as_bool(), &closed, )) }