Functional Weave
Code in Python

logistics.delivery-date@1.0.0

impl/rust.rs

3,733 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_business_days::add_business_days;  ← from dates.add-business-days ^1.0.0 · built alongside by fune
use super::dates_bank_holidays::bank_holidays;  ← from dates.bank-holidays ^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

fn is_time(value: &str) -> bool {
    let b = value.as_bytes();
    if b.len() != 5 || b[2] != b':' || !b.iter().enumerate().all(|(i, c)| i == 2 || c.is_ascii_digit()) {
        return false;
    }
    let hours = (b[0] - b'0') * 10 + (b[1] - b'0');
    hours <= 23 && b[3] <= b'5'
}

fn estimate(order_date: &str, order_time: &str, cut_off: &str, service_days: i64, holidays: &[String]) -> DeliveryEstimate {
    let working = day_of_week(order_date) <= 5 && !holidays.iter().any(|h| h == order_date);
    // HH:MM compares correctly as text. Strictly before: 15:00 misses a 15:00 cut-off.
    let same_day = working && order_time < cut_off;
    let dispatch = if same_day {
        order_date.to_string()
    } else {
        add_business_days(order_date, 1, holidays)
    };
    DeliveryEstimate {
        delivery_date: add_business_days(&dispatch, service_days, holidays),
        dispatch_date: dispatch,
        same_day_dispatch: same_day,
    }
}

/// When an order leaves the warehouse and when it should arrive: same
/// working day if placed before the cut-off, then service_days working days
/// in transit, skipping weekends, the region's bank holidays and the
/// caller's closures.
///
/// # Panics
/// Panics on a malformed time or date, negative service days, an unknown
/// region, or an estimate reaching a year with no published bank holidays.
pub fn delivery_date(
    order_date: &str,
    order_time: &str,
    cut_off: &str,
    service_days: i64,
    region: Option<&str>,
    closures: &[String],
) -> DeliveryEstimate {
    if !is_time(order_time) {
        panic!("orderTime must be a 24-hour time HH:MM, received \"{}\"", order_time);
    }
    if !is_time(cut_off) {
        panic!("cutOff must be a 24-hour time HH:MM, received \"{}\"", cut_off);
    }
    if service_days < 0 {
        panic!("serviceDays must not be negative, received {}", service_days);
    }
    day_of_week(order_date); // validates the date before its year is used

    let mut holidays: Vec<String> = closures.to_vec();
    let mut last_year: i64 = order_date[0..4].parse().unwrap();
    if let Some(r) = region {
        holidays.extend(bank_holidays(r, last_year).into_iter().map(|h| h.date));
    }
    // Load each later year the estimate reaches: an order on 31 December must
    // skip next year's New Year's Day. More holidays only move the answer later,
    // so this settles.
    loop {
        let result = estimate(order_date, order_time, cut_off, service_days, &holidays);
        let reached: i64 = result.delivery_date[0..4].parse().unwrap();
        let r = match region {
            Some(r) if reached > last_year => r,
            _ => return result,
        };
        for year in (last_year + 1)..=reached {
            holidays.extend(bank_holidays(r, year).into_iter().map(|h| h.date));
        }
        last_year = reached;
    }
}

pub fn delivery_estimate_to_value(e: &DeliveryEstimate) -> Value {
    Value::obj(vec![
        ("dispatchDate", Value::str(&e.dispatch_date)),
        ("deliveryDate", Value::str(&e.delivery_date)),
        ("sameDayDispatch", Value::Bool(e.same_day_dispatch)),
    ])
}

pub fn fune_vector(args: &[Value]) -> Value {
    let closures: Vec<String> = args[5].as_arr().iter().map(|v| v.as_str().to_string()).collect();
    let region = if args[4].is_null() { None } else { Some(args[4].as_str()) };
    delivery_estimate_to_value(&delivery_date(
        args[0].as_str(),
        args[1].as_str(),
        args[2].as_str(),
        args[3].as_i64(),
        region,
        &closures,
    ))
}