use super::funejson::Value; use super::dates_add_days::{add_days, days_between, parse_iso_date}; use super::dates_add_months::add_months; /// 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]))) }