1use chrono::{DateTime, Datelike, Duration, NaiveDate, NaiveTime, Utc};
9use std::collections::BTreeSet;
10use std::sync::Arc;
11
12use crate::holiday::{HolidayRule, Weekday, WeekendRoll};
13use crate::range::{
14 business_day_range, business_days_between, next_business_day, previous_business_day,
15 STANDARD_WEEKMASK,
16};
17use crate::trading_hours::{Session, TradingHours};
18
19pub use finance_enums::data::ExchangeCode_VARIANTS as EXCHANGE_CODES;
20
21#[derive(Clone, Copy, Debug, PartialEq, Eq)]
23pub enum MarketType {
24 Equity,
26 Options,
28 Futures,
30 Fx,
32 Bond,
34 Crypto,
36 Other,
38}
39
40impl MarketType {
41 pub fn as_str(self) -> &'static str {
42 match self {
43 MarketType::Equity => "equity",
44 MarketType::Options => "options",
45 MarketType::Futures => "futures",
46 MarketType::Fx => "fx",
47 MarketType::Bond => "bond",
48 MarketType::Crypto => "crypto",
49 MarketType::Other => "other",
50 }
51 }
52}
53
54pub const CRYPTO_WEEKMASK: [bool; 7] = [true, true, true, true, true, true, true];
56
57pub const FX_WEEKMASK: [bool; 7] = [true, true, true, true, true, false, true];
59
60pub const REGION_CODES: &[&str] = &[
62 "US", "UK", "GB", "EU", "JP", "HK", "CN", "CA", "AU", "IN", "DE", "FR", "NL", "BE", "PT", "IT",
63 "ES", "CH", "NO", "SE", "FI", "DK", "IS", "PL", "CZ", "HU", "AT", "IE", "KR", "SG", "TW", "TH",
64 "MY", "ID", "PH", "NZ", "ZA", "SA", "TR", "IL", "AE", "BR", "MX", "AR", "CL", "PE", "CO",
65];
66
67pub struct Calendar {
69 pub name: String,
70 pub market_type: MarketType,
71 pub weekmask: [bool; 7],
72 pub rules: Vec<HolidayRule>,
73 pub trading_hours: Option<TradingHours>,
74 pub early_closes: Vec<EarlyCloseRule>,
78 cache: HolidayCache,
79 early_cache: EarlyCloseCache,
80}
81
82#[derive(Clone, Debug)]
86pub struct EarlyCloseRule {
87 pub rule: HolidayRule,
88 pub close_time: NaiveTime,
89}
90
91#[derive(Default)]
92struct EarlyCloseCache {
93 inner: parking_lot_dummy::RwLock<
94 std::collections::HashMap<i32, Arc<std::collections::HashMap<NaiveDate, NaiveTime>>>,
95 >,
96}
97
98#[derive(Default)]
99struct HolidayCache {
100 inner: parking_lot_dummy::RwLock<std::collections::HashMap<i32, Arc<BTreeSet<NaiveDate>>>>,
101}
102
103mod parking_lot_dummy {
104 use std::sync::RwLock as StdRwLock;
105 pub struct RwLock<T>(pub StdRwLock<T>);
106 impl<T: Default> Default for RwLock<T> {
107 fn default() -> Self {
108 Self(StdRwLock::new(T::default()))
109 }
110 }
111 impl<T> RwLock<T> {
112 pub fn read(&self) -> std::sync::RwLockReadGuard<'_, T> {
113 self.0.read().unwrap()
114 }
115 pub fn write(&self) -> std::sync::RwLockWriteGuard<'_, T> {
116 self.0.write().unwrap()
117 }
118 }
119}
120
121impl Calendar {
122 pub fn new(
123 name: impl Into<String>,
124 weekmask: [bool; 7],
125 rules: Vec<HolidayRule>,
126 trading_hours: Option<TradingHours>,
127 ) -> Self {
128 Self::with_type(name, MarketType::Equity, weekmask, rules, trading_hours)
129 }
130
131 pub fn with_type(
132 name: impl Into<String>,
133 market_type: MarketType,
134 weekmask: [bool; 7],
135 rules: Vec<HolidayRule>,
136 trading_hours: Option<TradingHours>,
137 ) -> Self {
138 Self {
139 name: name.into(),
140 market_type,
141 weekmask,
142 rules,
143 trading_hours,
144 early_closes: Vec::new(),
145 cache: HolidayCache::default(),
146 early_cache: EarlyCloseCache::default(),
147 }
148 }
149
150 pub fn with_early_closes(mut self, ec: Vec<EarlyCloseRule>) -> Self {
152 self.early_closes = ec;
153 self
154 }
155
156 fn early_close_map(&self, year: i32) -> Arc<std::collections::HashMap<NaiveDate, NaiveTime>> {
158 if let Some(m) = self.early_cache.inner.read().get(&year).cloned() {
159 return m;
160 }
161 let mut m = std::collections::HashMap::new();
162 for ec in &self.early_closes {
163 if let Some(d) = ec.rule.observed_in(year) {
164 let i = d.weekday().num_days_from_monday() as usize;
167 if !self.weekmask[i] {
168 continue;
169 }
170 if self.holidays(year).contains(&d) {
171 continue;
172 }
173 m.insert(d, ec.close_time);
174 }
175 }
176 let arc = Arc::new(m);
177 self.early_cache.inner.write().insert(year, arc.clone());
178 arc
179 }
180
181 pub fn early_close_for(&self, date: NaiveDate) -> Option<NaiveTime> {
183 self.early_close_map(date.year()).get(&date).copied()
184 }
185
186 pub fn holidays(&self, year: i32) -> Arc<BTreeSet<NaiveDate>> {
187 if let Some(h) = self.cache.inner.read().get(&year).cloned() {
188 return h;
189 }
190 let mut set = BTreeSet::new();
191 for r in &self.rules {
192 for d in r.dates_in(year) {
193 set.insert(d);
194 }
195 }
196 let arc = Arc::new(set);
197 self.cache.inner.write().insert(year, arc.clone());
198 arc
199 }
200
201 pub fn holidays_between(&self, start: NaiveDate, end: NaiveDate) -> BTreeSet<NaiveDate> {
202 let mut out = BTreeSet::new();
203 for y in start.year()..=end.year() {
204 for d in self.holidays(y).iter() {
205 if *d >= start && *d <= end {
206 out.insert(*d);
207 }
208 }
209 }
210 out
211 }
212
213 pub fn is_holiday(&self, d: NaiveDate) -> bool {
214 self.holidays(d.year()).contains(&d)
215 }
216
217 pub fn is_business_day(&self, d: NaiveDate) -> bool {
218 let i = d.weekday().num_days_from_monday() as usize;
219 self.weekmask[i] && !self.is_holiday(d)
220 }
221
222 pub fn next_business_day(&self, d: NaiveDate) -> NaiveDate {
223 let years = [d.year(), d.year() + 1];
224 let mut h = BTreeSet::new();
225 for y in years {
226 for x in self.holidays(y).iter() {
227 h.insert(*x);
228 }
229 }
230 next_business_day(d, &self.weekmask, &h)
231 }
232
233 pub fn previous_business_day(&self, d: NaiveDate) -> NaiveDate {
234 let years = [d.year() - 1, d.year()];
235 let mut h = BTreeSet::new();
236 for y in years {
237 for x in self.holidays(y).iter() {
238 h.insert(*x);
239 }
240 }
241 previous_business_day(d, &self.weekmask, &h)
242 }
243
244 pub fn business_days_between(&self, start: NaiveDate, end: NaiveDate) -> i64 {
245 let h = self.holidays_between(start, end);
246 business_days_between(start, end, &self.weekmask, &h)
247 }
248
249 pub fn business_day_range(&self, start: NaiveDate, end: NaiveDate) -> Vec<NaiveDate> {
250 let h = self.holidays_between(start, end);
251 business_day_range(start, end, &self.weekmask, &h)
252 }
253
254 pub fn is_open(&self, when: DateTime<Utc>) -> bool {
262 let Some(th) = &self.trading_hours else {
263 return false;
264 };
265 let local_today = when.with_timezone(&th.timezone).date_naive();
266 for delta in [0i64, 1] {
267 let trading_day = local_today + Duration::days(delta);
268 if !self.is_business_day(trading_day) {
269 continue;
270 }
271 let early = self.early_close_for(trading_day);
272 let last_idx = th.sessions.len().saturating_sub(1);
273 for (i, s) in th.sessions.iter().enumerate() {
274 let Some((o, mut c)) = s.instants(th.timezone, trading_day) else {
275 continue;
276 };
277 if i == last_idx {
278 if let Some(t) = early {
279 if let Some(early_c) = adjust_close(th.timezone, trading_day, s, t) {
280 c = early_c;
281 }
282 }
283 }
284 if when >= o && when < c {
285 return true;
286 }
287 }
288 }
289 false
290 }
291
292 pub fn next_open(&self, when: DateTime<Utc>) -> Option<DateTime<Utc>> {
293 let th = self.trading_hours.as_ref()?;
294 let local_today = when.with_timezone(&th.timezone).date_naive();
295 for delta in 0..400i64 {
296 let trading_day = local_today + Duration::days(delta);
297 if !self.is_business_day(trading_day) {
298 continue;
299 }
300 for s in &th.sessions {
301 if let Some((o, _)) = s.instants(th.timezone, trading_day) {
302 if o >= when {
303 return Some(o);
304 }
305 }
306 }
307 }
308 None
309 }
310
311 pub fn next_close(&self, when: DateTime<Utc>) -> Option<DateTime<Utc>> {
312 let th = self.trading_hours.as_ref()?;
313 let local_today = when.with_timezone(&th.timezone).date_naive();
314 for delta in 0..400i64 {
315 let trading_day = local_today + Duration::days(delta);
316 if !self.is_business_day(trading_day) {
317 continue;
318 }
319 let early = self.early_close_for(trading_day);
320 let last_idx = th.sessions.len().saturating_sub(1);
321 for (i, s) in th.sessions.iter().enumerate() {
322 let Some((_, mut c)) = s.instants(th.timezone, trading_day) else {
323 continue;
324 };
325 if i == last_idx {
326 if let Some(t) = early {
327 if let Some(early_c) = adjust_close(th.timezone, trading_day, s, t) {
328 c = early_c;
329 }
330 }
331 }
332 if c >= when {
333 return Some(c);
334 }
335 }
336 }
337 None
338 }
339
340 pub fn sessions_between(
346 &self,
347 start: NaiveDate,
348 end: NaiveDate,
349 ) -> Vec<(DateTime<Utc>, DateTime<Utc>)> {
350 let Some(th) = &self.trading_hours else {
351 return Vec::new();
352 };
353 let mut out = Vec::new();
354 let last_idx = th.sessions.len().saturating_sub(1);
355 let mut d = start;
356 while d <= end {
357 if self.is_business_day(d) {
358 let early = self.early_close_for(d);
359 for (i, s) in th.sessions.iter().enumerate() {
360 let Some((o, mut c)) = s.instants(th.timezone, d) else {
361 continue;
362 };
363 if i == last_idx {
364 if let Some(t) = early {
365 if let Some(early_c) = adjust_close(th.timezone, d, s, t) {
366 c = early_c;
367 }
368 }
369 }
370 out.push((o, c));
371 }
372 }
373 d += Duration::days(1);
374 }
375 out
376 }
377}
378
379fn adjust_close(
383 tz: chrono_tz::Tz,
384 trading_day: NaiveDate,
385 session: &Session,
386 local_close_time: NaiveTime,
387) -> Option<DateTime<Utc>> {
388 use chrono::TimeZone;
389 let close_local_day = trading_day + Duration::days(session.close_day_offset as i64);
390 let close = tz
391 .from_local_datetime(&close_local_day.and_time(local_close_time))
392 .single()?;
393 Some(close.with_timezone(&Utc))
394}
395
396fn fixed(month: u32, day: u32, since_year: Option<i32>) -> HolidayRule {
399 HolidayRule::Fixed {
400 month,
401 day,
402 roll: WeekendRoll::NearestWeekday,
403 since_year,
404 }
405}
406
407fn fixed_no_roll(month: u32, day: u32, since_year: Option<i32>) -> HolidayRule {
408 HolidayRule::Fixed {
409 month,
410 day,
411 roll: WeekendRoll::None,
412 since_year,
413 }
414}
415
416fn nth(month: u32, weekday: Weekday, n: i32) -> HolidayRule {
417 HolidayRule::NthWeekday {
418 month,
419 weekday,
420 n,
421 since_year: None,
422 }
423}
424
425fn easter(offset_days: i32) -> HolidayRule {
426 HolidayRule::EasterOffset {
427 offset_days,
428 since_year: None,
429 }
430}
431
432fn nyse_rules() -> Vec<HolidayRule> {
435 vec![
436 fixed(1, 1, None),
437 nth(1, Weekday::Mon, 3),
438 nth(2, Weekday::Mon, 3),
439 easter(-2),
440 nth(5, Weekday::Mon, -1),
441 fixed(6, 19, Some(2021)),
442 fixed(7, 4, None),
443 nth(9, Weekday::Mon, 1),
444 nth(11, Weekday::Thu, 4),
445 fixed(12, 25, None),
446 ]
447}
448
449fn nyse_trading_hours() -> TradingHours {
450 TradingHours::new(
451 NaiveTime::from_hms_opt(9, 30, 0).unwrap(),
452 NaiveTime::from_hms_opt(16, 0, 0).unwrap(),
453 chrono_tz::America::New_York,
454 )
455}
456
457fn options_trading_hours() -> TradingHours {
461 TradingHours::new(
462 NaiveTime::from_hms_opt(9, 30, 0).unwrap(),
463 NaiveTime::from_hms_opt(16, 15, 0).unwrap(),
464 chrono_tz::America::New_York,
465 )
466}
467
468fn cme_globex_rules() -> Vec<HolidayRule> {
473 vec![fixed(1, 1, None), easter(-2), fixed(12, 25, None)]
474}
475
476fn cme_globex_overnight_hours() -> TradingHours {
478 TradingHours::from_sessions(
479 vec![Session::overnight(
480 NaiveTime::from_hms_opt(17, 0, 0).unwrap(),
481 NaiveTime::from_hms_opt(16, 0, 0).unwrap(),
482 )],
483 chrono_tz::America::Chicago,
484 )
485}
486
487fn cme_globex_energy_hours() -> TradingHours {
490 TradingHours::from_sessions(
491 vec![Session::overnight(
492 NaiveTime::from_hms_opt(17, 0, 0).unwrap(),
493 NaiveTime::from_hms_opt(16, 0, 0).unwrap(),
494 )],
495 chrono_tz::America::Chicago,
496 )
497}
498
499fn cfe_rules() -> Vec<HolidayRule> {
501 vec![
502 fixed(1, 1, None),
503 nth(1, Weekday::Mon, 3),
504 nth(2, Weekday::Mon, 3),
505 easter(-2),
506 nth(5, Weekday::Mon, -1),
507 fixed(6, 19, Some(2022)),
508 fixed(7, 4, None),
509 nth(9, Weekday::Mon, 1),
510 nth(11, Weekday::Thu, 4),
511 fixed(12, 25, None),
512 ]
513}
514
515fn cfe_trading_hours() -> TradingHours {
516 TradingHours::new(
517 NaiveTime::from_hms_opt(8, 30, 0).unwrap(),
518 NaiveTime::from_hms_opt(15, 15, 0).unwrap(),
519 chrono_tz::America::Chicago,
520 )
521}
522
523fn ice_us_rules() -> Vec<HolidayRule> {
525 vec![fixed(1, 1, None), easter(-2), fixed(12, 25, None)]
526}
527
528fn ice_us_hours() -> TradingHours {
529 TradingHours::from_sessions(
530 vec![Session::overnight(
531 NaiveTime::from_hms_opt(20, 0, 0).unwrap(),
532 NaiveTime::from_hms_opt(18, 0, 0).unwrap(),
533 )],
534 chrono_tz::America::New_York,
535 )
536}
537
538fn sifma_us_rules() -> Vec<HolidayRule> {
540 vec![
541 fixed(1, 1, None),
542 nth(1, Weekday::Mon, 3),
543 nth(2, Weekday::Mon, 3),
544 easter(-2),
545 nth(5, Weekday::Mon, -1),
546 fixed(6, 19, Some(2022)),
547 fixed(7, 4, None),
548 nth(9, Weekday::Mon, 1),
549 nth(10, Weekday::Mon, 2), fixed(11, 11, None), nth(11, Weekday::Thu, 4),
552 fixed(12, 25, None),
553 ]
554}
555
556fn sifma_us_hours() -> TradingHours {
557 TradingHours::new(
558 NaiveTime::from_hms_opt(7, 0, 0).unwrap(),
559 NaiveTime::from_hms_opt(17, 30, 0).unwrap(),
560 chrono_tz::America::New_York,
561 )
562}
563
564fn forex_rules() -> Vec<HolidayRule> {
567 vec![fixed(1, 1, None), fixed(12, 25, None)]
568}
569
570fn crypto_rules() -> Vec<HolidayRule> {
572 vec![]
573}
574
575fn lse_rules() -> Vec<HolidayRule> {
576 vec![
577 fixed(1, 1, None),
578 easter(-2),
579 easter(1),
580 nth(5, Weekday::Mon, 1),
581 nth(5, Weekday::Mon, -1),
582 nth(8, Weekday::Mon, -1),
583 fixed(12, 25, None),
584 fixed(12, 26, None),
585 ]
586}
587
588fn lse_trading_hours() -> TradingHours {
589 TradingHours::new(
590 NaiveTime::from_hms_opt(8, 0, 0).unwrap(),
591 NaiveTime::from_hms_opt(16, 30, 0).unwrap(),
592 chrono_tz::Europe::London,
593 )
594}
595
596fn tse_rules() -> Vec<HolidayRule> {
597 vec![
598 fixed_no_roll(1, 1, None),
599 fixed_no_roll(1, 2, None),
600 fixed_no_roll(1, 3, None),
601 nth(1, Weekday::Mon, 2),
602 fixed_no_roll(2, 11, None),
603 fixed_no_roll(2, 23, Some(2020)),
604 fixed_no_roll(4, 29, None),
605 fixed_no_roll(5, 3, None),
606 fixed_no_roll(5, 4, None),
607 fixed_no_roll(5, 5, None),
608 nth(7, Weekday::Mon, 3),
609 fixed_no_roll(8, 11, None),
610 nth(9, Weekday::Mon, 3),
611 nth(10, Weekday::Mon, 2),
612 fixed_no_roll(11, 3, None),
613 fixed_no_roll(11, 23, None),
614 fixed_no_roll(12, 31, None),
615 ]
616}
617
618fn tse_trading_hours() -> TradingHours {
619 TradingHours::new(
620 NaiveTime::from_hms_opt(9, 0, 0).unwrap(),
621 NaiveTime::from_hms_opt(15, 0, 0).unwrap(),
622 chrono_tz::Asia::Tokyo,
623 )
624}
625
626fn hkex_rules() -> Vec<HolidayRule> {
627 let lny: &'static [(i32, u32, u32)] = &[
628 (2020, 1, 27),
629 (2021, 2, 12),
630 (2022, 2, 1),
631 (2023, 1, 23),
632 (2024, 2, 12),
633 (2025, 1, 29),
634 (2026, 2, 17),
635 (2027, 2, 8),
636 (2028, 1, 26),
637 (2029, 2, 13),
638 (2030, 2, 4),
639 ];
640 vec![
641 fixed(1, 1, None),
642 HolidayRule::Tabulated { table: lny },
643 easter(-2),
644 easter(1),
645 fixed(5, 1, None),
646 fixed(7, 1, None),
647 fixed(10, 1, None),
648 fixed(12, 25, None),
649 fixed(12, 26, None),
650 ]
651}
652
653fn hkex_trading_hours() -> TradingHours {
654 TradingHours::new(
655 NaiveTime::from_hms_opt(9, 30, 0).unwrap(),
656 NaiveTime::from_hms_opt(16, 0, 0).unwrap(),
657 chrono_tz::Asia::Hong_Kong,
658 )
659}
660
661fn sse_rules() -> Vec<HolidayRule> {
662 let lny: &'static [(i32, u32, u32)] = &[
663 (2020, 1, 25),
664 (2021, 2, 12),
665 (2022, 2, 1),
666 (2023, 1, 22),
667 (2024, 2, 10),
668 (2025, 1, 29),
669 (2026, 2, 17),
670 (2027, 2, 6),
671 (2028, 1, 26),
672 (2029, 2, 13),
673 (2030, 2, 3),
674 ];
675 vec![
676 fixed(1, 1, None),
677 HolidayRule::Tabulated { table: lny },
678 fixed(5, 1, None),
679 fixed(10, 1, None),
680 fixed(10, 2, None),
681 fixed(10, 3, None),
682 ]
683}
684
685fn sse_trading_hours() -> TradingHours {
686 TradingHours::new(
687 NaiveTime::from_hms_opt(9, 30, 0).unwrap(),
688 NaiveTime::from_hms_opt(15, 0, 0).unwrap(),
689 chrono_tz::Asia::Shanghai,
690 )
691}
692
693fn xetra_rules() -> Vec<HolidayRule> {
694 vec![
695 fixed(1, 1, None),
696 easter(-2),
697 easter(1),
698 fixed(5, 1, None),
699 fixed(10, 3, None),
700 fixed(12, 24, None),
701 fixed(12, 25, None),
702 fixed(12, 26, None),
703 fixed(12, 31, None),
704 ]
705}
706
707fn xetra_trading_hours() -> TradingHours {
708 TradingHours::new(
709 NaiveTime::from_hms_opt(9, 0, 0).unwrap(),
710 NaiveTime::from_hms_opt(17, 30, 0).unwrap(),
711 chrono_tz::Europe::Berlin,
712 )
713}
714
715fn euronext_paris_rules() -> Vec<HolidayRule> {
716 vec![
717 fixed(1, 1, None),
718 easter(-2),
719 easter(1),
720 fixed(5, 1, None),
721 fixed(12, 25, None),
722 fixed(12, 26, None),
723 ]
724}
725
726fn euronext_paris_trading_hours() -> TradingHours {
727 TradingHours::new(
728 NaiveTime::from_hms_opt(9, 0, 0).unwrap(),
729 NaiveTime::from_hms_opt(17, 30, 0).unwrap(),
730 chrono_tz::Europe::Paris,
731 )
732}
733
734fn tsx_rules() -> Vec<HolidayRule> {
735 vec![
736 fixed(1, 1, None),
737 nth(2, Weekday::Mon, 3),
738 easter(-2),
739 nth(5, Weekday::Mon, -1),
740 fixed(7, 1, None),
741 nth(8, Weekday::Mon, 1),
742 nth(9, Weekday::Mon, 1),
743 nth(10, Weekday::Mon, 2),
744 fixed(12, 25, None),
745 fixed(12, 26, None),
746 ]
747}
748
749fn tsx_trading_hours() -> TradingHours {
750 TradingHours::new(
751 NaiveTime::from_hms_opt(9, 30, 0).unwrap(),
752 NaiveTime::from_hms_opt(16, 0, 0).unwrap(),
753 chrono_tz::America::Toronto,
754 )
755}
756
757fn asx_rules() -> Vec<HolidayRule> {
758 vec![
759 fixed(1, 1, None),
760 fixed(1, 26, None),
761 easter(-2),
762 easter(1),
763 fixed(4, 25, None),
764 nth(6, Weekday::Mon, 2),
765 fixed(12, 25, None),
766 fixed(12, 26, None),
767 ]
768}
769
770fn asx_trading_hours() -> TradingHours {
771 TradingHours::new(
772 NaiveTime::from_hms_opt(10, 0, 0).unwrap(),
773 NaiveTime::from_hms_opt(16, 0, 0).unwrap(),
774 chrono_tz::Australia::Sydney,
775 )
776}
777
778fn nse_rules() -> Vec<HolidayRule> {
779 vec![
780 fixed(1, 26, None),
781 fixed(8, 15, None),
782 fixed(10, 2, None),
783 fixed(12, 25, None),
784 ]
785}
786
787fn nse_trading_hours() -> TradingHours {
788 TradingHours::new(
789 NaiveTime::from_hms_opt(9, 15, 0).unwrap(),
790 NaiveTime::from_hms_opt(15, 30, 0).unwrap(),
791 chrono_tz::Asia::Kolkata,
792 )
793}
794
795fn ec(rule: HolidayRule, h: u32, m: u32) -> EarlyCloseRule {
798 EarlyCloseRule {
799 rule,
800 close_time: NaiveTime::from_hms_opt(h, m, 0).unwrap(),
801 }
802}
803
804fn nyse_early_closes() -> Vec<EarlyCloseRule> {
811 static BLACK_FRIDAY: &[(i32, u32, u32)] = &[
814 (2020, 11, 27),
815 (2021, 11, 26),
816 (2022, 11, 25),
817 (2023, 11, 24),
818 (2024, 11, 29),
819 (2025, 11, 28),
820 (2026, 11, 27),
821 (2027, 11, 26),
822 (2028, 11, 24),
823 (2029, 11, 23),
824 (2030, 11, 29),
825 (2031, 11, 28),
826 (2032, 11, 26),
827 (2033, 11, 25),
828 (2034, 11, 24),
829 (2035, 11, 23),
830 ];
831 vec![
832 ec(
833 HolidayRule::Tabulated {
834 table: BLACK_FRIDAY,
835 },
836 13,
837 0,
838 ),
839 ec(fixed_no_roll(12, 24, None), 13, 0),
840 ec(fixed_no_roll(7, 3, None), 13, 0),
841 ]
842}
843
844fn euro_basic_rules() -> Vec<HolidayRule> {
849 vec![
850 fixed(1, 1, None),
851 easter(-2),
852 easter(1),
853 fixed(5, 1, None),
854 fixed(12, 25, None),
855 fixed(12, 26, None),
856 ]
857}
858
859fn euronext_hours(tz: chrono_tz::Tz) -> TradingHours {
861 TradingHours::new(
862 NaiveTime::from_hms_opt(9, 0, 0).unwrap(),
863 NaiveTime::from_hms_opt(17, 30, 0).unwrap(),
864 tz,
865 )
866}
867
868fn xams_rules() -> Vec<HolidayRule> {
869 vec![
872 fixed(1, 1, None),
873 easter(-2),
874 easter(1),
875 fixed_no_roll(4, 27, Some(2014)),
876 easter(39),
877 easter(50),
878 fixed(12, 25, None),
879 fixed(12, 26, None),
880 ]
881}
882
883fn xbru_rules() -> Vec<HolidayRule> {
884 vec![
887 fixed(1, 1, None),
888 easter(-2),
889 easter(1),
890 fixed(5, 1, None),
891 easter(39),
892 easter(50),
893 fixed(12, 25, None),
894 fixed(12, 26, None),
895 ]
896}
897
898fn xlis_rules() -> Vec<HolidayRule> {
899 let mut r = euro_basic_rules();
901 r.push(easter(-47));
902 r
903}
904
905fn xmil_rules() -> Vec<HolidayRule> {
906 vec![
910 fixed(1, 1, None),
911 fixed_no_roll(1, 6, None),
912 easter(-2),
913 easter(1),
914 fixed_no_roll(4, 25, None),
915 fixed(5, 1, None),
916 fixed_no_roll(6, 2, None),
917 fixed_no_roll(8, 15, None),
918 fixed_no_roll(11, 1, None),
919 fixed_no_roll(12, 8, None),
920 fixed(12, 25, None),
921 fixed(12, 26, None),
922 ]
923}
924
925fn xmil_hours() -> TradingHours {
926 TradingHours::new(
927 NaiveTime::from_hms_opt(9, 0, 0).unwrap(),
928 NaiveTime::from_hms_opt(17, 30, 0).unwrap(),
929 chrono_tz::Europe::Rome,
930 )
931}
932
933fn xmad_rules() -> Vec<HolidayRule> {
934 vec![
938 fixed(1, 1, None),
939 fixed_no_roll(1, 6, None),
940 easter(-2),
941 easter(1),
942 fixed(5, 1, None),
943 fixed_no_roll(8, 15, None),
944 fixed_no_roll(10, 12, None),
945 fixed_no_roll(11, 1, None),
946 fixed_no_roll(12, 6, None),
947 fixed_no_roll(12, 8, None),
948 fixed(12, 25, None),
949 fixed(12, 26, None),
950 ]
951}
952
953fn xmad_hours() -> TradingHours {
954 TradingHours::new(
955 NaiveTime::from_hms_opt(9, 0, 0).unwrap(),
956 NaiveTime::from_hms_opt(17, 30, 0).unwrap(),
957 chrono_tz::Europe::Madrid,
958 )
959}
960
961fn xswx_rules() -> Vec<HolidayRule> {
962 vec![
965 fixed(1, 1, None),
966 fixed_no_roll(1, 2, None),
967 easter(-2),
968 easter(1),
969 fixed(5, 1, None),
970 easter(39),
971 easter(50),
972 fixed_no_roll(8, 1, None),
973 fixed(12, 25, None),
974 fixed(12, 26, None),
975 ]
976}
977
978fn xswx_hours() -> TradingHours {
979 TradingHours::new(
980 NaiveTime::from_hms_opt(9, 0, 0).unwrap(),
981 NaiveTime::from_hms_opt(17, 30, 0).unwrap(),
982 chrono_tz::Europe::Zurich,
983 )
984}
985
986fn xosl_rules() -> Vec<HolidayRule> {
987 vec![
991 fixed(1, 1, None),
992 easter(-3),
993 easter(-2),
994 easter(1),
995 fixed(5, 1, None),
996 fixed_no_roll(5, 17, None),
997 easter(39),
998 easter(50),
999 fixed(12, 25, None),
1000 fixed(12, 26, None),
1001 ]
1002}
1003
1004fn xosl_hours() -> TradingHours {
1005 TradingHours::new(
1006 NaiveTime::from_hms_opt(9, 0, 0).unwrap(),
1007 NaiveTime::from_hms_opt(16, 20, 0).unwrap(),
1008 chrono_tz::Europe::Oslo,
1009 )
1010}
1011
1012fn xsto_rules() -> Vec<HolidayRule> {
1013 vec![
1017 fixed(1, 1, None),
1018 fixed_no_roll(1, 6, None),
1019 easter(-2),
1020 easter(1),
1021 fixed(5, 1, None),
1022 easter(39),
1023 fixed_no_roll(6, 6, None),
1024 fixed_no_roll(12, 24, None),
1025 fixed(12, 25, None),
1026 fixed(12, 26, None),
1027 fixed_no_roll(12, 31, None),
1028 ]
1029}
1030
1031fn xsto_hours() -> TradingHours {
1032 TradingHours::new(
1033 NaiveTime::from_hms_opt(9, 0, 0).unwrap(),
1034 NaiveTime::from_hms_opt(17, 30, 0).unwrap(),
1035 chrono_tz::Europe::Stockholm,
1036 )
1037}
1038
1039fn xhel_rules() -> Vec<HolidayRule> {
1040 vec![
1044 fixed(1, 1, None),
1045 fixed_no_roll(1, 6, None),
1046 easter(-2),
1047 easter(1),
1048 fixed(5, 1, None),
1049 easter(39),
1050 fixed_no_roll(12, 6, None),
1051 fixed_no_roll(12, 24, None),
1052 fixed(12, 25, None),
1053 fixed(12, 26, None),
1054 ]
1055}
1056
1057fn xhel_hours() -> TradingHours {
1058 TradingHours::new(
1059 NaiveTime::from_hms_opt(10, 0, 0).unwrap(),
1060 NaiveTime::from_hms_opt(18, 30, 0).unwrap(),
1061 chrono_tz::Europe::Helsinki,
1062 )
1063}
1064
1065fn xcse_rules() -> Vec<HolidayRule> {
1066 vec![
1070 fixed(1, 1, None),
1071 easter(-3),
1072 easter(-2),
1073 easter(1),
1074 easter(39),
1075 fixed_no_roll(6, 5, None),
1076 fixed_no_roll(12, 24, None),
1077 fixed(12, 25, None),
1078 fixed(12, 26, None),
1079 ]
1080}
1081
1082fn xcse_hours() -> TradingHours {
1083 TradingHours::new(
1084 NaiveTime::from_hms_opt(9, 0, 0).unwrap(),
1085 NaiveTime::from_hms_opt(17, 0, 0).unwrap(),
1086 chrono_tz::Europe::Copenhagen,
1087 )
1088}
1089
1090fn xice_rules() -> Vec<HolidayRule> {
1091 vec![
1095 fixed(1, 1, None),
1096 easter(-3),
1097 easter(-2),
1098 easter(1),
1099 fixed(5, 1, None),
1100 easter(39),
1101 easter(50),
1102 fixed_no_roll(6, 17, None),
1103 fixed_no_roll(12, 24, None),
1104 fixed(12, 25, None),
1105 fixed(12, 26, None),
1106 ]
1107}
1108
1109fn xice_hours() -> TradingHours {
1110 TradingHours::new(
1111 NaiveTime::from_hms_opt(10, 0, 0).unwrap(),
1112 NaiveTime::from_hms_opt(15, 30, 0).unwrap(),
1113 chrono_tz::Atlantic::Reykjavik,
1114 )
1115}
1116
1117fn xwar_rules() -> Vec<HolidayRule> {
1118 vec![
1122 fixed(1, 1, None),
1123 fixed_no_roll(1, 6, None),
1124 easter(1),
1125 fixed(5, 1, None),
1126 fixed_no_roll(5, 3, None),
1127 easter(60),
1128 fixed_no_roll(8, 15, None),
1129 fixed_no_roll(11, 1, None),
1130 fixed_no_roll(11, 11, None),
1131 fixed(12, 25, None),
1132 fixed(12, 26, None),
1133 ]
1134}
1135
1136fn xwar_hours() -> TradingHours {
1137 TradingHours::new(
1138 NaiveTime::from_hms_opt(9, 0, 0).unwrap(),
1139 NaiveTime::from_hms_opt(17, 0, 0).unwrap(),
1140 chrono_tz::Europe::Warsaw,
1141 )
1142}
1143
1144fn xpra_rules() -> Vec<HolidayRule> {
1145 vec![
1149 fixed(1, 1, None),
1150 easter(-2),
1151 easter(1),
1152 fixed(5, 1, None),
1153 fixed_no_roll(5, 8, None),
1154 fixed_no_roll(7, 5, None),
1155 fixed_no_roll(7, 6, None),
1156 fixed_no_roll(9, 28, None),
1157 fixed_no_roll(10, 28, None),
1158 fixed_no_roll(11, 17, None),
1159 fixed_no_roll(12, 24, None),
1160 fixed(12, 25, None),
1161 fixed(12, 26, None),
1162 ]
1163}
1164
1165fn xpra_hours() -> TradingHours {
1166 TradingHours::new(
1167 NaiveTime::from_hms_opt(9, 0, 0).unwrap(),
1168 NaiveTime::from_hms_opt(16, 25, 0).unwrap(),
1169 chrono_tz::Europe::Prague,
1170 )
1171}
1172
1173fn xbud_rules() -> Vec<HolidayRule> {
1174 vec![
1178 fixed(1, 1, None),
1179 fixed_no_roll(3, 15, None),
1180 easter(-2),
1181 easter(1),
1182 fixed(5, 1, None),
1183 easter(50),
1184 fixed_no_roll(8, 20, None),
1185 fixed_no_roll(10, 23, None),
1186 fixed_no_roll(11, 1, None),
1187 fixed(12, 25, None),
1188 fixed(12, 26, None),
1189 ]
1190}
1191
1192fn xbud_hours() -> TradingHours {
1193 TradingHours::new(
1194 NaiveTime::from_hms_opt(9, 0, 0).unwrap(),
1195 NaiveTime::from_hms_opt(17, 0, 0).unwrap(),
1196 chrono_tz::Europe::Budapest,
1197 )
1198}
1199
1200fn xwbo_rules() -> Vec<HolidayRule> {
1201 vec![
1205 fixed(1, 1, None),
1206 easter(-2),
1207 easter(1),
1208 fixed(5, 1, None),
1209 easter(39),
1210 easter(50),
1211 easter(60),
1212 fixed_no_roll(8, 15, None),
1213 fixed_no_roll(10, 26, None),
1214 fixed_no_roll(11, 1, None),
1215 fixed_no_roll(12, 8, None),
1216 fixed_no_roll(12, 24, None),
1217 fixed(12, 25, None),
1218 fixed(12, 26, None),
1219 ]
1220}
1221
1222fn xwbo_hours() -> TradingHours {
1223 TradingHours::new(
1224 NaiveTime::from_hms_opt(9, 0, 0).unwrap(),
1225 NaiveTime::from_hms_opt(17, 30, 0).unwrap(),
1226 chrono_tz::Europe::Vienna,
1227 )
1228}
1229
1230fn xdub_rules() -> Vec<HolidayRule> {
1231 vec![
1235 fixed(1, 1, None),
1236 fixed(3, 17, None),
1237 easter(-2),
1238 easter(1),
1239 nth(5, Weekday::Mon, 1),
1240 nth(6, Weekday::Mon, 1),
1241 nth(8, Weekday::Mon, 1),
1242 nth(10, Weekday::Mon, -1),
1243 fixed(12, 25, None),
1244 fixed(12, 26, None),
1245 ]
1246}
1247
1248fn xdub_hours() -> TradingHours {
1249 TradingHours::new(
1250 NaiveTime::from_hms_opt(8, 0, 0).unwrap(),
1251 NaiveTime::from_hms_opt(16, 28, 0).unwrap(),
1252 chrono_tz::Europe::Dublin,
1253 )
1254}
1255
1256fn xkrx_rules() -> Vec<HolidayRule> {
1259 let seollal: &'static [(i32, u32, u32)] = &[
1262 (2020, 1, 24),
1263 (2020, 1, 27),
1264 (2021, 2, 11),
1265 (2021, 2, 12),
1266 (2022, 1, 31),
1267 (2022, 2, 1),
1268 (2022, 2, 2),
1269 (2023, 1, 23),
1270 (2023, 1, 24),
1271 (2024, 2, 9),
1272 (2024, 2, 12),
1273 (2025, 1, 28),
1274 (2025, 1, 29),
1275 (2025, 1, 30),
1276 (2026, 2, 16),
1277 (2026, 2, 17),
1278 (2026, 2, 18),
1279 ];
1280 let chuseok: &'static [(i32, u32, u32)] = &[
1281 (2020, 9, 30),
1282 (2020, 10, 1),
1283 (2020, 10, 2),
1284 (2021, 9, 20),
1285 (2021, 9, 21),
1286 (2021, 9, 22),
1287 (2022, 9, 9),
1288 (2022, 9, 12),
1289 (2023, 9, 28),
1290 (2023, 9, 29),
1291 (2024, 9, 16),
1292 (2024, 9, 17),
1293 (2024, 9, 18),
1294 (2025, 10, 6),
1295 (2025, 10, 7),
1296 (2025, 10, 8),
1297 (2026, 9, 24),
1298 (2026, 9, 25),
1299 ];
1300 vec![
1301 fixed(1, 1, None),
1302 HolidayRule::Tabulated { table: seollal },
1303 fixed_no_roll(3, 1, None), fixed_no_roll(5, 5, None), fixed_no_roll(6, 6, None), fixed_no_roll(8, 15, None), HolidayRule::Tabulated { table: chuseok },
1308 fixed_no_roll(10, 3, None), fixed_no_roll(10, 9, None), fixed(12, 25, None),
1311 ]
1312}
1313
1314fn xkrx_hours() -> TradingHours {
1315 TradingHours::new(
1316 NaiveTime::from_hms_opt(9, 0, 0).unwrap(),
1317 NaiveTime::from_hms_opt(15, 30, 0).unwrap(),
1318 chrono_tz::Asia::Seoul,
1319 )
1320}
1321
1322fn xses_rules() -> Vec<HolidayRule> {
1323 let lny: &'static [(i32, u32, u32)] = &[
1327 (2020, 1, 24),
1328 (2021, 2, 12),
1329 (2022, 2, 1),
1330 (2023, 1, 23),
1331 (2024, 2, 12),
1332 (2025, 1, 29),
1333 (2026, 2, 17),
1334 ];
1335 let lny2: &'static [(i32, u32, u32)] = &[
1336 (2020, 1, 27),
1337 (2021, 2, 15),
1338 (2022, 2, 2),
1339 (2023, 1, 24),
1340 (2024, 2, 13),
1341 (2025, 1, 30),
1342 (2026, 2, 18),
1343 ];
1344 vec![
1345 fixed(1, 1, None),
1346 HolidayRule::Tabulated { table: lny },
1347 HolidayRule::Tabulated { table: lny2 },
1348 easter(-2),
1349 fixed(5, 1, None),
1350 fixed(8, 9, None),
1351 fixed(12, 25, None),
1352 ]
1353}
1354
1355fn xses_hours() -> TradingHours {
1356 TradingHours::new(
1357 NaiveTime::from_hms_opt(9, 0, 0).unwrap(),
1358 NaiveTime::from_hms_opt(17, 0, 0).unwrap(),
1359 chrono_tz::Asia::Singapore,
1360 )
1361}
1362
1363fn xtai_rules() -> Vec<HolidayRule> {
1364 let lny: &'static [(i32, u32, u32)] = &[
1368 (2020, 1, 23),
1369 (2021, 2, 8),
1370 (2022, 1, 27),
1371 (2023, 1, 19),
1372 (2024, 2, 5),
1373 (2025, 1, 23),
1374 (2026, 2, 13),
1375 ];
1376 vec![
1377 fixed(1, 1, None),
1378 HolidayRule::Tabulated { table: lny },
1379 fixed_no_roll(2, 28, None), fixed_no_roll(4, 4, None), fixed_no_roll(4, 5, None), fixed(5, 1, None),
1383 fixed_no_roll(10, 10, None), ]
1385}
1386
1387fn xtai_hours() -> TradingHours {
1388 TradingHours::new(
1389 NaiveTime::from_hms_opt(9, 0, 0).unwrap(),
1390 NaiveTime::from_hms_opt(13, 30, 0).unwrap(),
1391 chrono_tz::Asia::Taipei,
1392 )
1393}
1394
1395fn xbkk_rules() -> Vec<HolidayRule> {
1396 vec![
1401 fixed(1, 1, None),
1402 fixed(4, 6, None),
1403 fixed_no_roll(4, 13, None),
1404 fixed_no_roll(4, 14, None),
1405 fixed_no_roll(4, 15, None),
1406 fixed(5, 1, None),
1407 fixed_no_roll(5, 4, None),
1408 fixed_no_roll(8, 12, None),
1409 fixed(10, 13, None),
1410 fixed(10, 23, None),
1411 fixed(12, 5, None),
1412 fixed(12, 10, None),
1413 fixed_no_roll(12, 31, None),
1414 ]
1415}
1416
1417fn xbkk_hours() -> TradingHours {
1418 TradingHours::new(
1419 NaiveTime::from_hms_opt(10, 0, 0).unwrap(),
1420 NaiveTime::from_hms_opt(16, 30, 0).unwrap(),
1421 chrono_tz::Asia::Bangkok,
1422 )
1423}
1424
1425fn xkls_rules() -> Vec<HolidayRule> {
1426 let lny: &'static [(i32, u32, u32)] = &[
1430 (2020, 1, 27),
1431 (2021, 2, 12),
1432 (2022, 2, 1),
1433 (2023, 1, 23),
1434 (2024, 2, 12),
1435 (2025, 1, 29),
1436 (2026, 2, 17),
1437 ];
1438 vec![
1439 fixed(1, 1, None),
1440 HolidayRule::Tabulated { table: lny },
1441 fixed(5, 1, None),
1442 nth(6, Weekday::Mon, 1),
1443 fixed(8, 31, None),
1444 fixed(9, 16, None),
1445 fixed(12, 25, None),
1446 ]
1447}
1448
1449fn xkls_hours() -> TradingHours {
1450 TradingHours::new(
1451 NaiveTime::from_hms_opt(9, 0, 0).unwrap(),
1452 NaiveTime::from_hms_opt(17, 0, 0).unwrap(),
1453 chrono_tz::Asia::Kuala_Lumpur,
1454 )
1455}
1456
1457fn xidx_rules() -> Vec<HolidayRule> {
1458 let lny: &'static [(i32, u32, u32)] = &[
1461 (2020, 1, 27),
1462 (2021, 2, 12),
1463 (2022, 2, 1),
1464 (2023, 1, 23),
1465 (2024, 2, 8),
1466 (2025, 1, 29),
1467 (2026, 2, 17),
1468 ];
1469 vec![
1470 fixed(1, 1, None),
1471 HolidayRule::Tabulated { table: lny },
1472 fixed(5, 1, None),
1473 fixed(6, 1, None),
1474 fixed(8, 17, None),
1475 fixed(12, 25, None),
1476 ]
1477}
1478
1479fn xidx_hours() -> TradingHours {
1480 TradingHours::new(
1481 NaiveTime::from_hms_opt(9, 0, 0).unwrap(),
1482 NaiveTime::from_hms_opt(15, 50, 0).unwrap(),
1483 chrono_tz::Asia::Jakarta,
1484 )
1485}
1486
1487fn xphs_rules() -> Vec<HolidayRule> {
1488 vec![
1493 fixed(1, 1, None),
1494 easter(-3),
1495 easter(-2),
1496 fixed(4, 9, None),
1497 fixed(5, 1, None),
1498 fixed(6, 12, None),
1499 fixed(8, 21, None),
1500 nth(8, Weekday::Mon, -1),
1501 fixed_no_roll(11, 1, None),
1502 fixed(11, 30, None),
1503 fixed(12, 25, None),
1504 fixed(12, 30, None),
1505 fixed_no_roll(12, 31, None),
1506 ]
1507}
1508
1509fn xphs_hours() -> TradingHours {
1510 TradingHours::new(
1511 NaiveTime::from_hms_opt(9, 30, 0).unwrap(),
1512 NaiveTime::from_hms_opt(15, 0, 0).unwrap(),
1513 chrono_tz::Asia::Manila,
1514 )
1515}
1516
1517fn xnze_rules() -> Vec<HolidayRule> {
1518 vec![
1523 fixed(1, 1, None),
1524 fixed(1, 2, None),
1525 fixed(2, 6, None),
1526 easter(-2),
1527 easter(1),
1528 fixed(4, 25, None),
1529 nth(6, Weekday::Mon, 1),
1530 nth(10, Weekday::Mon, 4),
1531 fixed(12, 25, None),
1532 fixed(12, 26, None),
1533 ]
1534}
1535
1536fn xnze_hours() -> TradingHours {
1537 TradingHours::new(
1538 NaiveTime::from_hms_opt(10, 0, 0).unwrap(),
1539 NaiveTime::from_hms_opt(16, 45, 0).unwrap(),
1540 chrono_tz::Pacific::Auckland,
1541 )
1542}
1543
1544fn xjse_rules() -> Vec<HolidayRule> {
1547 vec![
1552 fixed(1, 1, None),
1553 fixed(3, 21, None),
1554 easter(-2),
1555 easter(1),
1556 fixed(4, 27, None),
1557 fixed(5, 1, None),
1558 fixed(6, 16, None),
1559 fixed(8, 9, None),
1560 fixed(9, 24, None),
1561 fixed(12, 16, None),
1562 fixed(12, 25, None),
1563 fixed(12, 26, None),
1564 ]
1565}
1566
1567fn xjse_hours() -> TradingHours {
1568 TradingHours::new(
1569 NaiveTime::from_hms_opt(9, 0, 0).unwrap(),
1570 NaiveTime::from_hms_opt(17, 0, 0).unwrap(),
1571 chrono_tz::Africa::Johannesburg,
1572 )
1573}
1574
1575const MIDEAST_WEEKMASK: [bool; 7] = [true, true, true, true, false, false, true];
1577
1578fn xsau_rules() -> Vec<HolidayRule> {
1579 let eid_fitr: &'static [(i32, u32, u32)] = &[
1582 (2020, 5, 24),
1583 (2021, 5, 13),
1584 (2022, 5, 2),
1585 (2023, 4, 21),
1586 (2024, 4, 10),
1587 (2025, 3, 30),
1588 (2026, 3, 20),
1589 ];
1590 let eid_adha: &'static [(i32, u32, u32)] = &[
1591 (2020, 7, 31),
1592 (2021, 7, 20),
1593 (2022, 7, 9),
1594 (2023, 6, 28),
1595 (2024, 6, 16),
1596 (2025, 6, 6),
1597 (2026, 5, 27),
1598 ];
1599 vec![
1600 fixed_no_roll(2, 22, Some(2022)),
1601 fixed_no_roll(9, 23, None),
1602 HolidayRule::Tabulated { table: eid_fitr },
1603 HolidayRule::Tabulated { table: eid_adha },
1604 ]
1605}
1606
1607fn xsau_hours() -> TradingHours {
1608 TradingHours::new(
1609 NaiveTime::from_hms_opt(10, 0, 0).unwrap(),
1610 NaiveTime::from_hms_opt(15, 0, 0).unwrap(),
1611 chrono_tz::Asia::Riyadh,
1612 )
1613}
1614
1615fn xist_rules() -> Vec<HolidayRule> {
1616 let eid_fitr: &'static [(i32, u32, u32)] = &[
1620 (2020, 5, 24),
1621 (2021, 5, 13),
1622 (2022, 5, 2),
1623 (2023, 4, 21),
1624 (2024, 4, 10),
1625 (2025, 3, 30),
1626 (2026, 3, 20),
1627 ];
1628 let eid_adha: &'static [(i32, u32, u32)] = &[
1629 (2020, 7, 31),
1630 (2021, 7, 20),
1631 (2022, 7, 9),
1632 (2023, 6, 28),
1633 (2024, 6, 16),
1634 (2025, 6, 6),
1635 (2026, 5, 27),
1636 ];
1637 vec![
1638 fixed(1, 1, None),
1639 fixed(4, 23, None),
1640 fixed(5, 1, None),
1641 fixed(5, 19, None),
1642 fixed(7, 15, None),
1643 fixed(8, 30, None),
1644 fixed(10, 29, None),
1645 HolidayRule::Tabulated { table: eid_fitr },
1646 HolidayRule::Tabulated { table: eid_adha },
1647 ]
1648}
1649
1650fn xist_hours() -> TradingHours {
1651 TradingHours::new(
1652 NaiveTime::from_hms_opt(10, 0, 0).unwrap(),
1653 NaiveTime::from_hms_opt(18, 0, 0).unwrap(),
1654 chrono_tz::Europe::Istanbul,
1655 )
1656}
1657
1658const TASE_WEEKMASK: [bool; 7] = [true, true, true, true, false, false, true];
1660
1661fn xtae_rules() -> Vec<HolidayRule> {
1662 let purim: &'static [(i32, u32, u32)] = &[
1666 (2020, 3, 10),
1667 (2021, 2, 26),
1668 (2022, 3, 17),
1669 (2023, 3, 7),
1670 (2024, 3, 24),
1671 (2025, 3, 14),
1672 (2026, 3, 3),
1673 ];
1674 let passover_eve: &'static [(i32, u32, u32)] = &[
1675 (2020, 4, 8),
1676 (2021, 3, 27),
1677 (2022, 4, 15),
1678 (2023, 4, 5),
1679 (2024, 4, 22),
1680 (2025, 4, 12),
1681 (2026, 4, 1),
1682 ];
1683 let shavuot: &'static [(i32, u32, u32)] = &[
1684 (2020, 5, 29),
1685 (2021, 5, 17),
1686 (2022, 6, 5),
1687 (2023, 5, 26),
1688 (2024, 6, 12),
1689 (2025, 6, 2),
1690 (2026, 5, 22),
1691 ];
1692 let rosh: &'static [(i32, u32, u32)] = &[
1693 (2020, 9, 19),
1694 (2021, 9, 7),
1695 (2022, 9, 26),
1696 (2023, 9, 16),
1697 (2024, 10, 3),
1698 (2025, 9, 23),
1699 (2026, 9, 12),
1700 ];
1701 let yom_kippur: &'static [(i32, u32, u32)] = &[
1702 (2020, 9, 28),
1703 (2021, 9, 16),
1704 (2022, 10, 5),
1705 (2023, 9, 25),
1706 (2024, 10, 12),
1707 (2025, 10, 2),
1708 (2026, 9, 21),
1709 ];
1710 let sukkot: &'static [(i32, u32, u32)] = &[
1711 (2020, 10, 3),
1712 (2021, 9, 21),
1713 (2022, 10, 10),
1714 (2023, 9, 30),
1715 (2024, 10, 17),
1716 (2025, 10, 7),
1717 (2026, 9, 26),
1718 ];
1719 let independence: &'static [(i32, u32, u32)] = &[
1720 (2020, 4, 29),
1721 (2021, 4, 15),
1722 (2022, 5, 5),
1723 (2023, 4, 26),
1724 (2024, 5, 14),
1725 (2025, 5, 1),
1726 (2026, 4, 22),
1727 ];
1728 vec![
1729 HolidayRule::Tabulated { table: purim },
1730 HolidayRule::Tabulated {
1731 table: passover_eve,
1732 },
1733 HolidayRule::Tabulated { table: shavuot },
1734 HolidayRule::Tabulated {
1735 table: independence,
1736 },
1737 HolidayRule::Tabulated { table: rosh },
1738 HolidayRule::Tabulated { table: yom_kippur },
1739 HolidayRule::Tabulated { table: sukkot },
1740 ]
1741}
1742
1743fn xtae_hours() -> TradingHours {
1744 TradingHours::new(
1745 NaiveTime::from_hms_opt(9, 59, 0).unwrap(),
1746 NaiveTime::from_hms_opt(17, 14, 0).unwrap(),
1747 chrono_tz::Asia::Jerusalem,
1748 )
1749}
1750
1751fn xdfm_rules() -> Vec<HolidayRule> {
1752 let eid_fitr: &'static [(i32, u32, u32)] = &[
1755 (2020, 5, 24),
1756 (2021, 5, 13),
1757 (2022, 5, 2),
1758 (2023, 4, 21),
1759 (2024, 4, 10),
1760 (2025, 3, 30),
1761 (2026, 3, 20),
1762 ];
1763 let eid_adha: &'static [(i32, u32, u32)] = &[
1764 (2020, 7, 31),
1765 (2021, 7, 20),
1766 (2022, 7, 9),
1767 (2023, 6, 28),
1768 (2024, 6, 16),
1769 (2025, 6, 6),
1770 (2026, 5, 27),
1771 ];
1772 vec![
1773 fixed(1, 1, None),
1774 fixed(11, 30, None),
1775 fixed(12, 2, None),
1776 fixed(12, 3, None),
1777 HolidayRule::Tabulated { table: eid_fitr },
1778 HolidayRule::Tabulated { table: eid_adha },
1779 ]
1780}
1781
1782fn xdfm_hours() -> TradingHours {
1783 TradingHours::new(
1784 NaiveTime::from_hms_opt(10, 0, 0).unwrap(),
1785 NaiveTime::from_hms_opt(15, 0, 0).unwrap(),
1786 chrono_tz::Asia::Dubai,
1787 )
1788}
1789
1790fn bvmf_rules() -> Vec<HolidayRule> {
1793 vec![
1798 fixed(1, 1, None),
1799 easter(-48),
1800 easter(-47),
1801 easter(-2),
1802 fixed(4, 21, None),
1803 fixed(5, 1, None),
1804 easter(60),
1805 fixed(9, 7, None),
1806 fixed(10, 12, None),
1807 fixed(11, 2, None),
1808 fixed(11, 15, None),
1809 fixed(11, 20, Some(2024)),
1810 fixed_no_roll(12, 24, None),
1811 fixed(12, 25, None),
1812 fixed_no_roll(12, 31, None),
1813 ]
1814}
1815
1816fn bvmf_hours() -> TradingHours {
1817 TradingHours::new(
1818 NaiveTime::from_hms_opt(10, 0, 0).unwrap(),
1819 NaiveTime::from_hms_opt(17, 30, 0).unwrap(),
1820 chrono_tz::America::Sao_Paulo,
1821 )
1822}
1823
1824fn xmex_rules() -> Vec<HolidayRule> {
1825 vec![
1829 fixed(1, 1, None),
1830 nth(2, Weekday::Mon, 1),
1831 nth(3, Weekday::Mon, 3),
1832 easter(-3),
1833 easter(-2),
1834 fixed(5, 1, None),
1835 fixed(9, 16, None),
1836 nth(11, Weekday::Mon, 3),
1837 fixed(12, 25, None),
1838 ]
1839}
1840
1841fn xmex_hours() -> TradingHours {
1842 TradingHours::new(
1843 NaiveTime::from_hms_opt(8, 30, 0).unwrap(),
1844 NaiveTime::from_hms_opt(15, 0, 0).unwrap(),
1845 chrono_tz::America::Mexico_City,
1846 )
1847}
1848
1849fn xbue_rules() -> Vec<HolidayRule> {
1850 vec![
1855 fixed(1, 1, None),
1856 easter(-48),
1857 easter(-47),
1858 fixed(3, 24, None),
1859 fixed(4, 2, None),
1860 easter(-2),
1861 fixed(5, 1, None),
1862 fixed(5, 25, None),
1863 fixed(6, 20, None),
1864 fixed(7, 9, None),
1865 nth(8, Weekday::Mon, 3),
1866 fixed(10, 12, None),
1867 fixed(11, 20, None),
1868 fixed(12, 8, None),
1869 fixed(12, 25, None),
1870 ]
1871}
1872
1873fn xbue_hours() -> TradingHours {
1874 TradingHours::new(
1875 NaiveTime::from_hms_opt(11, 0, 0).unwrap(),
1876 NaiveTime::from_hms_opt(17, 0, 0).unwrap(),
1877 chrono_tz::America::Argentina::Buenos_Aires,
1878 )
1879}
1880
1881fn xsgo_rules() -> Vec<HolidayRule> {
1882 vec![
1887 fixed(1, 1, None),
1888 easter(-2),
1889 easter(-1),
1890 fixed(5, 1, None),
1891 fixed(5, 21, None),
1892 fixed(6, 29, None),
1893 fixed(7, 16, None),
1894 fixed(8, 15, None),
1895 fixed(9, 18, None),
1896 fixed(9, 19, None),
1897 fixed(10, 12, None),
1898 fixed(10, 31, None),
1899 fixed(11, 1, None),
1900 fixed(12, 8, None),
1901 fixed(12, 25, None),
1902 ]
1903}
1904
1905fn xsgo_hours() -> TradingHours {
1906 TradingHours::new(
1907 NaiveTime::from_hms_opt(9, 30, 0).unwrap(),
1908 NaiveTime::from_hms_opt(16, 0, 0).unwrap(),
1909 chrono_tz::America::Santiago,
1910 )
1911}
1912
1913fn xlim_rules() -> Vec<HolidayRule> {
1914 vec![
1918 fixed(1, 1, None),
1919 easter(-3),
1920 easter(-2),
1921 fixed(5, 1, None),
1922 fixed(6, 29, None),
1923 fixed(7, 28, None),
1924 fixed(7, 29, None),
1925 fixed(8, 30, None),
1926 fixed(10, 8, None),
1927 fixed(11, 1, None),
1928 fixed(12, 8, None),
1929 fixed(12, 25, None),
1930 ]
1931}
1932
1933fn xlim_hours() -> TradingHours {
1934 TradingHours::new(
1935 NaiveTime::from_hms_opt(8, 30, 0).unwrap(),
1936 NaiveTime::from_hms_opt(15, 0, 0).unwrap(),
1937 chrono_tz::America::Lima,
1938 )
1939}
1940
1941fn xbog_rules() -> Vec<HolidayRule> {
1942 vec![
1948 fixed(1, 1, None),
1949 fixed(1, 6, None),
1950 fixed(3, 19, None),
1951 easter(-3),
1952 easter(-2),
1953 fixed(5, 1, None),
1954 easter(39),
1955 easter(60),
1956 easter(68),
1957 fixed(7, 20, None),
1958 fixed(8, 7, None),
1959 fixed(8, 15, None),
1960 fixed(10, 12, None),
1961 fixed(11, 1, None),
1962 fixed(11, 11, None),
1963 fixed(12, 8, None),
1964 fixed(12, 25, None),
1965 ]
1966}
1967
1968fn xbog_hours() -> TradingHours {
1969 TradingHours::new(
1970 NaiveTime::from_hms_opt(9, 30, 0).unwrap(),
1971 NaiveTime::from_hms_opt(16, 0, 0).unwrap(),
1972 chrono_tz::America::Bogota,
1973 )
1974}
1975
1976#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1980enum Family {
1981 UsEquity,
1982 UsOptions,
1983 UsBondSifma,
1984 UsFuturesCme,
1985 UsFuturesCmeEnergy,
1986 UsFuturesIce,
1987 UsFuturesCfe,
1988 Forex24x5,
1989 Crypto24x7,
1990 Lse,
1991 Tse,
1992 Hkex,
1993 Sse,
1994 Xetra,
1995 EuronextParis,
1996 EuronextAms,
1997 EuronextBru,
1998 EuronextLis,
1999 EuronextDub,
2000 Tsx,
2001 Asx,
2002 Nse,
2003 Xmil,
2004 Xmad,
2005 Xswx,
2006 Xosl,
2007 Xsto,
2008 Xhel,
2009 Xcse,
2010 Xice,
2011 Xwar,
2012 Xpra,
2013 Xbud,
2014 Xwbo,
2015 Xkrx,
2016 Xses,
2017 Xtai,
2018 Xbkk,
2019 Xkls,
2020 Xidx,
2021 Xphs,
2022 Xnze,
2023 Xjse,
2024 Xsau,
2025 Xist,
2026 Xtae,
2027 Xdfm,
2028 Bvmf,
2029 Xmex,
2030 Xbue,
2031 Xsgo,
2032 Xlim,
2033 Xbog,
2034}
2035
2036fn family_for_mic(mic: &str) -> Option<Family> {
2037 use Family::*;
2038 let m = match mic {
2039 "XNYS" | "NYSD" | "XCIS" | "CISD" | "XCHI" | "ARCX" | "ARCD" | "ARCO"
2041 | "XASE" | "AMXO" | "XNAS" | "XNGS" | "XNCM" | "XNMS" | "NASD" | "XNDQ"
2042 | "XBOS" | "BOSD" | "XBXO" | "XPHL" | "XPSX" | "PSXD" | "XPHO" | "XPBT"
2043 | "XPOR" | "XNFI" | "EDGA" | "EDGD" | "EDGX" | "EDDP" | "EDGO" | "BATS"
2044 | "BZXD" | "BATO" | "BATY" | "BYXD" | "MEMX" | "MEMD" | "IEXG" | "LTSE"
2045 | "MIHI" | "MPRL" | "EPRL" | "EPRD" | "XMIO" | "EMLD"
2046 | "OTCM" | "CAVE" | "OTCB" | "OTCQ" | "PINL" | "PINI" | "PINX" | "PSGM"
2048 | "PINC" | "FINR" | "FINN" | "FINC" | "FINY" | "XADF" | "FINO" | "OOTC"
2049 | "XXXX" | "PYPR" | "SIMU" => UsEquity,
2051 "XISE" | "GMNI" | "MCRY" | "XCBO" | "C2OX" | "MXOP" | "OPRA" => UsOptions,
2053 "XCME" | "FCME" | "GLBX" | "XCBT" | "FCBT" | "XKBT" => UsFuturesCme,
2055 "XNYM" => UsFuturesCmeEnergy,
2057 "CFE" => UsFuturesCfe,
2059 "ICE_US" => UsFuturesIce,
2060 "SIFMA_US" => UsBondSifma,
2061 "FOREX" => Forex24x5,
2062 "CRYPTO" => Crypto24x7,
2063 "XTSE" | "XDRK" | "VDRK" | "XTSX" | "XTNX" | "XATS" | "XATX" | "ADRK"
2065 | "XMOD" | "XMOC" | "NEOE" | "NEOD" | "NEON" | "NEOC" | "XCNQ" | "PURE"
2066 | "CSE2" => Tsx,
2067 "XLON" => Lse,
2069 "XTKS" => Tse,
2070 "XHKG" => Hkex,
2071 "XSHG" => Sse,
2072 "XEUR" | "XFRA" => Xetra,
2073 "XPAR" => EuronextParis,
2074 "XAMS" => EuronextAms,
2075 "XBRU" => EuronextBru,
2076 "XLIS" => EuronextLis,
2077 "XDUB" => EuronextDub,
2078 "XMIL" => Xmil,
2079 "XMAD" => Xmad,
2080 "XSWX" => Xswx,
2081 "XOSL" => Xosl,
2082 "XSTO" => Xsto,
2083 "XHEL" => Xhel,
2084 "XCSE" => Xcse,
2085 "XICE" => Xice,
2086 "XWAR" => Xwar,
2087 "XPRA" => Xpra,
2088 "XBUD" => Xbud,
2089 "XWBO" => Xwbo,
2090 "XASX" => Asx,
2091 "XBOM" | "XNSE" => Nse,
2092 "XKRX" => Xkrx,
2093 "XSES" => Xses,
2094 "XTAI" => Xtai,
2095 "XBKK" => Xbkk,
2096 "XKLS" => Xkls,
2097 "XIDX" => Xidx,
2098 "XPHS" => Xphs,
2099 "XNZE" => Xnze,
2100 "XJSE" => Xjse,
2101 "XSAU" => Xsau,
2102 "XIST" => Xist,
2103 "XTAE" => Xtae,
2104 "XDFM" | "XADS" => Xdfm,
2105 "BVMF" => Bvmf,
2106 "XMEX" => Xmex,
2107 "XBUE" => Xbue,
2108 "XSGO" => Xsgo,
2109 "XLIM" => Xlim,
2110 "XBOG" => Xbog,
2111 _ => return None,
2112 };
2113 Some(m)
2114}
2115
2116fn build_family(name: &str, fam: Family) -> Calendar {
2117 use Family::*;
2118 match fam {
2119 UsEquity => Calendar::with_type(
2120 name,
2121 MarketType::Equity,
2122 STANDARD_WEEKMASK,
2123 nyse_rules(),
2124 Some(nyse_trading_hours()),
2125 )
2126 .with_early_closes(nyse_early_closes()),
2127 UsOptions => Calendar::with_type(
2128 name,
2129 MarketType::Options,
2130 STANDARD_WEEKMASK,
2131 nyse_rules(),
2132 Some(options_trading_hours()),
2133 )
2134 .with_early_closes(nyse_early_closes()),
2135 UsBondSifma => Calendar::with_type(
2136 name,
2137 MarketType::Bond,
2138 STANDARD_WEEKMASK,
2139 sifma_us_rules(),
2140 Some(sifma_us_hours()),
2141 ),
2142 UsFuturesCme => Calendar::with_type(
2143 name,
2144 MarketType::Futures,
2145 STANDARD_WEEKMASK,
2146 cme_globex_rules(),
2147 Some(cme_globex_overnight_hours()),
2148 ),
2149 UsFuturesCmeEnergy => Calendar::with_type(
2150 name,
2151 MarketType::Futures,
2152 STANDARD_WEEKMASK,
2153 cme_globex_rules(),
2154 Some(cme_globex_energy_hours()),
2155 ),
2156 UsFuturesIce => Calendar::with_type(
2157 name,
2158 MarketType::Futures,
2159 STANDARD_WEEKMASK,
2160 ice_us_rules(),
2161 Some(ice_us_hours()),
2162 ),
2163 UsFuturesCfe => Calendar::with_type(
2164 name,
2165 MarketType::Futures,
2166 STANDARD_WEEKMASK,
2167 cfe_rules(),
2168 Some(cfe_trading_hours()),
2169 ),
2170 Forex24x5 => Calendar::with_type(
2171 name,
2172 MarketType::Fx,
2173 STANDARD_WEEKMASK,
2174 forex_rules(),
2175 Some(TradingHours::forex_24x5()),
2176 ),
2177 Crypto24x7 => Calendar::with_type(
2178 name,
2179 MarketType::Crypto,
2180 CRYPTO_WEEKMASK,
2181 crypto_rules(),
2182 Some(TradingHours::crypto_24x7()),
2183 ),
2184 Lse => Calendar::with_type(
2185 name,
2186 MarketType::Equity,
2187 STANDARD_WEEKMASK,
2188 lse_rules(),
2189 Some(lse_trading_hours()),
2190 ),
2191 Tse => Calendar::with_type(
2192 name,
2193 MarketType::Equity,
2194 STANDARD_WEEKMASK,
2195 tse_rules(),
2196 Some(tse_trading_hours()),
2197 ),
2198 Hkex => Calendar::with_type(
2199 name,
2200 MarketType::Equity,
2201 STANDARD_WEEKMASK,
2202 hkex_rules(),
2203 Some(hkex_trading_hours()),
2204 ),
2205 Sse => Calendar::with_type(
2206 name,
2207 MarketType::Equity,
2208 STANDARD_WEEKMASK,
2209 sse_rules(),
2210 Some(sse_trading_hours()),
2211 ),
2212 Xetra => Calendar::with_type(
2213 name,
2214 MarketType::Equity,
2215 STANDARD_WEEKMASK,
2216 xetra_rules(),
2217 Some(xetra_trading_hours()),
2218 ),
2219 EuronextParis => Calendar::with_type(
2220 name,
2221 MarketType::Equity,
2222 STANDARD_WEEKMASK,
2223 euronext_paris_rules(),
2224 Some(euronext_paris_trading_hours()),
2225 ),
2226 EuronextAms => Calendar::with_type(
2227 name,
2228 MarketType::Equity,
2229 STANDARD_WEEKMASK,
2230 xams_rules(),
2231 Some(euronext_hours(chrono_tz::Europe::Amsterdam)),
2232 ),
2233 EuronextBru => Calendar::with_type(
2234 name,
2235 MarketType::Equity,
2236 STANDARD_WEEKMASK,
2237 xbru_rules(),
2238 Some(euronext_hours(chrono_tz::Europe::Brussels)),
2239 ),
2240 EuronextLis => Calendar::with_type(
2241 name,
2242 MarketType::Equity,
2243 STANDARD_WEEKMASK,
2244 xlis_rules(),
2245 Some(euronext_hours(chrono_tz::Europe::Lisbon)),
2246 ),
2247 EuronextDub => Calendar::with_type(
2248 name,
2249 MarketType::Equity,
2250 STANDARD_WEEKMASK,
2251 xdub_rules(),
2252 Some(xdub_hours()),
2253 ),
2254 Tsx => Calendar::with_type(
2255 name,
2256 MarketType::Equity,
2257 STANDARD_WEEKMASK,
2258 tsx_rules(),
2259 Some(tsx_trading_hours()),
2260 ),
2261 Asx => Calendar::with_type(
2262 name,
2263 MarketType::Equity,
2264 STANDARD_WEEKMASK,
2265 asx_rules(),
2266 Some(asx_trading_hours()),
2267 ),
2268 Nse => Calendar::with_type(
2269 name,
2270 MarketType::Equity,
2271 STANDARD_WEEKMASK,
2272 nse_rules(),
2273 Some(nse_trading_hours()),
2274 ),
2275 Xmil => Calendar::with_type(
2276 name,
2277 MarketType::Equity,
2278 STANDARD_WEEKMASK,
2279 xmil_rules(),
2280 Some(xmil_hours()),
2281 ),
2282 Xmad => Calendar::with_type(
2283 name,
2284 MarketType::Equity,
2285 STANDARD_WEEKMASK,
2286 xmad_rules(),
2287 Some(xmad_hours()),
2288 ),
2289 Xswx => Calendar::with_type(
2290 name,
2291 MarketType::Equity,
2292 STANDARD_WEEKMASK,
2293 xswx_rules(),
2294 Some(xswx_hours()),
2295 ),
2296 Xosl => Calendar::with_type(
2297 name,
2298 MarketType::Equity,
2299 STANDARD_WEEKMASK,
2300 xosl_rules(),
2301 Some(xosl_hours()),
2302 ),
2303 Xsto => Calendar::with_type(
2304 name,
2305 MarketType::Equity,
2306 STANDARD_WEEKMASK,
2307 xsto_rules(),
2308 Some(xsto_hours()),
2309 ),
2310 Xhel => Calendar::with_type(
2311 name,
2312 MarketType::Equity,
2313 STANDARD_WEEKMASK,
2314 xhel_rules(),
2315 Some(xhel_hours()),
2316 ),
2317 Xcse => Calendar::with_type(
2318 name,
2319 MarketType::Equity,
2320 STANDARD_WEEKMASK,
2321 xcse_rules(),
2322 Some(xcse_hours()),
2323 ),
2324 Xice => Calendar::with_type(
2325 name,
2326 MarketType::Equity,
2327 STANDARD_WEEKMASK,
2328 xice_rules(),
2329 Some(xice_hours()),
2330 ),
2331 Xwar => Calendar::with_type(
2332 name,
2333 MarketType::Equity,
2334 STANDARD_WEEKMASK,
2335 xwar_rules(),
2336 Some(xwar_hours()),
2337 ),
2338 Xpra => Calendar::with_type(
2339 name,
2340 MarketType::Equity,
2341 STANDARD_WEEKMASK,
2342 xpra_rules(),
2343 Some(xpra_hours()),
2344 ),
2345 Xbud => Calendar::with_type(
2346 name,
2347 MarketType::Equity,
2348 STANDARD_WEEKMASK,
2349 xbud_rules(),
2350 Some(xbud_hours()),
2351 ),
2352 Xwbo => Calendar::with_type(
2353 name,
2354 MarketType::Equity,
2355 STANDARD_WEEKMASK,
2356 xwbo_rules(),
2357 Some(xwbo_hours()),
2358 ),
2359 Xkrx => Calendar::with_type(
2360 name,
2361 MarketType::Equity,
2362 STANDARD_WEEKMASK,
2363 xkrx_rules(),
2364 Some(xkrx_hours()),
2365 ),
2366 Xses => Calendar::with_type(
2367 name,
2368 MarketType::Equity,
2369 STANDARD_WEEKMASK,
2370 xses_rules(),
2371 Some(xses_hours()),
2372 ),
2373 Xtai => Calendar::with_type(
2374 name,
2375 MarketType::Equity,
2376 STANDARD_WEEKMASK,
2377 xtai_rules(),
2378 Some(xtai_hours()),
2379 ),
2380 Xbkk => Calendar::with_type(
2381 name,
2382 MarketType::Equity,
2383 STANDARD_WEEKMASK,
2384 xbkk_rules(),
2385 Some(xbkk_hours()),
2386 ),
2387 Xkls => Calendar::with_type(
2388 name,
2389 MarketType::Equity,
2390 STANDARD_WEEKMASK,
2391 xkls_rules(),
2392 Some(xkls_hours()),
2393 ),
2394 Xidx => Calendar::with_type(
2395 name,
2396 MarketType::Equity,
2397 STANDARD_WEEKMASK,
2398 xidx_rules(),
2399 Some(xidx_hours()),
2400 ),
2401 Xphs => Calendar::with_type(
2402 name,
2403 MarketType::Equity,
2404 STANDARD_WEEKMASK,
2405 xphs_rules(),
2406 Some(xphs_hours()),
2407 ),
2408 Xnze => Calendar::with_type(
2409 name,
2410 MarketType::Equity,
2411 STANDARD_WEEKMASK,
2412 xnze_rules(),
2413 Some(xnze_hours()),
2414 ),
2415 Xjse => Calendar::with_type(
2416 name,
2417 MarketType::Equity,
2418 STANDARD_WEEKMASK,
2419 xjse_rules(),
2420 Some(xjse_hours()),
2421 ),
2422 Xsau => Calendar::with_type(
2423 name,
2424 MarketType::Equity,
2425 MIDEAST_WEEKMASK,
2426 xsau_rules(),
2427 Some(xsau_hours()),
2428 ),
2429 Xist => Calendar::with_type(
2430 name,
2431 MarketType::Equity,
2432 STANDARD_WEEKMASK,
2433 xist_rules(),
2434 Some(xist_hours()),
2435 ),
2436 Xtae => Calendar::with_type(
2437 name,
2438 MarketType::Equity,
2439 TASE_WEEKMASK,
2440 xtae_rules(),
2441 Some(xtae_hours()),
2442 ),
2443 Xdfm => Calendar::with_type(
2444 name,
2445 MarketType::Equity,
2446 STANDARD_WEEKMASK,
2447 xdfm_rules(),
2448 Some(xdfm_hours()),
2449 ),
2450 Bvmf => Calendar::with_type(
2451 name,
2452 MarketType::Equity,
2453 STANDARD_WEEKMASK,
2454 bvmf_rules(),
2455 Some(bvmf_hours()),
2456 ),
2457 Xmex => Calendar::with_type(
2458 name,
2459 MarketType::Equity,
2460 STANDARD_WEEKMASK,
2461 xmex_rules(),
2462 Some(xmex_hours()),
2463 ),
2464 Xbue => Calendar::with_type(
2465 name,
2466 MarketType::Equity,
2467 STANDARD_WEEKMASK,
2468 xbue_rules(),
2469 Some(xbue_hours()),
2470 ),
2471 Xsgo => Calendar::with_type(
2472 name,
2473 MarketType::Equity,
2474 STANDARD_WEEKMASK,
2475 xsgo_rules(),
2476 Some(xsgo_hours()),
2477 ),
2478 Xlim => Calendar::with_type(
2479 name,
2480 MarketType::Equity,
2481 STANDARD_WEEKMASK,
2482 xlim_rules(),
2483 Some(xlim_hours()),
2484 ),
2485 Xbog => Calendar::with_type(
2486 name,
2487 MarketType::Equity,
2488 STANDARD_WEEKMASK,
2489 xbog_rules(),
2490 Some(xbog_hours()),
2491 ),
2492 }
2493}
2494
2495pub fn calendar_for_exchange(code: &str) -> Option<Calendar> {
2498 let upper = code.to_ascii_uppercase();
2499 let fam = family_for_mic(&upper)?;
2500 Some(build_family(&upper, fam))
2501}
2502
2503pub fn calendar_for_region(code: &str) -> Option<Calendar> {
2505 match code.to_ascii_uppercase().as_str() {
2506 "US" => calendar_for_exchange("XNYS"),
2507 "UK" | "GB" => calendar_for_exchange("XLON"),
2508 "JP" => calendar_for_exchange("XTKS"),
2509 "HK" => calendar_for_exchange("XHKG"),
2510 "CN" => calendar_for_exchange("XSHG"),
2511 "DE" | "EU" => calendar_for_exchange("XFRA"),
2512 "FR" => calendar_for_exchange("XPAR"),
2513 "CA" => calendar_for_exchange("XTSE"),
2514 "AU" => calendar_for_exchange("XASX"),
2515 "IN" => calendar_for_exchange("XNSE"),
2516 "NL" => calendar_for_exchange("XAMS"),
2517 "BE" => calendar_for_exchange("XBRU"),
2518 "PT" => calendar_for_exchange("XLIS"),
2519 "IT" => calendar_for_exchange("XMIL"),
2520 "ES" => calendar_for_exchange("XMAD"),
2521 "CH" => calendar_for_exchange("XSWX"),
2522 "NO" => calendar_for_exchange("XOSL"),
2523 "SE" => calendar_for_exchange("XSTO"),
2524 "FI" => calendar_for_exchange("XHEL"),
2525 "DK" => calendar_for_exchange("XCSE"),
2526 "IS" => calendar_for_exchange("XICE"),
2527 "PL" => calendar_for_exchange("XWAR"),
2528 "CZ" => calendar_for_exchange("XPRA"),
2529 "HU" => calendar_for_exchange("XBUD"),
2530 "AT" => calendar_for_exchange("XWBO"),
2531 "IE" => calendar_for_exchange("XDUB"),
2532 "KR" => calendar_for_exchange("XKRX"),
2533 "SG" => calendar_for_exchange("XSES"),
2534 "TW" => calendar_for_exchange("XTAI"),
2535 "TH" => calendar_for_exchange("XBKK"),
2536 "MY" => calendar_for_exchange("XKLS"),
2537 "ID" => calendar_for_exchange("XIDX"),
2538 "PH" => calendar_for_exchange("XPHS"),
2539 "NZ" => calendar_for_exchange("XNZE"),
2540 "ZA" => calendar_for_exchange("XJSE"),
2541 "SA" => calendar_for_exchange("XSAU"),
2542 "TR" => calendar_for_exchange("XIST"),
2543 "IL" => calendar_for_exchange("XTAE"),
2544 "AE" => calendar_for_exchange("XDFM"),
2545 "BR" => calendar_for_exchange("BVMF"),
2546 "MX" => calendar_for_exchange("XMEX"),
2547 "AR" => calendar_for_exchange("XBUE"),
2548 "CL" => calendar_for_exchange("XSGO"),
2549 "PE" => calendar_for_exchange("XLIM"),
2550 "CO" => calendar_for_exchange("XBOG"),
2551 _ => None,
2552 }
2553}
2554
2555#[cfg(test)]
2556mod tests {
2557 use super::*;
2558 use chrono::TimeZone;
2559 use chrono::Timelike;
2560
2561 #[test]
2562 fn nyse_2024_business_day_count() {
2563 let cal = calendar_for_exchange("XNYS").unwrap();
2564 let n = cal.business_days_between(
2565 NaiveDate::from_ymd_opt(2024, 1, 1).unwrap(),
2566 NaiveDate::from_ymd_opt(2024, 12, 31).unwrap(),
2567 );
2568 assert_eq!(n, 252);
2569 }
2570
2571 #[test]
2572 fn nyse_christmas_2022_observed_monday() {
2573 let cal = calendar_for_exchange("XNYS").unwrap();
2574 assert!(cal.is_holiday(NaiveDate::from_ymd_opt(2022, 12, 26).unwrap()));
2575 }
2576
2577 #[test]
2578 fn nyse_juneteenth_first_year_2021() {
2579 let cal = calendar_for_exchange("XNYS").unwrap();
2580 assert!(!cal.is_holiday(NaiveDate::from_ymd_opt(2020, 6, 19).unwrap()));
2581 assert!(cal.is_holiday(NaiveDate::from_ymd_opt(2021, 6, 18).unwrap()));
2582 }
2583
2584 #[test]
2585 fn lse_easter_monday_2024() {
2586 let cal = calendar_for_exchange("XLON").unwrap();
2587 assert!(cal.is_holiday(NaiveDate::from_ymd_opt(2024, 4, 1).unwrap()));
2588 }
2589
2590 #[test]
2591 fn region_us_resolves_to_xnys() {
2592 let cal = calendar_for_region("US").unwrap();
2593 assert_eq!(cal.name, "XNYS");
2594 }
2595
2596 #[test]
2597 fn nyse_is_open_at_market_open() {
2598 let cal = calendar_for_exchange("XNYS").unwrap();
2599 let inst = chrono_tz::America::New_York
2600 .with_ymd_and_hms(2024, 1, 8, 9, 30, 0)
2601 .unwrap()
2602 .with_timezone(&Utc);
2603 assert!(cal.is_open(inst));
2604 let inst_b = chrono_tz::America::New_York
2605 .with_ymd_and_hms(2024, 1, 8, 9, 27, 0)
2606 .unwrap()
2607 .with_timezone(&Utc);
2608 assert!(!cal.is_open(inst_b));
2609 }
2610
2611 #[test]
2612 fn nyse_is_open_handles_dst() {
2613 let cal = calendar_for_exchange("XNYS").unwrap();
2614 let inst = chrono_tz::America::New_York
2615 .with_ymd_and_hms(2024, 3, 11, 9, 30, 0)
2616 .unwrap()
2617 .with_timezone(&Utc);
2618 assert!(cal.is_open(inst));
2619 }
2620
2621 #[test]
2622 fn cme_futures_open_sunday_evening() {
2623 let cal = calendar_for_exchange("XCME").unwrap();
2625 assert_eq!(cal.market_type, MarketType::Futures);
2626 let inst = chrono_tz::America::Chicago
2627 .with_ymd_and_hms(2024, 1, 7, 18, 0, 0)
2628 .unwrap()
2629 .with_timezone(&Utc);
2630 assert!(cal.is_open(inst));
2631 let inst2 = chrono_tz::America::Chicago
2633 .with_ymd_and_hms(2024, 1, 13, 3, 0, 0)
2634 .unwrap()
2635 .with_timezone(&Utc);
2636 assert!(!cal.is_open(inst2));
2637 }
2638
2639 #[test]
2640 fn nymex_energy_uses_chicago_tz() {
2641 let cal = calendar_for_exchange("XNYM").unwrap();
2642 assert_eq!(cal.market_type, MarketType::Futures);
2643 let inst = chrono_tz::America::Chicago
2645 .with_ymd_and_hms(2024, 1, 8, 9, 0, 0)
2646 .unwrap()
2647 .with_timezone(&Utc);
2648 assert!(cal.is_open(inst));
2649 }
2650
2651 #[test]
2652 fn cfe_classifies_as_futures() {
2653 let cal = calendar_for_exchange("CFE").unwrap();
2654 assert_eq!(cal.market_type, MarketType::Futures);
2655 let inst = chrono_tz::America::Chicago
2657 .with_ymd_and_hms(2024, 1, 10, 9, 0, 0)
2658 .unwrap()
2659 .with_timezone(&Utc);
2660 assert!(cal.is_open(inst));
2661 }
2662
2663 #[test]
2664 fn forex_open_tuesday_3am() {
2665 let cal = calendar_for_exchange("FOREX").unwrap();
2666 assert_eq!(cal.market_type, MarketType::Fx);
2667 let inst = chrono_tz::America::New_York
2668 .with_ymd_and_hms(2024, 1, 9, 3, 0, 0)
2669 .unwrap()
2670 .with_timezone(&Utc);
2671 assert!(cal.is_open(inst));
2672 }
2673
2674 #[test]
2675 fn crypto_open_saturday_3am() {
2676 let cal = calendar_for_exchange("CRYPTO").unwrap();
2677 assert_eq!(cal.market_type, MarketType::Crypto);
2678 let inst = chrono_tz::UTC
2679 .with_ymd_and_hms(2024, 1, 13, 3, 0, 0)
2680 .unwrap()
2681 .with_timezone(&Utc);
2682 assert!(cal.is_open(inst));
2683 }
2684
2685 #[test]
2686 fn options_close_at_1615() {
2687 let cal = calendar_for_exchange("OPRA").unwrap();
2688 assert_eq!(cal.market_type, MarketType::Options);
2689 let inst = chrono_tz::America::New_York
2690 .with_ymd_and_hms(2024, 1, 8, 16, 10, 0)
2691 .unwrap()
2692 .with_timezone(&Utc);
2693 assert!(cal.is_open(inst));
2694 }
2695
2696 #[test]
2697 fn sifma_includes_columbus_and_veterans() {
2698 let cal = calendar_for_exchange("SIFMA_US").unwrap();
2699 assert_eq!(cal.market_type, MarketType::Bond);
2700 assert!(cal.is_holiday(NaiveDate::from_ymd_opt(2024, 11, 11).unwrap()));
2702 assert!(cal.is_holiday(NaiveDate::from_ymd_opt(2024, 10, 14).unwrap()));
2704 }
2705
2706 #[test]
2707 fn ice_us_uses_overnight_session() {
2708 let cal = calendar_for_exchange("ICE_US").unwrap();
2709 let inst = chrono_tz::America::New_York
2711 .with_ymd_and_hms(2024, 1, 7, 21, 0, 0)
2712 .unwrap()
2713 .with_timezone(&Utc);
2714 assert!(cal.is_open(inst));
2715 }
2716
2717 #[test]
2718 fn all_exchange_codes_resolve() {
2719 for code in EXCHANGE_CODES {
2720 assert!(
2721 calendar_for_exchange(code).is_some(),
2722 "MIC {} did not resolve",
2723 code
2724 );
2725 }
2726 }
2727
2728 #[test]
2729 fn exchange_codes_are_sourced_from_finance_enums() {
2730 assert_eq!(EXCHANGE_CODES, finance_enums::data::ExchangeCode_VARIANTS);
2731 assert!(std::ptr::eq(
2732 EXCHANGE_CODES.as_ptr(),
2733 finance_enums::data::ExchangeCode_VARIANTS.as_ptr()
2734 ));
2735 }
2736
2737 #[test]
2738 fn otc_inherits_nyse_holidays() {
2739 let cal = calendar_for_exchange("PINX").unwrap();
2740 assert_eq!(cal.market_type, MarketType::Equity);
2741 assert!(cal.is_holiday(NaiveDate::from_ymd_opt(2024, 7, 4).unwrap()));
2742 }
2743
2744 #[test]
2745 fn canadian_calendar_for_neoe() {
2746 let cal = calendar_for_exchange("NEOE").unwrap();
2747 assert!(cal.is_holiday(NaiveDate::from_ymd_opt(2024, 7, 1).unwrap()));
2749 }
2750
2751 #[test]
2752 fn nyse_july3_2024_is_early_close() {
2753 let cal = calendar_for_exchange("XNYS").unwrap();
2755 let day = NaiveDate::from_ymd_opt(2024, 7, 3).unwrap();
2756 assert_eq!(
2757 cal.early_close_for(day),
2758 Some(NaiveTime::from_hms_opt(13, 0, 0).unwrap())
2759 );
2760 let inst = chrono_tz::America::New_York
2762 .with_ymd_and_hms(2024, 7, 3, 14, 0, 0)
2763 .unwrap()
2764 .with_timezone(&Utc);
2765 assert!(!cal.is_open(inst));
2766 let inst2 = chrono_tz::America::New_York
2768 .with_ymd_and_hms(2024, 7, 3, 12, 30, 0)
2769 .unwrap()
2770 .with_timezone(&Utc);
2771 assert!(cal.is_open(inst2));
2772 }
2773
2774 #[test]
2775 fn nyse_black_friday_2024_early_close() {
2776 let cal = calendar_for_exchange("XNYS").unwrap();
2778 assert_eq!(
2779 cal.early_close_for(NaiveDate::from_ymd_opt(2024, 11, 29).unwrap()),
2780 Some(NaiveTime::from_hms_opt(13, 0, 0).unwrap())
2781 );
2782 }
2783
2784 #[test]
2785 fn xams_kingsday_2024() {
2786 let cal = calendar_for_exchange("XAMS").unwrap();
2789 assert!(cal.is_holiday(NaiveDate::from_ymd_opt(2023, 4, 27).unwrap()));
2790 }
2791
2792 #[test]
2793 fn xkrx_seollal_2024_multi_day() {
2794 let cal = calendar_for_exchange("XKRX").unwrap();
2796 assert!(cal.is_holiday(NaiveDate::from_ymd_opt(2024, 2, 9).unwrap()));
2797 assert!(cal.is_holiday(NaiveDate::from_ymd_opt(2024, 2, 12).unwrap()));
2798 }
2799
2800 #[test]
2801 fn xtae_uses_sun_thu_weekmask() {
2802 let cal = calendar_for_exchange("XTAE").unwrap();
2803 assert!(cal.is_business_day(NaiveDate::from_ymd_opt(2024, 5, 5).unwrap()));
2805 assert!(!cal.is_business_day(NaiveDate::from_ymd_opt(2024, 5, 3).unwrap()));
2807 }
2808
2809 #[test]
2810 fn xsau_uses_sun_thu_weekmask() {
2811 let cal = calendar_for_exchange("XSAU").unwrap();
2812 assert!(cal.is_business_day(NaiveDate::from_ymd_opt(2024, 5, 5).unwrap()));
2813 assert!(!cal.is_business_day(NaiveDate::from_ymd_opt(2024, 5, 3).unwrap()));
2814 }
2815
2816 #[test]
2817 fn bvmf_carnival_2024() {
2818 let cal = calendar_for_exchange("BVMF").unwrap();
2820 assert!(cal.is_holiday(NaiveDate::from_ymd_opt(2024, 2, 12).unwrap()));
2821 assert!(cal.is_holiday(NaiveDate::from_ymd_opt(2024, 2, 13).unwrap()));
2822 }
2823
2824 #[test]
2825 fn region_br_resolves_to_bvmf() {
2826 let cal = calendar_for_region("BR").unwrap();
2827 assert_eq!(cal.name, "BVMF");
2828 }
2829
2830 #[test]
2831 fn xnze_waitangi_2024() {
2832 let cal = calendar_for_exchange("XNZE").unwrap();
2833 assert!(cal.is_holiday(NaiveDate::from_ymd_opt(2024, 2, 6).unwrap()));
2835 }
2836
2837 #[test]
2838 fn nyse_sessions_between_one_week_with_early_close() {
2839 let cal = calendar_for_exchange("XNYS").unwrap();
2840 let s = cal.sessions_between(
2842 NaiveDate::from_ymd_opt(2024, 7, 1).unwrap(),
2843 NaiveDate::from_ymd_opt(2024, 7, 5).unwrap(),
2844 );
2845 assert_eq!(s.len(), 4);
2846 let jul3_close_local = s[2].1.with_timezone(&chrono_tz::America::New_York);
2848 assert_eq!(jul3_close_local.hour(), 13);
2849 assert_eq!(jul3_close_local.minute(), 0);
2850 let jul5_close_local = s[3].1.with_timezone(&chrono_tz::America::New_York);
2852 assert_eq!(jul5_close_local.hour(), 16);
2853 }
2854
2855 #[test]
2856 fn nyse_holidays_between_q3_2024() {
2857 let cal = calendar_for_exchange("XNYS").unwrap();
2858 let h = cal.holidays_between(
2859 NaiveDate::from_ymd_opt(2024, 7, 1).unwrap(),
2860 NaiveDate::from_ymd_opt(2024, 9, 30).unwrap(),
2861 );
2862 assert!(h.contains(&NaiveDate::from_ymd_opt(2024, 7, 4).unwrap()));
2864 assert!(h.contains(&NaiveDate::from_ymd_opt(2024, 9, 2).unwrap()));
2865 assert_eq!(h.len(), 2);
2866 }
2867}