Functional Weave
Code in Python

todo.next-occurrence@1.0.0

impl/rust.rs

2,227 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::add_days;  ← from dates.add-days ^1.0.0 · built alongside by fune
use super::dates_day_of_week::day_of_week;  ← from dates.day-of-week ^1.0.0 · built alongside by fune
use super::subscriptions_next_billing_date::next_billing_date;  ← from subscriptions.next-billing-date ^1.0.0 · built alongside by fune
use super::todo_item::{recurrence_from_value, Recurrence};  ← from todo.item ^1.0.0 · built alongside by fune

/// The first date of the series strictly after both `due` and `today`.
///
/// Every occurrence is the anchor plus whole intervals, so month ends stay
/// month ends; finishing late skips the occurrences already missed.
///
/// # Panics
/// Panics on an unknown frequency, an interval below 1 (or above 1 for
/// weekdays), an impossible date, or an anchor after the due date.
pub fn next_occurrence(recurrence: &Recurrence, due: &str, today: &str) -> String {
    let frequency = recurrence.frequency.as_str();
    let interval = recurrence.interval;
    let step = match frequency {
        "daily" => "day",
        "weekly" => "week",
        "monthly" => "month",
        "yearly" => "year",
        "weekdays" => "",
        other => panic!(
            "unknown repeat frequency \"{}\": expected daily, weekdays, weekly, monthly or yearly",
            other
        ),
    };
    if interval < 1 {
        panic!("repeat interval must be 1 or more, received {}", interval);
    }
    if frequency == "weekdays" && interval != 1 {
        panic!("weekday repeats cannot skip weeks: interval must be 1, received {}", interval);
    }
    add_days(&recurrence.anchor, 0);
    add_days(due, 0);
    add_days(today, 0);
    if recurrence.anchor.as_str() > due {
        panic!("repeat anchor {} is after the due date {}", recurrence.anchor, due);
    }

    let after = if due > today { due } else { today };
    if frequency == "weekdays" {
        let mut next = add_days(after, 1);
        while day_of_week(&next) > 5 {
            next = add_days(&next, 1);
        }
        return next;
    }
    next_billing_date(&recurrence.anchor, step, interval, after)
}

pub fn fune_vector(args: &[Value]) -> Value {
    if let Value::Float(f) = args[0].get("interval") {
        panic!("repeat interval must be 1 or more, received {}", f);
    }
    let rule = recurrence_from_value(&args[0]).expect("a recurrence");
    Value::Str(next_occurrence(&rule, args[1].as_str(), args[2].as_str()))
}