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::{civil_from_days, days_in_month, epoch_day_from_iso}; ← from dates.add-days ^1.0.0 · built alongside by fune
use super::math_round_div::round_div; ← from math.round-div ^1.0.0 · built alongside by fune
use super::money_amount::{money, money_from_value, money_to_value, Money}; ← from money.amount ^1.0.0 · built alongside by fune
const MAX_MINOR: i64 = 1_000_000_000;
// lcm(28, 29, 30, 31): every month's daily rate is a whole number of these parts.
const MONTH_PARTS: i64 = 377_580;
/// Rent for the days from_date to to_date inclusive, from a monthly rent, as
/// one exact fraction rounded half-up once. The daily rate is never rounded
/// on its own, which is the usual source of a pound's error on a part-month
/// invoice.
///
/// # Panics
/// Panics on an out-of-range rent, a malformed date, a period that runs
/// backwards or is longer than 366 days, or an unknown method.
pub fn prorate_rent(monthly_rent: &Money, from_date: &str, to_date: &str, method: &str) -> Money {
if monthly_rent.minor.abs() > MAX_MINOR {
panic!(
"monthlyRent must be at most {} minor units either way, received {}",
MAX_MINOR, monthly_rent.minor
);
}
let start = epoch_day_from_iso(from_date);
let end = epoch_day_from_iso(to_date);
if end < start {
panic!("toDate {} is before fromDate {}", to_date, from_date);
}
let days = end - start + 1;
if days > 366 {
panic!("the period must be at most 366 days, received {}", days);
}
match method {
"annual-daily" => money(round_div(monthly_rent.minor * 12 * days, 365, "half-up"), &monthly_rent.currency),
"calendar-month" => {
// Each day is worth MONTH_PARTS / (days in its month) parts of a month.
let mut parts = 0i64;
let mut day = start;
while day <= end {
let date = civil_from_days(day);
let length = days_in_month(date.year, date.month);
let segment_end = end.min(day + (length - date.day));
parts += (segment_end - day + 1) * (MONTH_PARTS / length);
day = segment_end + 1;
}
money(round_div(monthly_rent.minor * parts, MONTH_PARTS, "half-up"), &monthly_rent.currency)
}
other => panic!("unknown method \"{}\": use annual-daily or calendar-month", other),
}
}
pub fn fune_vector(args: &[Value]) -> Value {
money_to_value(&prorate_rent(
&money_from_value(&args[0]),
args[1].as_str(),
args[2].as_str(),
args[3].as_str(),
))
}