Functional Weave
Code in Rust

dates.add-months@1.0.0

impl/rust.rs

1,682 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::{days_in_month, format_iso_date, parse_iso_date, CivilDate};  ← from dates.add-days ^1.0.0 · built alongside by fune

/// Add whole months, keeping the day of the month and clamping it to the last
/// day of the target month when that month is shorter (31 Jan + 1 = 28/29 Feb).
///
/// The clamp is applied to the result only. Callers stepping through a schedule
/// must add k months to the original anchor, not 1 month k times, or the 31st
/// decays to the 28th after February.
///
/// # Panics
/// Panics on a malformed or impossible date, or a result outside 0001-9999.
pub fn add_months(iso: &str, months: i64) -> String {
    let date = parse_iso_date(iso);
    // Count months from year 0 so the year and month fall out of one division.
    // div_euclid floors like Python's // and Math.floor for a negative total.
    let total = date.year * 12 + (date.month - 1) + months;
    let year = total.div_euclid(12);
    let month = total.rem_euclid(12) + 1;
    if !(1..=9999).contains(&year) {
        panic!(
            "\"{}\" plus {} months is outside the supported range 0001-01-01 to 9999-12-31",
            iso, months
        );
    }
    let day = date.day.min(days_in_month(year, month));
    format_iso_date(&CivilDate { year, month, day })
}

pub fn fune_vector(args: &[Value]) -> Value {
    // Refuse what the typed signature cannot hold, with the wording TypeScript
    // and Python use, rather than let the conversion below quietly change it.
    if let Value::Float(f) = args[1] {
        if f.fract() != 0.0 {
            panic!("months must be an integer, received {}", f);
        }
    }
    Value::str(&add_months(args[0].as_str(), args[1].as_i64()))
}