use crate::{
Calendar, Date, EasterOffset, FixedDate, LastWeekday, Month, NthWeekday, OneOff, Ordinal, Rule,
Weekday, Weekend, WeekendShift, Year, YearRange,
};
pub const SETTLEMENT: Calendar<'static> = Calendar {
name: "UK settlement",
weekend: Weekend::SAT_SUN,
rules: &[
Rule::Fixed(FixedDate::new(Month::Jan, 1).shift(WeekendShift::Forward)),
Rule::Easter(EasterOffset::good_friday()),
Rule::Easter(EasterOffset::easter_monday()),
Rule::NthWeekday(
NthWeekday::new(Ordinal::First, Weekday::Mon, Month::May)
.years(YearRange::literal_through(1994)),
),
Rule::NthWeekday(
NthWeekday::new(Ordinal::First, Weekday::Mon, Month::May)
.years(YearRange::literal_between(1996, 2019)),
),
Rule::NthWeekday(
NthWeekday::new(Ordinal::First, Weekday::Mon, Month::May)
.from_year(Year::literal(2021)),
),
Rule::OneOff(OneOff::new(Date::literal(1995, Month::May, 8))),
Rule::OneOff(OneOff::new(Date::literal(2020, Month::May, 8))),
Rule::LastWeekday(
LastWeekday::new(Weekday::Mon, Month::May).years(YearRange::literal_through(2001)),
),
Rule::LastWeekday(
LastWeekday::new(Weekday::Mon, Month::May)
.years(YearRange::literal_between(2003, 2011)),
),
Rule::LastWeekday(
LastWeekday::new(Weekday::Mon, Month::May)
.years(YearRange::literal_between(2013, 2021)),
),
Rule::LastWeekday(
LastWeekday::new(Weekday::Mon, Month::May).from_year(Year::literal(2023)),
),
Rule::OneOff(OneOff::new(Date::literal(2002, Month::Jun, 3))),
Rule::OneOff(OneOff::new(Date::literal(2002, Month::Jun, 4))),
Rule::OneOff(OneOff::new(Date::literal(2012, Month::Jun, 4))),
Rule::OneOff(OneOff::new(Date::literal(2012, Month::Jun, 5))),
Rule::OneOff(OneOff::new(Date::literal(2022, Month::Jun, 2))),
Rule::OneOff(OneOff::new(Date::literal(2022, Month::Jun, 3))),
Rule::LastWeekday(LastWeekday::new(Weekday::Mon, Month::Aug)),
Rule::Fixed(FixedDate::new(Month::Dec, 25).shift(WeekendShift::Forward)),
Rule::Fixed(FixedDate::new(Month::Dec, 26).shift(WeekendShift::Forward)),
Rule::OneOff(OneOff::new(Date::literal(2022, Month::Sep, 19))),
Rule::OneOff(OneOff::new(Date::literal(2023, Month::May, 8))),
Rule::OneOff(OneOff::new(Date::literal(1999, Month::Dec, 31))),
],
};
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used)]
mod tests {
use super::*;
use crate::DateRange;
use alloc::vec::Vec;
fn ymd(y: u16, m: Month, d: u8) -> Date {
Date::from_ymd(y, m, d).unwrap()
}
fn year(year: u16) -> core::ops::Range<Date> {
ymd(year, Month::Jan, 1)..ymd(year, Month::Dec, 31).add_days(1).unwrap()
}
fn observed(y: u16) -> Vec<Date> {
SETTLEMENT
.holidays(year(y))
.filter(|d| !SETTLEMENT.is_weekend(*d))
.collect()
}
fn assert_year(year: u16, expected: &[(Month, u8)]) {
let expected: Vec<Date> = expected.iter().map(|(m, d)| ymd(year, *m, *d)).collect();
assert_eq!(observed(year), expected, "{year}");
}
#[test]
fn published_bank_holidays_2024() {
assert_year(
2024,
&[
(Month::Jan, 1), (Month::Mar, 29), (Month::Apr, 1), (Month::May, 6), (Month::May, 27), (Month::Aug, 26), (Month::Dec, 25),
(Month::Dec, 26),
],
);
}
#[test]
fn published_bank_holidays_2023_include_the_coronation() {
assert_year(
2023,
&[
(Month::Jan, 2), (Month::Apr, 7), (Month::Apr, 10), (Month::May, 1), (Month::May, 8), (Month::May, 29), (Month::Aug, 28), (Month::Dec, 25),
(Month::Dec, 26),
],
);
}
#[test]
fn published_bank_holidays_2022_include_jubilee_and_funeral() {
assert_year(
2022,
&[
(Month::Jan, 3), (Month::Apr, 15), (Month::Apr, 18), (Month::May, 2), (Month::Jun, 2), (Month::Jun, 3), (Month::Aug, 29), (Month::Sep, 19), (Month::Dec, 26), (Month::Dec, 27), ],
);
assert!(!SETTLEMENT.is_holiday(ymd(2022, Month::May, 30)));
}
#[test]
fn published_bank_holidays_2021_split_christmas_across_monday_and_tuesday() {
assert_year(
2021,
&[
(Month::Jan, 1),
(Month::Apr, 2),
(Month::Apr, 5),
(Month::May, 3),
(Month::May, 31),
(Month::Aug, 30),
(Month::Dec, 27), (Month::Dec, 28), ],
);
}
#[test]
fn published_bank_holidays_2020_move_early_may_to_ve_day() {
assert_year(
2020,
&[
(Month::Jan, 1),
(Month::Apr, 10),
(Month::Apr, 13),
(Month::May, 8), (Month::May, 25), (Month::Aug, 31), (Month::Dec, 25),
(Month::Dec, 28), ],
);
assert!(!SETTLEMENT.is_holiday(ymd(2020, Month::May, 4)));
}
#[test]
fn published_bank_holidays_2012_include_the_diamond_jubilee() {
assert_year(
2012,
&[
(Month::Jan, 2), (Month::Apr, 6),
(Month::Apr, 9),
(Month::May, 7),
(Month::Jun, 4), (Month::Jun, 5), (Month::Aug, 27),
(Month::Dec, 25),
(Month::Dec, 26),
],
);
assert!(!SETTLEMENT.is_holiday(ymd(2012, Month::May, 28)));
}
#[test]
fn published_bank_holidays_2002_include_the_golden_jubilee() {
assert_year(
2002,
&[
(Month::Jan, 1),
(Month::Mar, 29),
(Month::Apr, 1),
(Month::May, 6),
(Month::Jun, 3), (Month::Jun, 4), (Month::Aug, 26),
(Month::Dec, 25),
(Month::Dec, 26),
],
);
assert!(!SETTLEMENT.is_holiday(ymd(2002, Month::May, 27)));
}
#[test]
fn published_bank_holidays_1999_include_millennium_day() {
assert_year(
1999,
&[
(Month::Jan, 1),
(Month::Apr, 2),
(Month::Apr, 5),
(Month::May, 3),
(Month::May, 31),
(Month::Aug, 30),
(Month::Dec, 27), (Month::Dec, 28), (Month::Dec, 31), ],
);
}
#[test]
fn published_bank_holidays_1995_move_early_may_to_ve_day() {
assert_year(
1995,
&[
(Month::Jan, 2), (Month::Apr, 14),
(Month::Apr, 17),
(Month::May, 8), (Month::May, 29), (Month::Aug, 28), (Month::Dec, 25),
(Month::Dec, 26),
],
);
assert!(!SETTLEMENT.is_holiday(ymd(1995, Month::May, 1)));
}
#[test]
fn no_substitute_is_ever_lost_to_a_collision() {
for y in 1996..=2060 {
let natural: Vec<Date> = year(y)
.dates()
.filter(|d| SETTLEMENT.rules.iter().any(|r| r.is_holiday(*d)))
.collect();
assert_eq!(observed(y).len(), natural.len(), "{y}: {natural:?}");
}
}
#[test]
fn eight_or_more_bank_holidays_every_modern_year() {
for year in 1996..=2030 {
assert!(observed(year).len() >= 8, "{year}: {:?}", observed(year));
}
}
}