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,
))
}