Functional Weave
Code in Python

health.age-in-months@1.0.1

impl/rust.rs

2,062 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::{add_days, days_between, parse_iso_date};  ← from dates.add-days ^1.0.0 · built alongside by fune
use super::dates_add_months::add_months;  ← from dates.add-months ^1.0.0 · built alongside by fune

/// The day the given number of months is completed. add_months clamps 31
/// January + 1 month to 28 February; a clamped day has not yet reached the
/// birth day, so the month completes the day after it (1 March), the same rule
/// dates.age uses for a 29 February birthday.
fn month_anniversary(birth_date: &str, birth_day: i64, months: i64) -> String {
    let shifted = add_months(birth_date, months);
    if parse_iso_date(&shifted).day < birth_day {
        add_days(&shifted, 1)
    } else {
        shifted
    }
}

/// Completed months and remaining days from `birth_date` to `on_date`.
///
/// # Panics
/// Panics on a malformed or impossible date, or `on_date` before `birth_date`.
pub fn age_in_months(birth_date: &str, on_date: &str) -> AgeInMonths {
    let birth = parse_iso_date(birth_date);
    let on = parse_iso_date(on_date);
    if on_date < birth_date {
        panic!("onDate {} is before birthDate {}", on_date, birth_date);
    }
    let mut months = (on.year - birth.year) * 12 + (on.month - birth.month);
    let mut anniversary = month_anniversary(birth_date, birth.day, months);
    if anniversary.as_str() > on_date {
        months -= 1;
        anniversary = month_anniversary(birth_date, birth.day, months);
    }
    AgeInMonths {
        months,
        days: days_between(&anniversary, on_date),
        total_days: days_between(birth_date, on_date),
    }
}

pub fn age_in_months_to_value(age: &AgeInMonths) -> Value {
    Value::obj(vec![
        ("months", Value::Int(age.months)),
        ("days", Value::Int(age.days)),
        ("totalDays", Value::Int(age.total_days)),
    ])
}

fn date_arg(value: &Value) -> &str {
    match value {
        Value::Str(s) => s.as_str(),
        _ => panic!("{:?} is not an ISO date (YYYY-MM-DD)", value),
    }
}

pub fn fune_vector(args: &[Value]) -> Value {
    age_in_months_to_value(&age_in_months(date_arg(&args[0]), date_arg(&args[1])))
}