use chrono::{Datelike, NaiveDate};
use std::collections::{BTreeSet, HashMap};
pub fn cal_yearly_days(dates: &[NaiveDate]) -> i64 {
assert!(!dates.is_empty(), "输入的日期数量必须大于0");
let dedup: BTreeSet<NaiveDate> = dates.iter().copied().collect();
let min = *dedup.iter().next().unwrap();
let max = *dedup.iter().next_back().unwrap();
if (max - min).num_days() < 365 {
log::warn!("时间跨度小于一年,直接返回 252");
return 252;
}
let mut per_year: HashMap<i32, i64> = HashMap::new();
for d in &dedup {
*per_year.entry(d.year()).or_insert(0) += 1;
}
per_year.values().copied().max().unwrap_or(0).min(365)
}
#[cfg(test)]
mod tests {
use super::*;
use chrono::NaiveDate;
fn d(y: i32, m: u32, day: u32) -> NaiveDate {
NaiveDate::from_ymd_opt(y, m, day).unwrap()
}
#[test]
#[should_panic(expected = "输入的日期数量必须大于0")]
fn empty_input_panics() {
cal_yearly_days(&[]);
}
#[test]
fn span_less_than_one_year_returns_252() {
let dates: Vec<NaiveDate> = (1..=200)
.map(|i| d(2024, 1, 1) + chrono::Duration::days(i))
.collect();
assert_eq!(cal_yearly_days(&dates), 252);
}
#[test]
fn multi_year_returns_max_year_count() {
let mut dates = Vec::new();
for i in 0..260 {
dates.push(d(2022, 1, 1) + chrono::Duration::days(i));
}
for i in 0..250 {
dates.push(d(2023, 1, 1) + chrono::Duration::days(i));
}
dates.push(d(2024, 2, 1));
assert_eq!(cal_yearly_days(&dates), 260);
}
#[test]
fn clamped_to_365() {
let dates: Vec<NaiveDate> = (0..800)
.map(|i| d(2020, 1, 1) + chrono::Duration::days(i))
.collect();
assert!(cal_yearly_days(&dates) <= 365);
}
}