1use 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)) || 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)) || self.is_named_holiday(&NEW_YEAR, date - Duration::days(8)) || self.is_named_holiday(&NEW_YEAR, date - Duration::days(9)) || self.is_named_holiday(&NEW_YEAR, date - Duration::days(13)) || self.is_named_holiday(&NEW_YEAR, date - Duration::days(14)) || self.is_named_holiday(&NEW_YEAR, date - Duration::days(20)) || self.is_named_holiday(&NEW_YEAR, date - Duration::days(21))
62 {
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)) || self.is_named_holiday(&PASSOVER, date + Duration::days(1)) || self.is_named_holiday(&PASSOVER, date)
81 || self.is_named_holiday(&PASSOVER, date - Duration::days(6)) || 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)) || self.is_named_holiday(&NEW_YEAR, date - Duration::days(9)) || self.is_named_holiday(&NEW_YEAR, date - Duration::days(14)) || self.is_named_holiday(&NEW_YEAR, date - Duration::days(21)) || (d == 27 && m == 2 && y == 2024) || (d == 1 && m == 1) || dd == em - 3 || (d >= 25 && w == Weekday::Mon && m == 5 && y != 2022) || (d == 3 && m == 6 && y == 2022)
99 || (d == 25 && m == 12) || (d == 26 && m == 12) || (d == 1 && m == 11 && y == 2022) || (d == 2 && m == 1 && y == 2023) || (d == 10 && m == 4 && y == 2023)
105 {
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 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}