use crate::astronomy;
use crate::date::{Date, days_in_month};
use crate::model::Holiday;
use std::any::TypeId;
use std::sync::Arc;
const NATIONAL_HOLIDAY_ACT_ENFORCEMENT: (i32, u32, u32) = (1948, 7, 20);
pub trait OriginalHolidayChecker: Send + Sync {
fn is_holiday(&self, date: Date) -> bool;
fn holiday_name(&self, date: Date) -> String;
}
#[derive(Clone)]
pub(crate) struct OriginalEntry {
pub(crate) type_id: TypeId,
pub(crate) checker: Arc<dyn OriginalHolidayChecker>,
}
pub(crate) fn nth_weekday_day(year: i32, month: u32, nth: u32, iso_weekday: u32) -> Option<u32> {
if !(1..=5).contains(&nth) || !(1..=7).contains(&iso_weekday) {
return None;
}
let dim = days_in_month(year, month)?;
let mut count = 0;
for day in 1..=dim {
if let Ok(date) = Date::new(year, month, day)
&& date.iso_weekday() == iso_weekday
{
count += 1;
if count == nth {
return Some(day);
}
}
}
None
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Builtin {
NewYear,
AdultDay,
FoundationDay,
EmperorsBirthday,
VernalEquinox,
GreeneryDay,
ShowaDay,
ConstitutionMemorialDay,
ChildrensDay,
SeaDay,
MountainDay,
RespectForTheAgedDay,
AutumnEquinox,
HealthAndSportsDay,
SportsDay,
CultureDay,
LaborThanksgivingDay,
ExtraHoliday1959,
ExtraHoliday1989,
ExtraHoliday1990,
ExtraHoliday1993,
ExtraHoliday2019May,
ExtraHoliday2019Oct,
}
impl Builtin {
pub(crate) const ALL: [Builtin; 23] = [
Builtin::NewYear,
Builtin::AdultDay,
Builtin::FoundationDay,
Builtin::EmperorsBirthday,
Builtin::VernalEquinox,
Builtin::GreeneryDay,
Builtin::ShowaDay,
Builtin::ConstitutionMemorialDay,
Builtin::ChildrensDay,
Builtin::SeaDay,
Builtin::MountainDay,
Builtin::RespectForTheAgedDay,
Builtin::AutumnEquinox,
Builtin::HealthAndSportsDay,
Builtin::SportsDay,
Builtin::CultureDay,
Builtin::LaborThanksgivingDay,
Builtin::ExtraHoliday1959,
Builtin::ExtraHoliday1989,
Builtin::ExtraHoliday1990,
Builtin::ExtraHoliday1993,
Builtin::ExtraHoliday2019May,
Builtin::ExtraHoliday2019Oct,
];
pub(crate) fn is_holiday(self, date: Date) -> bool {
let (y, m, d) = (date.year(), date.month(), date.day());
if (y, m, d) < NATIONAL_HOLIDAY_ACT_ENFORCEMENT {
return false;
}
match self {
Builtin::NewYear => m == 1 && d == 1,
Builtin::AdultDay => {
(y <= 1999 && m == 1 && d == 15)
|| (y >= 2000 && m == 1 && nth_weekday_day(y, 1, 2, 1) == Some(d))
}
Builtin::FoundationDay => y >= 1967 && m == 2 && d == 11,
Builtin::EmperorsBirthday => {
((1948..=1988).contains(&y) && m == 4 && d == 29)
|| ((1989..=2018).contains(&y) && m == 12 && d == 23)
|| (y >= 2020 && m == 2 && d == 23)
}
Builtin::VernalEquinox => m == 3 && d == astronomy::calculate_vernal_equinox(y),
Builtin::GreeneryDay => {
((1989..=2006).contains(&y) && m == 4 && d == 29) || (y >= 2007 && m == 5 && d == 4)
}
Builtin::ShowaDay => y >= 2007 && m == 4 && d == 29,
Builtin::ConstitutionMemorialDay => m == 5 && d == 3,
Builtin::ChildrensDay => m == 5 && d == 5,
Builtin::SeaDay => {
if y == 2020 {
return m == 7 && d == 23;
}
if y == 2021 {
return m == 7 && d == 22;
}
((1996..=2002).contains(&y) && m == 7 && d == 20)
|| (y >= 2003 && m == 7 && nth_weekday_day(y, 7, 3, 1) == Some(d))
}
Builtin::MountainDay => {
if y == 2020 {
return m == 8 && d == 10;
}
if y == 2021 {
return m == 8 && d == 8;
}
y >= 2016 && m == 8 && d == 11
}
Builtin::RespectForTheAgedDay => {
((1966..=2002).contains(&y) && m == 9 && d == 15)
|| (y >= 2003 && m == 9 && nth_weekday_day(y, 9, 3, 1) == Some(d))
}
Builtin::AutumnEquinox => m == 9 && d == astronomy::calculate_autumn_equinox(y),
Builtin::HealthAndSportsDay => {
((1966..=1999).contains(&y) && m == 10 && d == 10)
|| ((2000..=2019).contains(&y)
&& m == 10
&& nth_weekday_day(y, 10, 2, 1) == Some(d))
}
Builtin::SportsDay => {
if y == 2020 {
return m == 7 && d == 24;
}
if y == 2021 {
return m == 7 && d == 23;
}
y >= 2020 && m == 10 && nth_weekday_day(y, 10, 2, 1) == Some(d)
}
Builtin::CultureDay => m == 11 && d == 3,
Builtin::LaborThanksgivingDay => m == 11 && d == 23,
Builtin::ExtraHoliday1959 => y == 1959 && m == 4 && d == 10,
Builtin::ExtraHoliday1989 => y == 1989 && m == 2 && d == 24,
Builtin::ExtraHoliday1990 => y == 1990 && m == 11 && d == 12,
Builtin::ExtraHoliday1993 => y == 1993 && m == 6 && d == 9,
Builtin::ExtraHoliday2019May => y == 2019 && m == 5 && d == 1,
Builtin::ExtraHoliday2019Oct => y == 2019 && m == 10 && d == 22,
}
}
pub(crate) fn name(self) -> &'static str {
match self {
Builtin::NewYear => "元日",
Builtin::AdultDay => "成人の日",
Builtin::FoundationDay => "建国記念の日",
Builtin::EmperorsBirthday => "天皇誕生日",
Builtin::VernalEquinox => "春分の日",
Builtin::GreeneryDay => "みどりの日",
Builtin::ShowaDay => "昭和の日",
Builtin::ConstitutionMemorialDay => "憲法記念日",
Builtin::ChildrensDay => "こどもの日",
Builtin::SeaDay => "海の日",
Builtin::MountainDay => "山の日",
Builtin::RespectForTheAgedDay => "敬老の日",
Builtin::AutumnEquinox => "秋分の日",
Builtin::HealthAndSportsDay => "体育の日",
Builtin::SportsDay => "スポーツの日",
Builtin::CultureDay => "文化の日",
Builtin::LaborThanksgivingDay => "勤労感謝の日",
Builtin::ExtraHoliday1959 => "皇太子・明仁親王の結婚の儀",
Builtin::ExtraHoliday1989 => "昭和天皇の大喪の礼",
Builtin::ExtraHoliday1990 => "即位の礼正殿の儀",
Builtin::ExtraHoliday1993 => "皇太子・皇太子徳仁親王の結婚の儀",
Builtin::ExtraHoliday2019May => "天皇の即位の日",
Builtin::ExtraHoliday2019Oct => "即位礼正殿の儀",
}
}
}
#[derive(Clone)]
pub(crate) enum Checker {
Builtin(Builtin),
Transfer,
National,
Original(OriginalEntry),
}
pub(crate) fn checker_holiday(checker: &Checker, date: Date, all: &[Checker]) -> Option<String> {
match checker {
Checker::Builtin(b) => {
if b.is_holiday(date) {
Some(b.name().to_string())
} else {
None
}
}
Checker::Transfer => transfer_name(date, all),
Checker::National => {
if national_holiday(date, all) {
Some("国民の休日".to_string())
} else {
None
}
}
Checker::Original(e) => {
if e.checker.is_holiday(date) {
Some(e.checker.holiday_name(date))
} else {
None
}
}
}
}
pub(crate) fn compute_holidays(checkers: &[Checker], date: Date) -> Vec<Holiday> {
let mut result = Vec::new();
for checker in checkers {
if let Some(name) = checker_holiday(checker, date, checkers) {
result.push(Holiday::new(date, name));
}
}
result
}
fn first_builtin_on(date: Date, all: &[Checker]) -> Option<Builtin> {
all.iter().find_map(|c| match c {
Checker::Builtin(b) if b.is_holiday(date) => Some(*b),
_ => None,
})
}
pub(crate) fn transfer_name(date: Date, all: &[Checker]) -> Option<String> {
if date.year() < 1973 {
return None;
}
if date.iso_weekday() == 7 {
return None;
}
if first_builtin_on(date, all).is_some() {
return None;
}
let mut current = date.pred();
loop {
match first_builtin_on(current, all) {
None => return None,
Some(b) => {
if current.iso_weekday() == 7 {
return Some(format!("{} 振替休日", b.name()));
}
}
}
current = current.pred();
}
}
pub(crate) fn national_holiday(date: Date, all: &[Checker]) -> bool {
if date.iso_weekday() == 7 {
return false;
}
let is_hol = |d: Date| -> bool {
all.iter().any(|c| match c {
Checker::National | Checker::Original(_) => false,
Checker::Builtin(b) => b.is_holiday(d),
Checker::Transfer => transfer_name(d, all).is_some(),
})
};
if is_hol(date) {
return false;
}
is_hol(date.pred()) && is_hol(date.succ())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn nth_weekday_basic() {
assert_eq!(nth_weekday_day(2017, 1, 2, 1), Some(9));
assert_eq!(nth_weekday_day(2017, 7, 3, 1), Some(17));
assert_eq!(nth_weekday_day(2017, 1, 0, 1), None);
assert_eq!(nth_weekday_day(2017, 1, 2, 8), None);
}
}