Functional Weave
Code in Rust

property.rent-proration@1.0.1

impl/rust.rs

2,483 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_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(),
    ))
}