Functional Weave
Code in Rust

charts.ticks@1.0.0

impl/rust/time_ticks.rs

3,230 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::{civil_from_days, days_from_civil, epoch_day_from_iso, iso_from_epoch_day};  ← from dates.add-days ^1.0.0 · built alongside by fune

// 1970-01-05, the first Monday on or after the epoch, as a day number.
const FIRST_MONDAY: i64 = 4;
const MAX_TICKS: usize = 10000;

fn ceil_div(a: i64, b: i64) -> i64 {
    -((-a).div_euclid(b))
}

/// Calendar boundaries between two dates, inclusive, anchored to the calendar
/// rather than to `start` so that panning does not move them: days by
/// (day - 1) % step, weeks since 1970-01-05, months by (month - 1) % step,
/// quarters by quarter of the year, years by year % step.
///
/// # Panics
/// Panics on a date that is not a real ISO date, an unknown interval, `step`
/// less than 1, or more than 10,000 ticks.
pub fn time_ticks(start: &str, stop: &str, interval: &str, step: i64) -> Vec<String> {
    if step < 1 {
        panic!("step must be a whole number of at least 1, got {}", step);
    }
    let a = epoch_day_from_iso(start);
    let b = epoch_day_from_iso(stop);
    let reverse = b < a;
    let (lo, hi) = if reverse { (b, a) } else { (a, b) };
    let mut days: Vec<i64> = Vec::new();
    let add = |day: i64, days: &mut Vec<i64>| {
        if days.len() >= MAX_TICKS {
            panic!("too many ticks: more than {}; use a longer interval or step", MAX_TICKS);
        }
        days.push(day);
    };

    match interval {
        "day" => {
            for d in lo..=hi {
                if (civil_from_days(d).day - 1) % step == 0 {
                    add(d, &mut days);
                }
            }
        }
        "week" => {
            let mut k = ceil_div(lo - FIRST_MONDAY, 7);
            k = ceil_div(k, step) * step;
            let mut d = FIRST_MONDAY + 7 * k;
            while d <= hi {
                add(d, &mut days);
                d += 7 * step;
            }
        }
        "month" | "quarter" | "year" => {
            let months = match interval {
                "month" => step,
                "quarter" => 3 * step,
                _ => 12,
            };
            let first = civil_from_days(lo);
            let (mut year, mut month) = (first.year, first.month);
            loop {
                let d = days_from_civil(year, month, 1);
                if d > hi {
                    break;
                }
                let on_step = if interval == "year" {
                    month == 1 && year % step == 0
                } else {
                    (month - 1) % months == 0
                };
                if d >= lo && on_step {
                    add(d, &mut days);
                }
                month += 1;
                if month > 12 {
                    month = 1;
                    year += 1;
                }
            }
        }
        other => panic!("unknown time interval \"{}\"", other),
    }
    let mut ticks: Vec<String> = days.into_iter().map(iso_from_epoch_day).collect();
    if reverse {
        ticks.reverse();
    }
    ticks
}

pub fn fune_vector(args: &[Value]) -> Value {
    Value::Arr(
        time_ticks(args[0].as_str(), args[1].as_str(), args[2].as_str(), args[3].as_i64())
            .iter()
            .map(|s| Value::str(s))
            .collect(),
    )
}