use super::funejson::Value; use super::dates_add_days::{civil_from_days, days_from_civil, epoch_day_from_iso, iso_from_epoch_day}; // 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 { 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 = Vec::new(); let add = |day: i64, days: &mut Vec| { 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 = 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(), ) }