use chrono::Datelike;
use chrono::Duration;
use chrono::NaiveDate;
use super::CalendarExt;
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum BusinessDayConvention {
Unadjusted,
Following,
#[default]
ModifiedFollowing,
Preceding,
ModifiedPreceding,
}
impl std::fmt::Display for BusinessDayConvention {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Unadjusted => write!(f, "Unadjusted"),
Self::Following => write!(f, "Following"),
Self::ModifiedFollowing => write!(f, "Modified Following"),
Self::Preceding => write!(f, "Preceding"),
Self::ModifiedPreceding => write!(f, "Modified Preceding"),
}
}
}
impl BusinessDayConvention {
pub fn adjust(&self, date: NaiveDate, calendar: &(impl CalendarExt + ?Sized)) -> NaiveDate {
match self {
Self::Unadjusted => date,
Self::Following => advance_to_business_day(date, 1, calendar),
Self::Preceding => advance_to_business_day(date, -1, calendar),
Self::ModifiedFollowing => {
let adjusted = advance_to_business_day(date, 1, calendar);
if adjusted.month() != date.month() {
advance_to_business_day(date, -1, calendar)
} else {
adjusted
}
}
Self::ModifiedPreceding => {
let adjusted = advance_to_business_day(date, -1, calendar);
if adjusted.month() != date.month() {
advance_to_business_day(date, 1, calendar)
} else {
adjusted
}
}
}
}
}
fn advance_to_business_day(
mut date: NaiveDate,
step: i64,
calendar: &(impl CalendarExt + ?Sized),
) -> NaiveDate {
let delta = Duration::days(step);
while !calendar.is_business_day(date) {
date += delta;
}
date
}
#[cfg(test)]
mod tests {
use chrono::NaiveDate;
use super::super::holiday::Calendar;
use super::super::holiday::HolidayCalendar;
use super::*;
#[test]
fn following_skips_weekend() {
let cal = Calendar::new(HolidayCalendar::UnitedStates);
let saturday = NaiveDate::from_ymd_opt(2024, 1, 6).unwrap();
let monday = NaiveDate::from_ymd_opt(2024, 1, 8).unwrap();
assert_eq!(
BusinessDayConvention::Following.adjust(saturday, &cal),
monday
);
}
#[test]
fn preceding_skips_weekend() {
let cal = Calendar::new(HolidayCalendar::UnitedStates);
let sunday = NaiveDate::from_ymd_opt(2024, 1, 7).unwrap();
let friday = NaiveDate::from_ymd_opt(2024, 1, 5).unwrap();
assert_eq!(
BusinessDayConvention::Preceding.adjust(sunday, &cal),
friday
);
}
#[test]
fn modified_following_stays_in_month() {
let cal = Calendar::new(HolidayCalendar::UnitedStates);
let saturday = NaiveDate::from_ymd_opt(2024, 3, 30).unwrap();
let friday = NaiveDate::from_ymd_opt(2024, 3, 29).unwrap();
assert_eq!(
BusinessDayConvention::ModifiedFollowing.adjust(saturday, &cal),
friday
);
}
#[test]
fn unadjusted_returns_same_date() {
let cal = Calendar::new(HolidayCalendar::UnitedStates);
let saturday = NaiveDate::from_ymd_opt(2024, 1, 6).unwrap();
assert_eq!(
BusinessDayConvention::Unadjusted.adjust(saturday, &cal),
saturday
);
}
}