1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
//! North Macedonia calendars.
//!
//! Port of `ql/time/calendars/northmacedonia.{hpp,cpp}`.
use crate::shared::shared;
use crate::time::calendar::{Calendar, CalendarImpl, is_weekend_sat_sun, orthodox_easter_monday};
use crate::time::calendars::islamicholidays::moon_sighting::{is_eid_al_adha, is_eid_al_fitr};
use crate::time::date::{Date, Month, Year};
use crate::time::weekday::Weekday;
/// Last year for which North Macedonia's Islamic (Eid) holidays are tabulated
/// via `moon_sighting` (matching QuantLib's data). Past this year the calendar
/// falls through to its weekend and recurring rules, exactly like QuantLib
/// (which never checks the year).
pub const HOLIDAY_HORIZON: Year = 2040;
/// Market handled by the North Macedonia calendar.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Market {
/// Macedonian Stock Exchange.
Mse,
}
/// The North Macedonia calendar. The default market is [`Market::Mse`].
///
/// # Accuracy
///
/// North Macedonia's Islamic (Eid) holidays are tabulated (from QuantLib, via
/// the `moon_sighting` tables) only through 2040. After 2040 the tabulated
/// holidays are not observed and the calendar falls through to its weekend and
/// recurring rules, exactly like QuantLib (which never checks the year).
pub struct NorthMacedonia;
impl NorthMacedonia {
/// Builds a North Macedonia calendar for the given `market`.
pub fn new(market: Market) -> Calendar {
let imp: crate::shared::Shared<dyn CalendarImpl> = match market {
Market::Mse => shared(MseImpl),
};
Calendar::from_impl(imp)
}
}
struct MseImpl;
impl CalendarImpl for MseImpl {
fn holiday_horizon(&self) -> Option<Year> {
Some(HOLIDAY_HORIZON)
}
fn name(&self) -> String {
"Macedonian Stock Exchange".to_string()
}
fn is_weekend(&self, w: Weekday) -> bool {
is_weekend_sat_sun(w)
}
fn is_business_day(&self, date: Date) -> bool {
let w = date.weekday();
let d = date.day_of_month();
let dd = date.day_of_year();
let m = date.month();
let y = date.year();
let em = orthodox_easter_monday(y);
!(is_weekend_sat_sun(w)
|| is_eid_al_fitr(date)
|| is_eid_al_adha(date)
// New Year
|| (d == 1 && m == Month::January)
// Orthodox Christmas
|| (d == 7 && m == Month::January)
// Easter Monday
|| (dd == em)
// Labour Day
|| (d == 1 && m == Month::May)
// Saints Cyril and Methodius Day
|| (d == 24 && m == Month::May)
// Republic Day
|| (d == 2 && m == Month::August)
// Independence Day
|| (d == 8 && m == Month::September)
// Day of People's Uprising
|| (d == 11 && m == Month::October)
// Day of the Macedonian Revolutionary Struggle
|| (d == 23 && m == Month::October)
// Saint Clement of Ohrid Day,
|| (d == 8 && m == Month::December))
}
}
#[cfg(test)]
mod tests {
use super::*;
// Spot-checks, not a full transcription of test-suite/calendars.cpp.
#[test]
fn name_matches_quantlib() {
assert_eq!(
NorthMacedonia::new(Market::Mse).name(),
"Macedonian Stock Exchange"
);
}
#[test]
fn fixed_holidays() {
let c = NorthMacedonia::new(Market::Mse);
for (d, m) in [
(1, Month::January), // New Year
(7, Month::January), // Orthodox Christmas
(1, Month::May), // Labour Day
(24, Month::May), // Saints Cyril and Methodius Day
(2, Month::August), // Republic Day
(8, Month::September), // Independence Day
(11, Month::October), // Day of People's Uprising
(23, Month::October), // Day of the Macedonian Revolutionary Struggle
(8, Month::December), // Saint Clement of Ohrid Day
] {
assert!(c.is_holiday(Date::new(d, m, 2020)), "{d} {m} 2020");
}
}
#[test]
fn weekend_rule() {
let c = NorthMacedonia::new(Market::Mse);
assert!(c.is_weekend(Weekday::Saturday));
assert!(c.is_weekend(Weekday::Sunday));
assert!(!c.is_weekend(Weekday::Friday));
}
#[test]
fn in_horizon_query_works() {
// A query at the last tabulated year must not panic. New Year is
// unconditional.
let c = NorthMacedonia::new(Market::Mse);
assert!(c.is_holiday(Date::new(1, Month::January, HOLIDAY_HORIZON)));
}
#[test]
fn beyond_horizon_falls_through() {
// QuantLib never checks the year: past the tables only weekends and
// recurring rules apply, and tabulated dates do not recur.
let c = NorthMacedonia::new(Market::Mse);
assert!(c.is_holiday(Date::new(7, Month::January, 2041)));
assert!(c.is_business_day(Date::new(8, Month::January, 2041)));
}
}