-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add public holidays calendars for Ukraine, UK, and US
Added implementation of public holidays for Ukraine, the UK, and the US in the third-party calendar module. Including various markets and special closures days. Calendar functionality was expanded to indicate business days specific to these countries. This will aid in accurate business day calculations for these markets. Test cases were also added to validate the accuracy of the holidays and business days implemented.
- Loading branch information
1 parent
d4617f0
commit e5950e6
Showing
2 changed files
with
259 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
[package] | ||
name = "calendar" | ||
version.workspace = true | ||
authors.workspace = true | ||
edition.workspace = true | ||
repository.workspace = true | ||
keywords.workspace = true | ||
categories.workspace = true | ||
readme = "../../README.md" | ||
description = "Time related code for the qlab" | ||
license-file = "LICENSE" | ||
|
||
[dependencies] | ||
chrono = "0.4.31" | ||
num-traits = { workspace = true } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,244 @@ | ||
use chrono::{Datelike, Duration, NaiveDate, Weekday}; | ||
use std::fmt::Debug; | ||
|
||
const EASTER_MONDAY: [u32; 299] = [ | ||
98, 90, 103, 95, 114, 106, 91, 111, 102, // 1901-1909 | ||
87, 107, 99, 83, 103, 95, 115, 99, 91, 111, // 1910-1919 | ||
96, 87, 107, 92, 112, 103, 95, 108, 100, 91, // 1920-1929 | ||
111, 96, 88, 107, 92, 112, 104, 88, 108, 100, // 1930-1939 | ||
85, 104, 96, 116, 101, 92, 112, 97, 89, 108, // 1940-1949 | ||
100, 85, 105, 96, 109, 101, 93, 112, 97, 89, // 1950-1959 | ||
109, 93, 113, 105, 90, 109, 101, 86, 106, 97, // 1960-1969 | ||
89, 102, 94, 113, 105, 90, 110, 101, 86, 106, // 1970-1979 | ||
98, 110, 102, 94, 114, 98, 90, 110, 95, 86, // 1980-1989 | ||
106, 91, 111, 102, 94, 107, 99, 90, 103, 95, // 1990-1999 | ||
115, 106, 91, 111, 103, 87, 107, 99, 84, 103, // 2000-2009 | ||
95, 115, 100, 91, 111, 96, 88, 107, 92, 112, // 2010-2019 | ||
104, 95, 108, 100, 92, 111, 96, 88, 108, 92, // 2020-2029 | ||
112, 104, 89, 108, 100, 85, 105, 96, 116, 101, // 2030-2039 | ||
93, 112, 97, 89, 109, 100, 85, 105, 97, 109, // 2040-2049 | ||
101, 93, 113, 97, 89, 109, 94, 113, 105, 90, // 2050-2059 | ||
110, 101, 86, 106, 98, 89, 102, 94, 114, 105, // 2060-2069 | ||
90, 110, 102, 86, 106, 98, 111, 102, 94, 114, // 2070-2079 | ||
99, 90, 110, 95, 87, 106, 91, 111, 103, 94, // 2080-2089 | ||
107, 99, 91, 103, 95, 115, 107, 91, 111, 103, // 2090-2099 | ||
88, 108, 100, 85, 105, 96, 109, 101, 93, 112, // 2100-2109 | ||
97, 89, 109, 93, 113, 105, 90, 109, 101, 86, // 2110-2119 | ||
106, 97, 89, 102, 94, 113, 105, 90, 110, 101, // 2120-2129 | ||
86, 106, 98, 110, 102, 94, 114, 98, 90, 110, // 2130-2139 | ||
95, 86, 106, 91, 111, 102, 94, 107, 99, 90, // 2140-2149 | ||
103, 95, 115, 106, 91, 111, 103, 87, 107, 99, // 2150-2159 | ||
84, 103, 95, 115, 100, 91, 111, 96, 88, 107, // 2160-2169 | ||
92, 112, 104, 95, 108, 100, 92, 111, 96, 88, // 2170-2179 | ||
108, 92, 112, 104, 89, 108, 100, 85, 105, 96, // 2180-2189 | ||
116, 101, 93, 112, 97, 89, 109, 100, 85, 105, // 2190-2199 | ||
]; | ||
|
||
const ORTHODOX_EASTER_MONDAY: [u32; 299] = [ | ||
105, 118, 110, 102, 121, 106, 126, 118, 102, // 1901-1909 | ||
122, 114, 99, 118, 110, 95, 115, 106, 126, 111, // 1910-1919 | ||
103, 122, 107, 99, 119, 110, 123, 115, 107, 126, // 1920-1929 | ||
111, 103, 123, 107, 99, 119, 104, 123, 115, 100, // 1930-1939 | ||
120, 111, 96, 116, 108, 127, 112, 104, 124, 115, // 1940-1949 | ||
100, 120, 112, 96, 116, 108, 128, 112, 104, 124, // 1950-1959 | ||
109, 100, 120, 105, 125, 116, 101, 121, 113, 104, // 1960-1969 | ||
117, 109, 101, 120, 105, 125, 117, 101, 121, 113, // 1970-1979 | ||
98, 117, 109, 129, 114, 105, 125, 110, 102, 121, // 1980-1989 | ||
106, 98, 118, 109, 122, 114, 106, 118, 110, 102, // 1990-1999 | ||
122, 106, 126, 118, 103, 122, 114, 99, 119, 110, // 2000-2009 | ||
95, 115, 107, 126, 111, 103, 123, 107, 99, 119, // 2010-2019 | ||
111, 123, 115, 107, 127, 111, 103, 123, 108, 99, // 2020-2029 | ||
119, 104, 124, 115, 100, 120, 112, 96, 116, 108, // 2030-2039 | ||
128, 112, 104, 124, 116, 100, 120, 112, 97, 116, // 2040-2049 | ||
108, 128, 113, 104, 124, 109, 101, 120, 105, 125, // 2050-2059 | ||
117, 101, 121, 113, 105, 117, 109, 101, 121, 105, // 2060-2069 | ||
125, 110, 102, 121, 113, 98, 118, 109, 129, 114, // 2070-2079 | ||
106, 125, 110, 102, 122, 106, 98, 118, 110, 122, // 2080-2089 | ||
114, 99, 119, 110, 102, 115, 107, 126, 118, 103, // 2090-2099 | ||
123, 115, 100, 120, 112, 96, 116, 108, 128, 112, // 2100-2109 | ||
104, 124, 109, 100, 120, 105, 125, 116, 108, 121, // 2110-2119 | ||
113, 104, 124, 109, 101, 120, 105, 125, 117, 101, // 2120-2129 | ||
121, 113, 98, 117, 109, 129, 114, 105, 125, 110, // 2130-2139 | ||
102, 121, 113, 98, 118, 109, 129, 114, 106, 125, // 2140-2149 | ||
110, 102, 122, 106, 126, 118, 103, 122, 114, 99, // 2150-2159 | ||
119, 110, 102, 115, 107, 126, 111, 103, 123, 114, // 2160-2169 | ||
99, 119, 111, 130, 115, 107, 127, 111, 103, 123, // 2170-2179 | ||
108, 99, 119, 104, 124, 115, 100, 120, 112, 103, // 2180-2189 | ||
116, 108, 128, 119, 104, 124, 116, 100, 120, 112, // 2190-2199 | ||
]; | ||
|
||
pub mod argentina; | ||
|
||
pub mod austria; | ||
|
||
pub mod australia; | ||
|
||
pub mod botswana; | ||
|
||
pub mod brazil; | ||
|
||
pub mod canada; | ||
|
||
pub mod chile; | ||
|
||
pub mod china; | ||
|
||
pub mod czechrepublic; | ||
|
||
pub mod denmark; | ||
|
||
pub mod finland; | ||
|
||
pub mod france; | ||
|
||
pub mod germany; | ||
|
||
pub mod hongkong; | ||
|
||
pub mod hungary; | ||
|
||
pub mod iceland; | ||
|
||
pub mod india; | ||
|
||
pub mod indonesia; | ||
|
||
pub mod israel; | ||
|
||
pub mod italy; | ||
|
||
pub mod japan; | ||
|
||
pub mod jointcalendar; | ||
|
||
pub mod mexico; | ||
|
||
pub mod newzealand; | ||
|
||
pub mod norway; | ||
|
||
pub mod poland; | ||
|
||
pub mod romania; | ||
|
||
pub mod russia; | ||
|
||
pub mod singapore; | ||
|
||
pub mod slovakia; | ||
|
||
pub mod southafrica; | ||
|
||
pub mod southkorea; | ||
|
||
pub mod sweden; | ||
|
||
pub mod switzerland; | ||
|
||
pub mod taiwan; | ||
|
||
pub mod target; | ||
|
||
pub mod thailand; | ||
|
||
pub mod turkey; | ||
|
||
pub mod ukraine; | ||
|
||
pub mod unitedkingdom; | ||
|
||
pub mod unitedstates; | ||
|
||
pub mod weekendsonly; | ||
|
||
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 -= Duration::days(1); | ||
} | ||
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) | ||
} | ||
|
||
#[inline] | ||
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::days(i64::from(include_last))) | ||
- (from + Duration::days(i64::from(include_first)))) | ||
.num_days(); | ||
for date in (from + Duration::days(i64::from(include_first))) | ||
.iter_days() | ||
.take(usize::try_from(day_diff).unwrap()) | ||
{ | ||
if self.is_business_day(date) { | ||
day_count += 1; | ||
} | ||
} | ||
day_count | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use super::Calendar; | ||
use crate::unitedkingdom::UnitedKingdom; | ||
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); | ||
} | ||
} |