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, epoch_day_from_iso, 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 first billing date strictly after `after_date`: anchor + k intervals
/// for the smallest k >= 0 that lands after it. Each candidate is computed
/// from the anchor, never from the previous billing date, so the 31st stays on
/// month ends instead of decaying to the 28th after February.
///
/// # Panics
/// Panics if `interval_count` is below 1, the interval is unknown, or either
/// date is not a real ISO date.
pub fn next_billing_date(anchor_date: &str, interval: &str, interval_count: i64, after_date: &str) -> String {
if interval_count < 1 {
panic!("interval count must be 1 or more, received {}", interval_count);
}
let (days, months): (i64, i64) = match interval {
"day" => (1, 0),
"week" => (7, 0),
"month" => (0, 1),
"quarter" => (0, 3),
"year" => (0, 12),
other => panic!(
"unknown billing interval \"{}\": expected day, week, month, quarter or year",
other
),
};
let anchor = epoch_day_from_iso(anchor_date);
let after = epoch_day_from_iso(after_date);
if after < anchor {
return anchor_date.to_string();
}
if days > 0 {
let step = days * interval_count;
let k = (after - anchor).div_euclid(step) + 1;
return add_days(anchor_date, k * step);
}
// Jump straight to the period containing after_date's month; the clamp can
// only put that candidate on or before after_date, so at most one or two
// steps forward are needed from there.
let step = months * interval_count;
let a = parse_iso_date(anchor_date);
let b = parse_iso_date(after_date);
let months_apart = (b.year - a.year) * 12 + (b.month - a.month);
let mut k = months_apart.div_euclid(step);
let mut candidate = add_months(anchor_date, k * step);
while epoch_day_from_iso(&candidate) <= after {
k += 1;
candidate = add_months(anchor_date, k * step);
}
candidate
}
pub fn fune_vector(args: &[Value]) -> Value {
Value::str(&next_billing_date(
args[0].as_str(),
args[1].as_str(),
args[2].as_i64(),
args[3].as_str(),
))
}