use chrono::{Datelike, NaiveDate};
use once_cell::sync::Lazy;
use std::{
collections::{BTreeMap, HashMap, HashSet, VecDeque},
ops::Range,
sync::RwLock,
};
pub type HolidayMap = HashMap<Country, HashMap<Year, BTreeMap<NaiveDate, Holiday>>>;
pub type Year = i32;
pub type Result<T> = std::result::Result<T, Error>;
static DATA: Lazy<RwLock<HolidayMap>> = Lazy::new(|| RwLock::new(HolidayMap::new()));
pub fn init() -> Result<()> {
let map = Builder::new().build()?;
init_holiday(map)
}
pub fn get(country: Country, date: NaiveDate) -> Result<Option<Holiday>> {
let Some(data) = Lazy::get(&DATA) else { return Err(Error::Uninitialized); };
let data = data.read().map_err(|e| Error::LockError(e.to_string()))?;
let Some(map) = data.get(&country) else {
return Err(Error::CountryNotAvailable);
};
let Some(map) = map.get(&date.year()) else {
return Err(Error::YearNotAvailable);
};
Ok(map.get(&date).cloned())
}
pub fn contains(country: Country, date: NaiveDate) -> Result<bool> {
let Some(data) = Lazy::get(&DATA) else { return Err(Error::Uninitialized); };
let data = data.read().map_err(|e| Error::LockError(e.to_string()))?;
let Some(map) = data.get(&country) else {
return Err(Error::CountryNotAvailable);
};
let Some(map) = map.get(&date.year()) else {
return Err(Error::YearNotAvailable);
};
Ok(map.get(&date).is_some())
}
#[derive(Debug)]
pub struct Iter {
since: NaiveDate,
until: NaiveDate,
buf: VecDeque<Holiday>,
}
impl std::iter::Iterator for Iter {
type Item = Holiday;
fn next(&mut self) -> Option<Self::Item> {
let next = self.buf.pop_front()?;
if next.date < self.since {
return self.next();
}
if next.date < self.until {
Some(next)
} else {
None
}
}
}
pub fn iter(country: Country, since: NaiveDate, until: NaiveDate) -> Result<Iter> {
let Some(data) = Lazy::get(&DATA) else { return Err(Error::Uninitialized); };
let mut buf = VecDeque::new();
let mut y = since.year();
while y <= until.year() {
let data = data.read().map_err(|e| Error::LockError(e.to_string()))?;
let Some(map) = data.get(&country) else {
return Err(Error::CountryNotAvailable);
};
let Some(map) = map.get(&y) else {
break;
};
buf.extend(map.values().cloned());
y += 1;
}
Ok(Iter { since, until, buf })
}
#[derive(Default)]
pub struct Builder {
countries: Option<HashSet<Country>>,
years: Option<std::ops::Range<Year>>,
}
impl Builder {
pub fn new() -> Self {
Self::default()
}
pub fn countries(mut self, countries: &[Country]) -> Self {
self.countries = Some(countries.iter().copied().collect());
self
}
pub fn years(mut self, years: Range<Year>) -> Self {
self.years = Some(years);
self
}
pub fn build(self) -> Result<HolidayMap> {
let Builder { countries, years } = self;
build(countries.as_ref(), years.as_ref())
}
pub fn init(self) -> Result<()> {
let Builder { countries, years } = self;
let map = build(countries.as_ref(), years.as_ref())?;
init_holiday(map)
}
}
#[derive(Debug, Clone)]
pub struct Holiday {
pub code: Country,
pub country: String,
pub date: chrono::NaiveDate,
pub name: String,
}
impl Holiday {
fn new(
code: Country,
country: impl Into<String>,
date: NaiveDate,
name: impl Into<String>,
) -> Holiday {
Holiday {
code,
country: country.into(),
date,
name: name.into(),
}
}
}
#[derive(thiserror::Error, Debug, PartialEq, Eq)]
pub enum Error {
#[error("Holiday is not available for this country")]
CountryNotAvailable,
#[error("Holiday is not available for this year")]
YearNotAvailable,
#[error("Holiday database is not initialized yet")]
Uninitialized,
#[error("Failed to get RwLock: {0}")]
LockError(String),
#[error("Unexpexted error occurred: {0}")]
Unexpected(String),
}
fn init_holiday(map: HolidayMap) -> Result<()> {
match DATA.write() {
Ok(mut data) => {
*data = map;
Ok(())
}
Err(e) => Err(Error::LockError(e.to_string())),
}
}
#[doc(hidden)]
pub trait NaiveDateExt {
fn from_ymd_res(year: i32, month: u32, day: u32) -> Result<NaiveDate> {
NaiveDate::from_ymd_opt(year, month, day)
.ok_or_else(|| Error::Unexpected(format!("invalid date {year}-{month}-{day}")))
}
}
impl NaiveDateExt for NaiveDate {}
include!("data.rs");