1use chrono::{Datelike, Duration, NaiveDate, Weekday};
2use std::cmp::Ordering;
3use std::fmt::Debug;
4
5use crate::error::Result;
6use crate::time::businessdayconvention::BusinessDayConvention;
7
8static EASTER_MONDAY: [u32; 299] = [
9 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, ];
40
41static ORTHODOX_EASTER_MONDAY: [u32; 299] = [
42 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, ];
73
74pub mod argentina;
75pub use argentina::Argentina;
76pub mod austria;
77pub use austria::Austria;
78pub mod australia;
79pub use australia::Australia;
80pub mod botswana;
81pub use botswana::Botswana;
82pub mod brazil;
83pub use brazil::Brazil;
84pub mod canada;
85pub use canada::Canada;
86pub mod chile;
87pub use chile::Chile;
88pub mod china;
89pub use china::China;
90pub mod czechrepublic;
91pub use czechrepublic::CzechRepublic;
92pub mod denmark;
93pub use denmark::Denmark;
94pub mod finland;
95pub use finland::Finland;
96pub mod france;
97pub use france::France;
98pub mod germany;
99pub use germany::Germany;
100pub mod hongkong;
101pub use hongkong::HongKong;
102pub mod hungary;
103pub use hungary::Hungary;
104pub mod iceland;
105pub use iceland::Iceland;
106pub mod india;
107pub use india::India;
108pub mod indonesia;
109pub use indonesia::Indonesia;
110pub mod israel;
111pub use israel::Israel;
112pub mod italy;
113pub use italy::Italy;
114pub mod japan;
115pub use japan::Japan;
116pub mod jointcalendar;
117pub use jointcalendar::JointCalendar;
118pub mod mexico;
119pub use mexico::Mexico;
120pub mod newzealand;
121pub use newzealand::NewZealand;
122pub mod norway;
123pub use norway::Norway;
124pub mod poland;
125pub use poland::Poland;
126pub mod romania;
127pub use romania::Romania;
128pub mod russia;
129pub use russia::Russia;
130pub mod singapore;
131pub use singapore::Singapore;
132pub mod slovakia;
133pub use slovakia::Slovakia;
134pub mod southafrica;
135pub use southafrica::SouthAfrica;
136pub mod southkorea;
137pub use southkorea::SouthKorea;
138pub mod sweden;
139pub use sweden::Sweden;
140pub mod switzerland;
141pub use switzerland::Switzerland;
142pub mod taiwan;
143pub use taiwan::Taiwan;
144pub mod target;
145pub use target::Target;
146pub mod thailand;
147pub use thailand::Thailand;
148pub mod turkey;
149pub use turkey::Turkey;
150pub mod ukraine;
151pub use ukraine::Ukraine;
152pub mod unitedkingdom;
153pub use unitedkingdom::UnitedKingdom;
154pub mod unitedstates;
155pub use unitedstates::UnitedStates;
156pub mod weekendsonly;
157use crate::time::period::{ONE_DAY, Period};
158pub use weekendsonly::WeekendsOnly;
159
160#[typetag::serde(tag = "type")]
161pub trait Calendar: Debug {
162 fn naive_date_to_dkmy(&self, date: NaiveDate) -> (u32, Weekday, u32, i32, u32) {
163 (
164 date.day(),
165 date.weekday(),
166 date.month(),
167 date.year(),
168 date.ordinal(),
169 )
170 }
171
172 fn last_day_of_month(&self, date: NaiveDate) -> NaiveDate {
173 let year = date.year();
174 let month = date.month();
175 NaiveDate::from_ymd_opt(year, month + 1, 1)
176 .unwrap_or(NaiveDate::from_ymd_opt(year + 1, 1, 1).unwrap())
177 .pred_opt()
178 .unwrap()
179 }
180
181 fn end_of_month(&self, date: NaiveDate) -> NaiveDate {
182 let mut last_day_of_month = self.last_day_of_month(date);
183 while self.is_holiday(last_day_of_month) {
184 last_day_of_month -= ONE_DAY;
185 }
186 last_day_of_month
187 }
188
189 fn easter_monday(&self, year: i32) -> u32 {
190 EASTER_MONDAY[year as usize - 1901usize]
191 }
192
193 fn orthodox_easter_monday(&self, year: i32) -> u32 {
194 ORTHODOX_EASTER_MONDAY[year as usize - 1901usize]
195 }
196
197 fn is_weekend(&self, date: NaiveDate) -> bool {
198 let weekday = date.weekday();
199 matches!(weekday, Weekday::Sat | Weekday::Sun)
200 }
201
202 fn is_business_day(&self, date: NaiveDate) -> bool;
203
204 fn is_holiday(&self, date: NaiveDate) -> bool {
205 !self.is_business_day(date)
206 }
207
208 fn adjust(&self, date: NaiveDate, bdc: BusinessDayConvention) -> Option<NaiveDate> {
209 if bdc == BusinessDayConvention::Unadjusted {
210 return Some(date);
211 }
212
213 let mut d1 = date;
214
215 if bdc == BusinessDayConvention::Following
216 || bdc == BusinessDayConvention::ModifiedFollowing
217 || bdc == BusinessDayConvention::HalfMonthModifiedFollowing
218 {
219 while self.is_holiday(d1) {
220 d1 += ONE_DAY;
221 }
222 if (bdc == BusinessDayConvention::ModifiedFollowing
223 || bdc == BusinessDayConvention::HalfMonthModifiedFollowing)
224 && d1.month() != date.month()
225 {
226 return self.adjust(date, BusinessDayConvention::Preceding);
227 }
228 if bdc == BusinessDayConvention::HalfMonthModifiedFollowing
229 && date.day() <= 15
230 && d1.day() > 15
231 {
232 return self.adjust(date, BusinessDayConvention::Preceding);
233 }
234 } else if bdc == BusinessDayConvention::Preceding
235 || bdc == BusinessDayConvention::ModifiedPreceding
236 {
237 while self.is_holiday(d1) {
238 d1 -= ONE_DAY;
239 }
240 if bdc == BusinessDayConvention::ModifiedPreceding && d1.month() != date.month() {
241 return self.adjust(date, BusinessDayConvention::Following);
242 }
243 } else if bdc == BusinessDayConvention::Nearest {
244 let mut d2 = date;
245 while self.is_holiday(d1) && self.is_holiday(d2) {
246 d1 += ONE_DAY;
247 d2 -= ONE_DAY;
248 }
249 return if self.is_holiday(d1) {
250 Some(d1)
251 } else {
252 Some(d2)
253 };
254 } else {
255 return None;
256 }
257 Some(d1)
258 }
259
260 fn advance(
261 &self,
262 date: NaiveDate,
263 period: Period,
264 bdc: BusinessDayConvention,
265 end_of_month: Option<bool>,
266 ) -> Result<Option<NaiveDate>> {
267 let end_of_month = end_of_month.unwrap_or(false);
268
269 match period {
270 Period::Months(_) | Period::Years(_) => {
271 let advance_date = (date + period)?;
272 Ok(if end_of_month {
273 Some(self.end_of_month(self.adjust(advance_date, bdc).unwrap()))
274 } else {
275 self.adjust(advance_date, bdc)
276 })
277 }
278 Period::Days(mut num) => {
279 let mut advance_date = date;
280 let target: i64 = 0;
281 match num.cmp(&target) {
282 Ordering::Equal => Ok(self.adjust(date, bdc)),
283 Ordering::Greater => {
284 while num > 0 {
285 advance_date = (advance_date + Period::Days(1))?;
286 while !self.is_business_day(advance_date) {
287 advance_date = (advance_date + Period::Days(1))?;
288 }
289 num -= 1;
290 }
291 Ok(Some(advance_date))
292 }
293 Ordering::Less => {
294 while num < 0 {
295 advance_date = (advance_date - Period::Days(1))?;
296 while !self.is_business_day(advance_date) {
297 advance_date = (advance_date - Period::Days(1))?;
298 }
299 num += 1;
300 }
301 Ok(Some(advance_date))
302 }
303 }
304 }
305 _ => {
306 let advance_date = (date + period)?;
307 Ok(self.adjust(advance_date, bdc))
308 }
309 }
310 }
311
312 fn business_days_between(
313 &self,
314 from: NaiveDate,
315 to: NaiveDate,
316 include_first: Option<bool>,
317 include_last: Option<bool>,
318 ) -> i64 {
319 if from > to {
320 return -self.business_days_between(to, from, include_first, include_last);
321 }
322 let include_first = include_first.unwrap_or(true);
323 let include_last = include_last.unwrap_or(false);
324 let mut day_count = 0;
325 let day_diff = ((to + Duration::try_days(if include_last { 1 } else { 0 }).unwrap())
326 - (from + Duration::try_days(if include_first { 1 } else { 0 }).unwrap()))
327 .num_days();
328 for date in (from + Duration::try_days(if include_first { 1 } else { 0 }).unwrap())
329 .iter_days()
330 .take(day_diff as usize)
331 {
332 if self.is_business_day(date) {
333 day_count += 1;
334 }
335 }
336 day_count
337 }
338}
339
340#[cfg(test)]
341mod tests {
342 use super::UnitedKingdom;
343 use crate::time::calendars::Calendar;
344 use chrono::NaiveDate;
345
346 #[test]
347 fn test_business_days_between() {
348 let calendar = UnitedKingdom::default();
349 let from = NaiveDate::from_ymd_opt(2023, 4, 1).unwrap();
350 let to = NaiveDate::from_ymd_opt(2023, 4, 30).unwrap();
351 assert_eq!(calendar.business_days_between(from, to, None, None,), 18);
352 assert_eq!(calendar.business_days_between(to, from, None, None,), -18);
353 }
354}