finquant/time/calendars/
israel.rs

1// Holidays in Israel.
2
3use crate::time::calendars::Calendar;
4use std::collections::HashSet;
5
6use chrono::{Datelike, Duration, NaiveDate, Weekday};
7use once_cell::sync::Lazy;
8use serde::{Deserialize, Serialize};
9
10#[derive(Deserialize, Serialize, Debug)]
11pub enum IsraelMarket {
12    TelAviv,
13    Shir,
14}
15
16#[derive(Deserialize, Serialize, Default, Debug)]
17pub struct Israel {
18    pub market: Option<IsraelMarket>,
19}
20
21#[typetag::serde]
22impl Calendar for Israel {
23    fn is_business_day(&self, date: NaiveDate) -> bool {
24        match self.market {
25            Some(IsraelMarket::TelAviv) => self.tel_aviv_is_business_day(date),
26            Some(IsraelMarket::Shir) => self.shir_is_business_day(date),
27            None => self.shir_is_business_day(date),
28        }
29    }
30}
31
32impl Israel {
33    fn is_named_holiday(&self, set: &HashSet<(u32, u32, i32)>, date: NaiveDate) -> bool {
34        set.contains(&(date.day(), date.month(), date.year()))
35    }
36
37    fn tel_aviv_is_business_day(&self, date: NaiveDate) -> bool {
38        let y = date.year();
39        if self.is_weekend(date) {
40            return false;
41        }
42
43        if self.is_named_holiday(&PURIM, date)
44            || (y <= 2020 && self.is_named_holiday(&PASSOVER, date + Duration::days(1)))
45            || self.is_named_holiday(&PASSOVER, date)
46            || self.is_named_holiday(&PASSOVER, date - Duration::days(5))
47            || self.is_named_holiday(&PASSOVER, date - Duration::days(6))
48            || self.is_named_holiday(&INDEPENDENCE_DAY, date + Duration::days(1)) // Memorial Day
49            || self.is_named_holiday(&INDEPENDENCE_DAY, date)
50            || (y <= 2020 && self.is_named_holiday(&SHAVUOT, date + Duration::days(1)))
51            || self.is_named_holiday(&SHAVUOT, date)
52            || self.is_named_holiday(&FAST_DAY, date)
53            || (y <= 2019 && self.is_named_holiday(&NEW_YEAR, date + Duration::days(1)))
54            || self.is_named_holiday(&NEW_YEAR, date)
55            || self.is_named_holiday(&NEW_YEAR, date - Duration::days(1)) // 2nd day of new year
56            || self.is_named_holiday(&NEW_YEAR, date - Duration::days(8)) // Eve of Yom Kippur
57            || self.is_named_holiday(&NEW_YEAR, date - Duration::days(9)) // Yom Kippur = NEW_YEAR(d-9);
58            || self.is_named_holiday(&NEW_YEAR, date - Duration::days(13)) // Eve of Sukkot
59            || self.is_named_holiday(&NEW_YEAR, date - Duration::days(14)) // Sukkot =  NEW_YEAR(d-14);
60            || self.is_named_holiday(&NEW_YEAR, date - Duration::days(20)) // Eve of Simchat
61            || self.is_named_holiday(&NEW_YEAR, date - Duration::days(21))
62        // Simchat Torah = NEW_YEAR(d-21);
63        {
64            return false;
65        }
66
67        true
68    }
69
70    fn shir_is_business_day(&self, date: NaiveDate) -> bool {
71        let (d, w, m, y, dd) = self.naive_date_to_dkmy(date);
72        let em = self.easter_monday(y);
73        if self.is_weekend(date) {
74            return false;
75        }
76
77        if self.is_named_holiday(&PURIM, date)
78            || self.is_named_holiday(&PURIM, date - Duration::days(1)) // Jerusalem Purim
79            || self.is_named_holiday(&PASSOVER, date + Duration::days(1)) // Eve of Passover
80            || self.is_named_holiday(&PASSOVER, date)
81            || self.is_named_holiday(&PASSOVER, date - Duration::days(6)) // Last day of Passover
82            || self.is_named_holiday(&INDEPENDENCE_DAY, date)
83            || self.is_named_holiday(&SHAVUOT, date)
84            || self.is_named_holiday(&FAST_DAY, date)
85            || self.is_named_holiday(&NEW_YEAR, date + Duration::days(1))
86            || self.is_named_holiday(&NEW_YEAR, date)
87            || self.is_named_holiday(&NEW_YEAR, date - Duration::days(1))
88            || self.is_named_holiday(&NEW_YEAR, date - Duration::days(8)) // Eve of Yom Kippur
89            || self.is_named_holiday(&NEW_YEAR, date - Duration::days(9)) // Yom Kippur = NEW_YEAR(d-9);
90            || self.is_named_holiday(&NEW_YEAR, date - Duration::days(14)) // Sukkot =  NEW_YEAR(d-14);
91            || self.is_named_holiday(&NEW_YEAR, date - Duration::days(21)) // Simchat Torah = NEW_YEAR(d-21);
92            // one-off closings
93            || (d == 27 && m == 2 && y == 2024) // Municipal elections
94            // holidays abroad
95            || (d == 1 && m == 1) // Western New Year's day
96            || dd == em - 3 // Good Friday
97            || (d >= 25 && w == Weekday::Mon && m == 5 && y != 2022) // Spring Bank Holiday
98            || (d == 3 && m == 6 && y == 2022)
99            || (d == 25 && m == 12) // Christmas
100            || (d == 26 && m == 12) // Boxing day
101            // other days when fixings were not published
102            || (d == 1 && m == 11 && y == 2022) // no idea why
103            || (d == 2 && m == 1 && y == 2023) // Maybe New Year's Day is adjusted to Monday?
104            || (d == 10 && m == 4 && y == 2023)
105        // Easter Monday, not a holiday in 2024 and 2025
106        {
107            return false;
108        }
109
110        true
111    }
112}
113
114pub static PURIM: Lazy<HashSet<(u32, u32, i32)>> = Lazy::new(|| {
115    HashSet::from([
116        (21, 3, 2000),
117        (9, 3, 2001),
118        (26, 2, 2002),
119        (18, 3, 2003),
120        (7, 3, 2004),
121        (25, 3, 2005),
122        (14, 3, 2006),
123        (4, 3, 2007),
124        (21, 3, 2008),
125        (10, 3, 2009),
126        (28, 2, 2010),
127        (20, 3, 2011),
128        (8, 3, 2012),
129        (24, 2, 2013),
130        (16, 3, 2014),
131        (5, 3, 2015),
132        (24, 3, 2016),
133        (12, 3, 2017),
134        (1, 3, 2018),
135        (21, 3, 2019),
136        (10, 3, 2020),
137        (26, 2, 2021),
138        (17, 3, 2022),
139        (7, 3, 2023),
140        (24, 3, 2024),
141        (14, 3, 2025),
142        (3, 3, 2026),
143        (23, 3, 2027),
144        (12, 3, 2028),
145        (1, 3, 2029),
146        (19, 3, 2030),
147        (9, 3, 2031),
148        (26, 2, 2032),
149        (15, 3, 2033),
150        (5, 3, 2034),
151        (25, 3, 2035),
152        (13, 3, 2036),
153        (1, 3, 2037),
154        (21, 3, 2038),
155        (10, 3, 2039),
156        (28, 2, 2040),
157        (17, 3, 2041),
158        (6, 3, 2042),
159        (26, 3, 2043),
160        (13, 3, 2044),
161        (3, 3, 2045),
162        (22, 3, 2046),
163        (12, 3, 2047),
164        (28, 2, 2048),
165        (18, 3, 2049),
166        (8, 3, 2050),
167    ])
168});
169
170pub static PASSOVER: Lazy<HashSet<(u32, u32, i32)>> = Lazy::new(|| {
171    HashSet::from([
172        (20, 4, 2000),
173        (8, 4, 2001),
174        (28, 3, 2002),
175        (17, 4, 2003),
176        (6, 4, 2004),
177        (24, 4, 2005),
178        (13, 4, 2006),
179        (3, 4, 2007),
180        (20, 4, 2008),
181        (9, 4, 2009),
182        (30, 3, 2010),
183        (19, 4, 2011),
184        (7, 4, 2012),
185        (26, 3, 2013),
186        (15, 4, 2014),
187        (4, 4, 2015),
188        (23, 4, 2016),
189        (11, 4, 2017),
190        (31, 3, 2018),
191        (20, 4, 2019),
192        (9, 4, 2020),
193        (28, 3, 2021),
194        (16, 4, 2022),
195        (6, 4, 2023),
196        (23, 4, 2024),
197        (13, 4, 2025),
198        (2, 4, 2026),
199        (22, 4, 2027),
200        (11, 4, 2028),
201        (31, 3, 2029),
202        (18, 4, 2030),
203        (8, 4, 2031),
204        (27, 3, 2032),
205        (14, 4, 2033),
206        (4, 4, 2034),
207        (24, 4, 2035),
208        (12, 4, 2036),
209        (31, 3, 2037),
210        (20, 4, 2038),
211        (9, 4, 2039),
212        (29, 3, 2040),
213        (16, 4, 2041),
214        (5, 4, 2042),
215        (25, 4, 2043),
216        (12, 4, 2044),
217        (2, 4, 2045),
218        (21, 4, 2046),
219        (11, 4, 2047),
220        (29, 3, 2048),
221        (17, 4, 2049),
222        (7, 4, 2050),
223    ])
224});
225
226pub static INDEPENDENCE_DAY: Lazy<HashSet<(u32, u32, i32)>> = Lazy::new(|| {
227    HashSet::from([
228        (10, 5, 2000),
229        (26, 4, 2001),
230        (17, 4, 2002),
231        (7, 5, 2003),
232        (27, 4, 2004),
233        (12, 5, 2005),
234        (3, 5, 2006),
235        (24, 4, 2007),
236        (8, 5, 2008),
237        (29, 4, 2009),
238        (20, 4, 2010),
239        (10, 5, 2011),
240        (26, 4, 2012),
241        (16, 4, 2013),
242        (6, 5, 2014),
243        (23, 4, 2015),
244        (12, 5, 2016),
245        (2, 5, 2017),
246        (19, 4, 2018),
247        (9, 5, 2019),
248        (29, 4, 2020),
249        (15, 4, 2021),
250        (5, 5, 2022),
251        (26, 4, 2023),
252        (14, 5, 2024),
253        (1, 5, 2025),
254        (22, 4, 2026),
255        (12, 5, 2027),
256        (2, 5, 2028),
257        (19, 4, 2029),
258        (8, 5, 2030),
259        (29, 4, 2031),
260        (15, 4, 2032),
261        (4, 5, 2033),
262        (25, 4, 2034),
263        (15, 5, 2035),
264        (1, 5, 2036),
265        (21, 4, 2037),
266        (10, 5, 2038),
267        (28, 4, 2039),
268        (18, 4, 2040),
269        (7, 5, 2041),
270        (24, 4, 2042),
271        (14, 5, 2043),
272        (3, 5, 2044),
273        (20, 4, 2045),
274        (10, 5, 2046),
275        (1, 5, 2047),
276        (16, 4, 2048),
277        (6, 5, 2049),
278        (27, 4, 2050),
279    ])
280});
281
282pub static SHAVUOT: Lazy<HashSet<(u32, u32, i32)>> = Lazy::new(|| {
283    HashSet::from([
284        (9, 6, 2000),
285        (28, 5, 2001),
286        (17, 5, 2002),
287        (6, 6, 2003),
288        (26, 5, 2004),
289        (13, 6, 2005),
290        (2, 6, 2006),
291        (23, 5, 2007),
292        (9, 6, 2008),
293        (29, 5, 2009),
294        (19, 5, 2010),
295        (8, 6, 2011),
296        (27, 5, 2012),
297        (15, 5, 2013),
298        (4, 6, 2014),
299        (24, 5, 2015),
300        (12, 6, 2016),
301        (31, 5, 2017),
302        (20, 5, 2018),
303        (9, 6, 2019),
304        (29, 5, 2020),
305        (17, 5, 2021),
306        (5, 6, 2022),
307        (26, 5, 2023),
308        (12, 6, 2024),
309        (2, 6, 2025),
310        (22, 5, 2026),
311        (11, 6, 2027),
312        (31, 5, 2028),
313        (20, 5, 2029),
314        (7, 6, 2030),
315        (28, 5, 2031),
316        (16, 5, 2032),
317        (3, 6, 2033),
318        (24, 5, 2034),
319        (13, 6, 2035),
320        (1, 6, 2036),
321        (20, 5, 2037),
322        (9, 6, 2038),
323        (29, 5, 2039),
324        (18, 5, 2040),
325        (5, 6, 2041),
326        (25, 5, 2042),
327        (14, 6, 2043),
328        (1, 6, 2044),
329        (22, 5, 2045),
330        (10, 6, 2046),
331        (31, 5, 2047),
332        (18, 5, 2048),
333        (6, 6, 2049),
334        (27, 5, 2050),
335    ])
336});
337
338pub static FAST_DAY: Lazy<HashSet<(u32, u32, i32)>> = Lazy::new(|| {
339    HashSet::from([
340        (10, 8, 2000),
341        (29, 7, 2001),
342        (18, 7, 2002),
343        (7, 8, 2003),
344        (27, 7, 2004),
345        (14, 8, 2005),
346        (3, 8, 2006),
347        (24, 7, 2007),
348        (10, 8, 2008),
349        (30, 7, 2009),
350        (20, 7, 2010),
351        (9, 8, 2011),
352        (29, 7, 2012),
353        (16, 7, 2013),
354        (5, 8, 2014),
355        (26, 7, 2015),
356        (14, 8, 2016),
357        (1, 8, 2017),
358        (22, 7, 2018),
359        (11, 8, 2019),
360        (30, 7, 2020),
361        (18, 7, 2021),
362        (7, 8, 2022),
363        (27, 7, 2023),
364        (13, 8, 2024),
365        (3, 8, 2025),
366        (23, 7, 2026),
367        (12, 8, 2027),
368        (1, 8, 2028),
369        (22, 7, 2029),
370        (8, 8, 2030),
371        (29, 7, 2031),
372        (18, 7, 2032),
373        (4, 8, 2033),
374        (25, 7, 2034),
375        (14, 8, 2035),
376        (3, 8, 2036),
377        (21, 7, 2037),
378        (10, 8, 2038),
379        (31, 7, 2039),
380        (19, 7, 2040),
381        (6, 8, 2041),
382        (27, 7, 2042),
383        (16, 8, 2043),
384        (2, 8, 2044),
385        (23, 7, 2045),
386        (12, 8, 2046),
387        (1, 8, 2047),
388        (19, 7, 2048),
389        (8, 8, 2049),
390        (28, 7, 2050),
391    ])
392});
393
394pub static NEW_YEAR: Lazy<HashSet<(u32, u32, i32)>> = Lazy::new(|| {
395    HashSet::from([
396        (30, 9, 2000),
397        (17, 9, 2001),
398        (7, 9, 2002),
399        (27, 9, 2003),
400        (16, 9, 2004),
401        (4, 10, 2005),
402        (23, 9, 2006),
403        (13, 9, 2007),
404        (30, 9, 2008),
405        (19, 9, 2009),
406        (9, 9, 2010),
407        (29, 9, 2011),
408        (17, 9, 2012),
409        (5, 9, 2013),
410        (25, 9, 2014),
411        (14, 9, 2015),
412        (3, 10, 2016),
413        (21, 9, 2017),
414        (10, 9, 2018),
415        (30, 9, 2019),
416        (19, 9, 2020),
417        (7, 9, 2021),
418        (26, 9, 2022),
419        (16, 9, 2023),
420        (3, 10, 2024),
421        (23, 9, 2025),
422        (12, 9, 2026),
423        (2, 10, 2027),
424        (21, 9, 2028),
425        (10, 9, 2029),
426        (28, 9, 2030),
427        (18, 9, 2031),
428        (6, 9, 2032),
429        (24, 9, 2033),
430        (14, 9, 2034),
431        (4, 10, 2035),
432        (22, 9, 2036),
433        (10, 9, 2037),
434        (30, 9, 2038),
435        (19, 9, 2039),
436        (8, 9, 2040),
437        (26, 9, 2041),
438        (15, 9, 2042),
439        (5, 10, 2043),
440        (22, 9, 2044),
441        (12, 9, 2045),
442        (1, 10, 2046),
443        (21, 9, 2047),
444        (8, 9, 2048),
445        (27, 9, 2049),
446        (17, 9, 2050),
447    ])
448});
449
450#[cfg(test)]
451mod tests {
452    use super::Israel;
453    use crate::time::calendars::Calendar;
454    use chrono::{Duration, NaiveDate};
455
456    #[test]
457    fn test_israel_holiday() {
458        // Test all results from 2023-01-01 to 2023-12-31
459        let expected_results_for_2023 = vec![
460            false, false, true, true, true, true, false, false, true, true, true, true, true,
461            false, false, true, true, true, true, true, false, false, true, true, true, true, true,
462            false, false, true, true, true, true, true, false, false, true, true, true, true, true,
463            false, false, true, true, true, true, true, false, false, true, true, true, true, true,
464            false, false, true, true, true, true, true, false, false, true, false, false, true,
465            true, false, false, true, true, true, true, true, false, false, true, true, true, true,
466            true, false, false, true, true, true, true, true, false, false, true, true, false,
467            false, false, false, false, false, true, false, true, true, false, false, true, true,
468            true, true, true, false, false, true, true, false, true, true, false, false, true,
469            true, true, true, true, false, false, true, true, true, true, true, false, false, true,
470            true, true, true, true, false, false, true, true, true, true, false, false, false,
471            false, true, true, true, true, false, false, true, true, true, true, true, false,
472            false, true, true, true, true, true, false, false, true, true, true, true, true, false,
473            false, true, true, true, true, true, false, false, true, true, true, true, true, false,
474            false, true, true, true, true, true, false, false, true, true, true, true, true, false,
475            false, true, true, true, false, true, false, false, true, true, true, true, true,
476            false, false, true, true, true, true, true, false, false, true, true, true, true, true,
477            false, false, true, true, true, true, true, false, false, true, true, true, true, true,
478            false, false, true, true, true, true, true, false, false, true, true, true, true,
479            false, false, false, true, true, true, true, true, false, false, false, true, true,
480            true, true, false, false, true, true, true, true, true, false, false, true, true, true,
481            true, true, false, false, true, true, true, true, true, false, false, true, true, true,
482            true, true, false, false, true, true, true, true, true, false, false, true, true, true,
483            true, true, false, false, true, true, true, true, true, false, false, true, true, true,
484            true, true, false, false, true, true, true, true, true, false, false, true, true, true,
485            true, true, false, false, true, true, true, true, true, false, false, true, true, true,
486            true, true, false, false, false, false, true, true, true, false, false, false,
487        ];
488        let first_date = NaiveDate::from_ymd_opt(2023, 1, 1).unwrap();
489        for n in 0i32..365 {
490            let target_date = first_date + Duration::try_days(n as i64).unwrap();
491            let expected = expected_results_for_2023[n as usize];
492            assert_eq!(
493                Israel::default().is_business_day(target_date),
494                expected,
495                "Mismatch on {}",
496                target_date
497            );
498        }
499    }
500}