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()))
}