Functional Weave
Code in TypeScript

dates.month-boundaries@1.0.0

impl/rust.rs

1,901 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

/// The first and last day of the month containing a date, and its length.
///
/// All three are returned together because a caller needs them together - a
/// billing period, a statement range, a pro-rata fraction - and because
/// deriving `end` from `start` in caller code is where "31 January plus one
/// month" bugs come from.
///
/// `end` is the last day of the month, inclusive, not the 1st of the next
/// month. Inclusive is what a statement prints and what a human checks against.
///
/// # Panics
/// Panics if the date is malformed or impossible.
pub fn month_boundaries(iso: &str) -> MonthBoundaries {
    let date = parse_iso_date(iso);
    // February is the only month whose length is not a constant, and it follows
    // the full Gregorian rule: 2024 had 29 days, 1900 had 28 (a century year),
    // 2000 had 29 (divisible by 400).
    let days = days_in_month(date.year, date.month);
    MonthBoundaries {
        start: format_iso_date(&CivilDate {
            year: date.year,
            month: date.month,
            day: 1,
        }),
        end: format_iso_date(&CivilDate {
            year: date.year,
            month: date.month,
            day: days,
        }),
        days,
    }
}

/// True when the date is the last day of its month - a common billing trigger.
pub fn is_month_end(iso: &str) -> bool {
    let date = parse_iso_date(iso);
    date.day == days_in_month(date.year, date.month)
}

pub fn month_boundaries_to_value(bounds: &MonthBoundaries) -> Value {
    Value::obj(vec![
        ("start", Value::str(&bounds.start)),
        ("end", Value::str(&bounds.end)),
        ("days", Value::Int(bounds.days)),
    ])
}

pub fn fune_vector(args: &[Value]) -> Value {
    month_boundaries_to_value(&month_boundaries(args[0].as_str()))
}