use chrono::{Datelike, Duration, NaiveDate, Weekday};
use std::cmp::Ordering;
use std::fmt::Debug;
use crate::error::Result;
use crate::time::businessdayconvention::BusinessDayConvention;
static EASTER_MONDAY: [u32; 299] = [
98, 90, 103, 95, 114, 106, 91, 111, 102, 87, 107, 99, 83, 103, 95, 115, 99, 91, 111, 96, 87, 107, 92, 112, 103, 95, 108, 100, 91, 111, 96, 88, 107, 92, 112, 104, 88, 108, 100, 85, 104, 96, 116, 101, 92, 112, 97, 89, 108, 100, 85, 105, 96, 109, 101, 93, 112, 97, 89, 109, 93, 113, 105, 90, 109, 101, 86, 106, 97, 89, 102, 94, 113, 105, 90, 110, 101, 86, 106, 98, 110, 102, 94, 114, 98, 90, 110, 95, 86, 106, 91, 111, 102, 94, 107, 99, 90, 103, 95, 115, 106, 91, 111, 103, 87, 107, 99, 84, 103, 95, 115, 100, 91, 111, 96, 88, 107, 92, 112, 104, 95, 108, 100, 92, 111, 96, 88, 108, 92, 112, 104, 89, 108, 100, 85, 105, 96, 116, 101, 93, 112, 97, 89, 109, 100, 85, 105, 97, 109, 101, 93, 113, 97, 89, 109, 94, 113, 105, 90, 110, 101, 86, 106, 98, 89, 102, 94, 114, 105, 90, 110, 102, 86, 106, 98, 111, 102, 94, 114, 99, 90, 110, 95, 87, 106, 91, 111, 103, 94, 107, 99, 91, 103, 95, 115, 107, 91, 111, 103, 88, 108, 100, 85, 105, 96, 109, 101, 93, 112, 97, 89, 109, 93, 113, 105, 90, 109, 101, 86, 106, 97, 89, 102, 94, 113, 105, 90, 110, 101, 86, 106, 98, 110, 102, 94, 114, 98, 90, 110, 95, 86, 106, 91, 111, 102, 94, 107, 99, 90, 103, 95, 115, 106, 91, 111, 103, 87, 107, 99, 84, 103, 95, 115, 100, 91, 111, 96, 88, 107, 92, 112, 104, 95, 108, 100, 92, 111, 96, 88, 108, 92, 112, 104, 89, 108, 100, 85, 105, 96, 116, 101, 93, 112, 97, 89, 109, 100, 85, 105, ];
static ORTHODOX_EASTER_MONDAY: [u32; 299] = [
105, 118, 110, 102, 121, 106, 126, 118, 102, 122, 114, 99, 118, 110, 95, 115, 106, 126, 111, 103, 122, 107, 99, 119, 110, 123, 115, 107, 126, 111, 103, 123, 107, 99, 119, 104, 123, 115, 100, 120, 111, 96, 116, 108, 127, 112, 104, 124, 115, 100, 120, 112, 96, 116, 108, 128, 112, 104, 124, 109, 100, 120, 105, 125, 116, 101, 121, 113, 104, 117, 109, 101, 120, 105, 125, 117, 101, 121, 113, 98, 117, 109, 129, 114, 105, 125, 110, 102, 121, 106, 98, 118, 109, 122, 114, 106, 118, 110, 102, 122, 106, 126, 118, 103, 122, 114, 99, 119, 110, 95, 115, 107, 126, 111, 103, 123, 107, 99, 119, 111, 123, 115, 107, 127, 111, 103, 123, 108, 99, 119, 104, 124, 115, 100, 120, 112, 96, 116, 108, 128, 112, 104, 124, 116, 100, 120, 112, 97, 116, 108, 128, 113, 104, 124, 109, 101, 120, 105, 125, 117, 101, 121, 113, 105, 117, 109, 101, 121, 105, 125, 110, 102, 121, 113, 98, 118, 109, 129, 114, 106, 125, 110, 102, 122, 106, 98, 118, 110, 122, 114, 99, 119, 110, 102, 115, 107, 126, 118, 103, 123, 115, 100, 120, 112, 96, 116, 108, 128, 112, 104, 124, 109, 100, 120, 105, 125, 116, 108, 121, 113, 104, 124, 109, 101, 120, 105, 125, 117, 101, 121, 113, 98, 117, 109, 129, 114, 105, 125, 110, 102, 121, 113, 98, 118, 109, 129, 114, 106, 125, 110, 102, 122, 106, 126, 118, 103, 122, 114, 99, 119, 110, 102, 115, 107, 126, 111, 103, 123, 114, 99, 119, 111, 130, 115, 107, 127, 111, 103, 123, 108, 99, 119, 104, 124, 115, 100, 120, 112, 103, 116, 108, 128, 119, 104, 124, 116, 100, 120, 112, ];
pub mod argentina;
pub use argentina::Argentina;
pub mod austria;
pub use austria::Austria;
pub mod australia;
pub use australia::Australia;
pub mod botswana;
pub use botswana::Botswana;
pub mod brazil;
pub use brazil::Brazil;
pub mod canada;
pub use canada::Canada;
pub mod chile;
pub use chile::Chile;
pub mod china;
pub use china::China;
pub mod czechrepublic;
pub use czechrepublic::CzechRepublic;
pub mod denmark;
pub use denmark::Denmark;
pub mod finland;
pub use finland::Finland;
pub mod france;
pub use france::France;
pub mod germany;
pub use germany::Germany;
pub mod hongkong;
pub use hongkong::HongKong;
pub mod hungary;
pub use hungary::Hungary;
pub mod iceland;
pub use iceland::Iceland;
pub mod india;
pub use india::India;
pub mod indonesia;
pub use indonesia::Indonesia;
pub mod israel;
pub use israel::Israel;
pub mod italy;
pub use italy::Italy;
pub mod japan;
pub use japan::Japan;
pub mod jointcalendar;
pub use jointcalendar::JointCalendar;
pub mod mexico;
pub use mexico::Mexico;
pub mod newzealand;
pub use newzealand::NewZealand;
pub mod norway;
pub use norway::Norway;
pub mod poland;
pub use poland::Poland;
pub mod romania;
pub use romania::Romania;
pub mod russia;
pub use russia::Russia;
pub mod singapore;
pub use singapore::Singapore;
pub mod slovakia;
pub use slovakia::Slovakia;
pub mod southafrica;
pub use southafrica::SouthAfrica;
pub mod southkorea;
pub use southkorea::SouthKorea;
pub mod sweden;
pub use sweden::Sweden;
pub mod switzerland;
pub use switzerland::Switzerland;
pub mod taiwan;
pub use taiwan::Taiwan;
pub mod target;
pub use target::Target;
pub mod thailand;
pub use thailand::Thailand;
pub mod turkey;
pub use turkey::Turkey;
pub mod ukraine;
pub use ukraine::Ukraine;
pub mod unitedkingdom;
pub use unitedkingdom::UnitedKingdom;
pub mod unitedstates;
pub use unitedstates::UnitedStates;
pub mod weekendsonly;
use crate::time::period::{ONE_DAY, Period};
pub use weekendsonly::WeekendsOnly;
#[typetag::serde(tag = "type")]
pub trait Calendar: Debug {
fn naive_date_to_dkmy(&self, date: NaiveDate) -> (u32, Weekday, u32, i32, u32) {
(
date.day(),
date.weekday(),
date.month(),
date.year(),
date.ordinal(),
)
}
fn last_day_of_month(&self, date: NaiveDate) -> NaiveDate {
let year = date.year();
let month = date.month();
NaiveDate::from_ymd_opt(year, month + 1, 1)
.unwrap_or(NaiveDate::from_ymd_opt(year + 1, 1, 1).unwrap())
.pred_opt()
.unwrap()
}
fn end_of_month(&self, date: NaiveDate) -> NaiveDate {
let mut last_day_of_month = self.last_day_of_month(date);
while self.is_holiday(last_day_of_month) {
last_day_of_month -= ONE_DAY;
}
last_day_of_month
}
fn easter_monday(&self, year: i32) -> u32 {
EASTER_MONDAY[year as usize - 1901usize]
}
fn orthodox_easter_monday(&self, year: i32) -> u32 {
ORTHODOX_EASTER_MONDAY[year as usize - 1901usize]
}
fn is_weekend(&self, date: NaiveDate) -> bool {
let weekday = date.weekday();
matches!(weekday, Weekday::Sat | Weekday::Sun)
}
fn is_business_day(&self, date: NaiveDate) -> bool;
fn is_holiday(&self, date: NaiveDate) -> bool {
!self.is_business_day(date)
}
fn adjust(&self, date: NaiveDate, bdc: BusinessDayConvention) -> Option<NaiveDate> {
if bdc == BusinessDayConvention::Unadjusted {
return Some(date);
}
let mut d1 = date;
if bdc == BusinessDayConvention::Following
|| bdc == BusinessDayConvention::ModifiedFollowing
|| bdc == BusinessDayConvention::HalfMonthModifiedFollowing
{
while self.is_holiday(d1) {
d1 += ONE_DAY;
}
if (bdc == BusinessDayConvention::ModifiedFollowing
|| bdc == BusinessDayConvention::HalfMonthModifiedFollowing)
&& d1.month() != date.month()
{
return self.adjust(date, BusinessDayConvention::Preceding);
}
if bdc == BusinessDayConvention::HalfMonthModifiedFollowing
&& date.day() <= 15
&& d1.day() > 15
{
return self.adjust(date, BusinessDayConvention::Preceding);
}
} else if bdc == BusinessDayConvention::Preceding
|| bdc == BusinessDayConvention::ModifiedPreceding
{
while self.is_holiday(d1) {
d1 -= ONE_DAY;
}
if bdc == BusinessDayConvention::ModifiedPreceding && d1.month() != date.month() {
return self.adjust(date, BusinessDayConvention::Following);
}
} else if bdc == BusinessDayConvention::Nearest {
let mut d2 = date;
while self.is_holiday(d1) && self.is_holiday(d2) {
d1 += ONE_DAY;
d2 -= ONE_DAY;
}
return if self.is_holiday(d1) {
Some(d1)
} else {
Some(d2)
};
} else {
return None;
}
Some(d1)
}
fn advance(
&self,
date: NaiveDate,
period: Period,
bdc: BusinessDayConvention,
end_of_month: Option<bool>,
) -> Result<Option<NaiveDate>> {
let end_of_month = end_of_month.unwrap_or(false);
match period {
Period::Months(_) | Period::Years(_) => {
let advance_date = (date + period)?;
Ok(if end_of_month {
Some(self.end_of_month(self.adjust(advance_date, bdc).unwrap()))
} else {
self.adjust(advance_date, bdc)
})
}
Period::Days(mut num) => {
let mut advance_date = date;
let target: i64 = 0;
match num.cmp(&target) {
Ordering::Equal => Ok(self.adjust(date, bdc)),
Ordering::Greater => {
while num > 0 {
advance_date = (advance_date + Period::Days(1))?;
while !self.is_business_day(advance_date) {
advance_date = (advance_date + Period::Days(1))?;
}
num -= 1;
}
Ok(Some(advance_date))
}
Ordering::Less => {
while num < 0 {
advance_date = (advance_date - Period::Days(1))?;
while !self.is_business_day(advance_date) {
advance_date = (advance_date - Period::Days(1))?;
}
num += 1;
}
Ok(Some(advance_date))
}
}
}
_ => {
let advance_date = (date + period)?;
Ok(self.adjust(advance_date, bdc))
}
}
}
fn business_days_between(
&self,
from: NaiveDate,
to: NaiveDate,
include_first: Option<bool>,
include_last: Option<bool>,
) -> i64 {
if from > to {
return -self.business_days_between(to, from, include_first, include_last);
}
let include_first = include_first.unwrap_or(true);
let include_last = include_last.unwrap_or(false);
let mut day_count = 0;
let day_diff = ((to + Duration::try_days(if include_last { 1 } else { 0 }).unwrap())
- (from + Duration::try_days(if include_first { 1 } else { 0 }).unwrap()))
.num_days();
for date in (from + Duration::try_days(if include_first { 1 } else { 0 }).unwrap())
.iter_days()
.take(day_diff as usize)
{
if self.is_business_day(date) {
day_count += 1;
}
}
day_count
}
}
#[cfg(test)]
mod tests {
use super::UnitedKingdom;
use crate::time::calendars::Calendar;
use chrono::NaiveDate;
#[test]
fn test_business_days_between() {
let calendar = UnitedKingdom::default();
let from = NaiveDate::from_ymd_opt(2023, 4, 1).unwrap();
let to = NaiveDate::from_ymd_opt(2023, 4, 30).unwrap();
assert_eq!(calendar.business_days_between(from, to, None, None,), 18);
assert_eq!(calendar.business_days_between(to, from, None, None,), -18);
}
}