From 80a2e2ff4306a5d5e8c4317162652dfacaaa8803 Mon Sep 17 00:00:00 2001 From: nadav7679 Date: Tue, 12 Nov 2024 14:25:18 +0000 Subject: [PATCH 1/5] Basic support for Israel (Jewish) Calander --- crates/RustQuant_time/Cargo.toml | 3 +- .../src/countries/middle_east/israel.rs | 155 ++++++++++++++++++ crates/RustQuant_time/src/countries/mod.rs | 6 + 3 files changed, 163 insertions(+), 1 deletion(-) create mode 100644 crates/RustQuant_time/src/countries/middle_east/israel.rs diff --git a/crates/RustQuant_time/Cargo.toml b/crates/RustQuant_time/Cargo.toml index 9461fec..18d89e1 100644 --- a/crates/RustQuant_time/Cargo.toml +++ b/crates/RustQuant_time/Cargo.toml @@ -20,7 +20,8 @@ RustQuant = { path = "../RustQuant" } [dependencies] RustQuant_iso = { workspace = true } RustQuant_utils = { workspace = true } - +reqwest = { version = "0.12.9", features = ["blocking"] } +serde.workspace = true time = { workspace = true } ## ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/crates/RustQuant_time/src/countries/middle_east/israel.rs b/crates/RustQuant_time/src/countries/middle_east/israel.rs new file mode 100644 index 0000000..6848944 --- /dev/null +++ b/crates/RustQuant_time/src/countries/middle_east/israel.rs @@ -0,0 +1,155 @@ +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// RustQuant: A Rust library for quantitative finance tools. +// Copyright (C) 2022-2024 https://github.com/avhz +// Dual licensed under Apache 2.0 and MIT. +// See: +// - LICENSE-APACHE.md +// - LICENSE-MIT.md +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// IMPORTS +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +use crate::calendar::Calendar; +use crate::utilities::unpack_date; +use serde::Deserialize; +use reqwest::{blocking::Client, Error}; +use time::{Date, Weekday}; +use RustQuant_iso::*; + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// STRUCTS, ENUMS, TRAITS +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +/// Israel a national holiday calendar. +pub struct IsraelCalendar; + + +// Two structs to capture response +#[derive(Deserialize, Debug)] +struct ResponseData { + items: Vec, +} + +#[derive(Deserialize, Debug)] +struct Item { + title: String, +} + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// IMPLEMENTATIONS, METHODS +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +fn jewish_holiday(date: &String) -> Result { + let url = "https://www.hebcal.com/hebcal"; + + let params = [("v", "1"), ("cfg", "json"), ("maj", "on"), ("start", date), ("end", date)]; + + + let response = Client::new() + .get(url) + .query(¶ms) + .send() + .map_err(|e| { + eprintln!("Failed to send request: {}", e); + e + })?; + + println!("{:?}", &response); + + let json: ResponseData = response.json().map_err(|e| { + eprintln!("Failed to parse Json response: {}", e); + e + })?; + + Ok(!json.items.is_empty()) +} + + +impl Calendar for IsraelCalendar { + fn name(&self) -> &'static str { + "Israel" + } + + fn country_code(&self) -> ISO_3166 { + ISRAEL + } + + fn market_identifier_code(&self) -> ISO_10383 { + XTAE + } + + fn is_holiday(&self, date: Date) -> bool { + let (y, m, d, wd, yd, em) = unpack_date(date, false); + let m = m as u8; + + // Jewish weekend (Friday, Saturday) + if ( wd == Weekday::Friday || wd == Weekday::Saturday) { + return true; + } + + match jewish_holiday(&format!("{:04}-{:02}-{:02}", y, m, d)) { + Ok(is_holiday) => is_holiday, + Err(e) => { + eprintln!("Error checking Jewish holiday: {}", e); + false // default to non-holiday if there's an error + } + } + } +} + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// UNIT TESTS +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +#[cfg(test)] +mod test_israel { + use super::*; + use time::macros::date; + + // Test to verify the name() method. + #[test] + fn test_name() { + let calendar = IsraelCalendar; + assert_eq!(calendar.name(), "Israel"); + } + + // Test to verify if weekends are not considered business days. + #[test] + fn test_is_weekend() { + let calendar = IsraelCalendar; + let fri = date!(2023 - 01 - 27); + let sat = date!(2023 - 01 - 28); + assert!(!calendar.is_business_day(fri)); + assert!(!calendar.is_business_day(sat)); + } + + // Test to verify if the is_business_day() method properly accounts for public holidays. + #[test] + fn test_is_public_holiday() { + let calendar = IsraelCalendar; + let purim = date!(2024 - 03 - 24); // Purim holiday 2024 + let sukkot = date!(2024 - 10 - 17); // Sukkot holiday 2024 + let passover_23 = date!(2023 - 4 - 05); // Passover eve 2023 + let passover_24 = date!(2023 - 4 - 22); // Passover eve 2024 + + assert!(!calendar.is_business_day(purim)); + assert!(!calendar.is_business_day(sukkot)); + assert!(!calendar.is_business_day(passover_23)); + assert!(!calendar.is_business_day(passover_24)); + } + + // Test to verify if the is_business_day() method properly accounts for regular business days. + #[test] + fn test_is_regular_business_day() { + let calendar = IsraelCalendar; + let regular_day1 = date!(2021 - 08 - 04); + let regular_day2 = date!(2024 - 04 - 09); + let regular_day3 = date!(2023 - 11 - 27); + + assert!(calendar.is_business_day(regular_day1)); + assert!(calendar.is_business_day(regular_day2)); + assert!(calendar.is_business_day(regular_day3)); + } +} diff --git a/crates/RustQuant_time/src/countries/mod.rs b/crates/RustQuant_time/src/countries/mod.rs index 83be635..8615816 100644 --- a/crates/RustQuant_time/src/countries/mod.rs +++ b/crates/RustQuant_time/src/countries/mod.rs @@ -80,3 +80,9 @@ pub mod south_america { /// Chile holidays and calendars. pub mod chile; } + +/// Calanders implemented for Middle Eastern countries. +pub mod middle_east { + /// Israeli (Jewish) holidays and calander, implemented with an external API. + pub mod israel; +} \ No newline at end of file From 6b20ffeb4d579e4a4831359cdbc9fdbbf63edf32 Mon Sep 17 00:00:00 2001 From: nadav7679 Date: Tue, 12 Nov 2024 14:46:39 +0000 Subject: [PATCH 2/5] Removed unused println plus formatting --- .../src/countries/middle_east/israel.rs | 27 +++++++++---------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/crates/RustQuant_time/src/countries/middle_east/israel.rs b/crates/RustQuant_time/src/countries/middle_east/israel.rs index 6848944..401baa4 100644 --- a/crates/RustQuant_time/src/countries/middle_east/israel.rs +++ b/crates/RustQuant_time/src/countries/middle_east/israel.rs @@ -13,8 +13,8 @@ use crate::calendar::Calendar; use crate::utilities::unpack_date; -use serde::Deserialize; use reqwest::{blocking::Client, Error}; +use serde::Deserialize; use time::{Date, Weekday}; use RustQuant_iso::*; @@ -25,11 +25,10 @@ use RustQuant_iso::*; /// Israel a national holiday calendar. pub struct IsraelCalendar; - // Two structs to capture response #[derive(Deserialize, Debug)] struct ResponseData { - items: Vec, + items: Vec, } #[derive(Deserialize, Debug)] @@ -43,21 +42,20 @@ struct Item { fn jewish_holiday(date: &String) -> Result { let url = "https://www.hebcal.com/hebcal"; - - let params = [("v", "1"), ("cfg", "json"), ("maj", "on"), ("start", date), ("end", date)]; + let params = [ + ("v", "1"), + ("cfg", "json"), + ("maj", "on"), + ("start", date), + ("end", date), + ]; - let response = Client::new() - .get(url) - .query(¶ms) - .send() - .map_err(|e| { + let response = Client::new().get(url).query(¶ms).send().map_err(|e| { eprintln!("Failed to send request: {}", e); e })?; - println!("{:?}", &response); - let json: ResponseData = response.json().map_err(|e| { eprintln!("Failed to parse Json response: {}", e); e @@ -66,7 +64,6 @@ fn jewish_holiday(date: &String) -> Result { Ok(!json.items.is_empty()) } - impl Calendar for IsraelCalendar { fn name(&self) -> &'static str { "Israel" @@ -83,9 +80,9 @@ impl Calendar for IsraelCalendar { fn is_holiday(&self, date: Date) -> bool { let (y, m, d, wd, yd, em) = unpack_date(date, false); let m = m as u8; - + // Jewish weekend (Friday, Saturday) - if ( wd == Weekday::Friday || wd == Weekday::Saturday) { + if (wd == Weekday::Friday || wd == Weekday::Saturday) { return true; } From ac3651dd2514d1f2bda4c170aec7064058365c52 Mon Sep 17 00:00:00 2001 From: nadav7679 Date: Wed, 13 Nov 2024 22:28:45 +0000 Subject: [PATCH 3/5] Jewish holidays update check using heca_lib --- crates/RustQuant_time/Cargo.toml | 3 +- .../src/countries/middle_east/israel.rs | 102 ++++++++++-------- 2 files changed, 59 insertions(+), 46 deletions(-) diff --git a/crates/RustQuant_time/Cargo.toml b/crates/RustQuant_time/Cargo.toml index 18d89e1..cee733d 100644 --- a/crates/RustQuant_time/Cargo.toml +++ b/crates/RustQuant_time/Cargo.toml @@ -20,7 +20,8 @@ RustQuant = { path = "../RustQuant" } [dependencies] RustQuant_iso = { workspace = true } RustQuant_utils = { workspace = true } -reqwest = { version = "0.12.9", features = ["blocking"] } +chrono = "0.4.38" +heca-lib = "1.3.2" serde.workspace = true time = { workspace = true } diff --git a/crates/RustQuant_time/src/countries/middle_east/israel.rs b/crates/RustQuant_time/src/countries/middle_east/israel.rs index 401baa4..bd362d7 100644 --- a/crates/RustQuant_time/src/countries/middle_east/israel.rs +++ b/crates/RustQuant_time/src/countries/middle_east/israel.rs @@ -11,13 +11,43 @@ // IMPORTS // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +use heca_lib::prelude::HebrewMonth; +use time::{Date, Weekday}; + +use chrono::Utc; +use chrono::offset::TimeZone; +use heca_lib::HebrewDate; + use crate::calendar::Calendar; use crate::utilities::unpack_date; -use reqwest::{blocking::Client, Error}; -use serde::Deserialize; -use time::{Date, Weekday}; use RustQuant_iso::*; +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// CONSTANTS +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +const JEWISH_HOLIDAYS: [(u8, u8); 19] = [ + (12, 29), // Jewish new year (Rosh Hashana) I + (1, 1), // Jewish new year (Rosh Hashana) II + (1, 2), // Jewish new year (Rosh Hashana) II + (1, 9), // Yom Kippur I + (1, 10), // Yom Kippur II + (1, 14), // Sukkot I + (1, 15), // Sukkot II + (1, 22), // Simchat Torah I + (1, 23), // Simchat Torah II + (6, 14), // Purim + (7, 14), // Passover I + (7, 15), // Passover II + (7, 20), // Passover two I + (7, 21), // Passover two II + (8, 5), // Memorial day + (8, 6), // Independence day + (9, 5), // Shavut I + (9, 6), // Shavut I + (11, 9), // Tisha Be'av +]; + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // STRUCTS, ENUMS, TRAITS // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -25,44 +55,10 @@ use RustQuant_iso::*; /// Israel a national holiday calendar. pub struct IsraelCalendar; -// Two structs to capture response -#[derive(Deserialize, Debug)] -struct ResponseData { - items: Vec, -} - -#[derive(Deserialize, Debug)] -struct Item { - title: String, -} - // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // IMPLEMENTATIONS, METHODS // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -fn jewish_holiday(date: &String) -> Result { - let url = "https://www.hebcal.com/hebcal"; - - let params = [ - ("v", "1"), - ("cfg", "json"), - ("maj", "on"), - ("start", date), - ("end", date), - ]; - - let response = Client::new().get(url).query(¶ms).send().map_err(|e| { - eprintln!("Failed to send request: {}", e); - e - })?; - - let json: ResponseData = response.json().map_err(|e| { - eprintln!("Failed to parse Json response: {}", e); - e - })?; - - Ok(!json.items.is_empty()) -} impl Calendar for IsraelCalendar { fn name(&self) -> &'static str { @@ -78,7 +74,7 @@ impl Calendar for IsraelCalendar { } fn is_holiday(&self, date: Date) -> bool { - let (y, m, d, wd, yd, em) = unpack_date(date, false); + let (y, m, d, wd, _, _) = unpack_date(date, false); let m = m as u8; // Jewish weekend (Friday, Saturday) @@ -86,13 +82,29 @@ impl Calendar for IsraelCalendar { return true; } - match jewish_holiday(&format!("{:04}-{:02}-{:02}", y, m, d)) { - Ok(is_holiday) => is_holiday, - Err(e) => { - eprintln!("Error checking Jewish holiday: {}", e); - false // default to non-holiday if there's an error - } - } + let hebrew_date: HebrewDate = Utc.with_ymd_and_hms(y.into(), m.into(), d.into(), 0, 0, 0).unwrap().try_into().unwrap(); + let month = match hebrew_date.month() { + HebrewMonth::Tishrei => 1, + HebrewMonth::Cheshvan => 2, + HebrewMonth::Kislev => 3, + HebrewMonth::Teves => 4, + HebrewMonth::Shvat => 5, + HebrewMonth::Adar => 6, + HebrewMonth::Adar1 => 6, + HebrewMonth::Adar2 => 100, // Adar 2 is a leap-year month and never has a holiday. 100 is an arbitrary escape value. + HebrewMonth::Nissan => 7, + HebrewMonth::Iyar => 8, + HebrewMonth::Sivan => 9, + HebrewMonth::Tammuz => 10, + HebrewMonth::Av => 11, + HebrewMonth::Elul => 12, + }; + + let date_tuple: (u8, u8) = (month, hebrew_date.day().get() as u8); + println!("{:?}", hebrew_date); + println!("{:?}", date_tuple); + + JEWISH_HOLIDAYS.contains(&date_tuple) } } From 83017e1816a89adde7970820ec0687da8a6722bc Mon Sep 17 00:00:00 2001 From: nadav7679 Date: Wed, 13 Nov 2024 22:38:08 +0000 Subject: [PATCH 4/5] Added tests and fixed memorial day --- .../src/countries/middle_east/israel.rs | 25 ++++++++++--------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/crates/RustQuant_time/src/countries/middle_east/israel.rs b/crates/RustQuant_time/src/countries/middle_east/israel.rs index bd362d7..a174198 100644 --- a/crates/RustQuant_time/src/countries/middle_east/israel.rs +++ b/crates/RustQuant_time/src/countries/middle_east/israel.rs @@ -27,10 +27,10 @@ use RustQuant_iso::*; // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ const JEWISH_HOLIDAYS: [(u8, u8); 19] = [ - (12, 29), // Jewish new year (Rosh Hashana) I + (12, 29), // Jewish new year (Rosh Hashana) I (1, 1), // Jewish new year (Rosh Hashana) II (1, 2), // Jewish new year (Rosh Hashana) II - (1, 9), // Yom Kippur I + (1, 9), // Yom Kippur I (1, 10), // Yom Kippur II (1, 14), // Sukkot I (1, 15), // Sukkot II @@ -41,8 +41,8 @@ const JEWISH_HOLIDAYS: [(u8, u8); 19] = [ (7, 15), // Passover II (7, 20), // Passover two I (7, 21), // Passover two II - (8, 5), // Memorial day - (8, 6), // Independence day + (8, 4), // Memorial day + (8, 5), // Independence day (9, 5), // Shavut I (9, 6), // Shavut I (11, 9), // Tisha Be'av @@ -91,7 +91,7 @@ impl Calendar for IsraelCalendar { HebrewMonth::Shvat => 5, HebrewMonth::Adar => 6, HebrewMonth::Adar1 => 6, - HebrewMonth::Adar2 => 100, // Adar 2 is a leap-year month and never has a holiday. 100 is an arbitrary escape value. + HebrewMonth::Adar2 => 100, // Adar2 is a leap-year month and never has a holiday. 100 is an arbitrary escape value. HebrewMonth::Nissan => 7, HebrewMonth::Iyar => 8, HebrewMonth::Sivan => 9, @@ -100,11 +100,7 @@ impl Calendar for IsraelCalendar { HebrewMonth::Elul => 12, }; - let date_tuple: (u8, u8) = (month, hebrew_date.day().get() as u8); - println!("{:?}", hebrew_date); - println!("{:?}", date_tuple); - - JEWISH_HOLIDAYS.contains(&date_tuple) + JEWISH_HOLIDAYS.contains(&(month, hebrew_date.day().get() as u8)) } } @@ -138,15 +134,20 @@ mod test_israel { #[test] fn test_is_public_holiday() { let calendar = IsraelCalendar; - let purim = date!(2024 - 03 - 24); // Purim holiday 2024 - let sukkot = date!(2024 - 10 - 17); // Sukkot holiday 2024 + let purim = date!(2024 - 03 - 24); // Purim holiday 2024 + let sukkot = date!(2024 - 10 - 17); // Sukkot holiday 2024 let passover_23 = date!(2023 - 4 - 05); // Passover eve 2023 let passover_24 = date!(2023 - 4 - 22); // Passover eve 2024 + let shavuot_26 = date!(2026 - 5 - 22); // Shavuot 2026 + let memorial_26 = date!(2026 - 4 - 21); // Memorial day 2026 assert!(!calendar.is_business_day(purim)); assert!(!calendar.is_business_day(sukkot)); assert!(!calendar.is_business_day(passover_23)); assert!(!calendar.is_business_day(passover_24)); + assert!(!calendar.is_business_day(shavuot_26)); + assert!(!calendar.is_business_day(memorial_26)); + } // Test to verify if the is_business_day() method properly accounts for regular business days. From c3e5f92fc74e38fad6b34d230c6a7234873e0185 Mon Sep 17 00:00:00 2001 From: nadav7679 Date: Tue, 19 Nov 2024 19:14:37 +0000 Subject: [PATCH 5/5] Removed chrono and added icu, updated independence/memorial/tisha-beav holiday rules --- crates/RustQuant_time/Cargo.toml | 3 +- .../src/countries/middle_east/israel.rs | 177 ++++++++++++------ 2 files changed, 120 insertions(+), 60 deletions(-) diff --git a/crates/RustQuant_time/Cargo.toml b/crates/RustQuant_time/Cargo.toml index cee733d..5556cd1 100644 --- a/crates/RustQuant_time/Cargo.toml +++ b/crates/RustQuant_time/Cargo.toml @@ -20,8 +20,7 @@ RustQuant = { path = "../RustQuant" } [dependencies] RustQuant_iso = { workspace = true } RustQuant_utils = { workspace = true } -chrono = "0.4.38" -heca-lib = "1.3.2" +icu = "1.5.0" serde.workspace = true time = { workspace = true } diff --git a/crates/RustQuant_time/src/countries/middle_east/israel.rs b/crates/RustQuant_time/src/countries/middle_east/israel.rs index a174198..8021d1d 100644 --- a/crates/RustQuant_time/src/countries/middle_east/israel.rs +++ b/crates/RustQuant_time/src/countries/middle_east/israel.rs @@ -11,12 +11,9 @@ // IMPORTS // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -use heca_lib::prelude::HebrewMonth; use time::{Date, Weekday}; -use chrono::Utc; -use chrono::offset::TimeZone; -use heca_lib::HebrewDate; +use icu; use crate::calendar::Calendar; use crate::utilities::unpack_date; @@ -26,26 +23,23 @@ use RustQuant_iso::*; // CONSTANTS // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -const JEWISH_HOLIDAYS: [(u8, u8); 19] = [ - (12, 29), // Jewish new year (Rosh Hashana) I - (1, 1), // Jewish new year (Rosh Hashana) II - (1, 2), // Jewish new year (Rosh Hashana) II - (1, 9), // Yom Kippur I - (1, 10), // Yom Kippur II - (1, 14), // Sukkot I - (1, 15), // Sukkot II - (1, 22), // Simchat Torah I - (1, 23), // Simchat Torah II - (6, 14), // Purim - (7, 14), // Passover I - (7, 15), // Passover II - (7, 20), // Passover two I - (7, 21), // Passover two II - (8, 4), // Memorial day - (8, 5), // Independence day - (9, 5), // Shavut I - (9, 6), // Shavut I - (11, 9), // Tisha Be'av +const JEWISH_HOLIDAYS: [(u8, u8); 16] = [ + (12, 29), // Jewish new year (Rosh Hashana) I + (1, 1), // Jewish new year (Rosh Hashana) II + (1, 2), // Jewish new year (Rosh Hashana) II + (1, 9), // Yom Kippur I + (1, 10), // Yom Kippur II + (1, 14), // Sukkot I + (1, 15), // Sukkot II + (1, 22), // Simchat Torah I + (1, 23), // Simchat Torah II + (6, 14), // Purim + (7, 14), // Passover I + (7, 15), // Passover II + (7, 20), // Passover two I + (7, 21), // Passover two II + (9, 5), // Shavut I + (9, 6), // Shavut I ]; // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -59,6 +53,14 @@ pub struct IsraelCalendar; // IMPLEMENTATIONS, METHODS // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +impl IsraelCalendar { + /// Hebrew weekend is Friday and Saturday, + /// as opposed to Saturday and Sunday in the Gregorian calendar. + fn is_weekend(&self, date: Date) -> bool { + let wd = date.weekday(); + wd == Weekday::Friday || wd == Weekday::Saturday + } +} impl Calendar for IsraelCalendar { fn name(&self) -> &'static str { @@ -73,34 +75,44 @@ impl Calendar for IsraelCalendar { XTAE } + fn is_business_day(&self, date: Date) -> bool { + !self.is_weekend(date) && !self.is_holiday(date) + } + fn is_holiday(&self, date: Date) -> bool { let (y, m, d, wd, _, _) = unpack_date(date, false); let m = m as u8; + let iso_date = icu::calendar::Date::try_new_iso_date(y, m, d) + .expect("Failed to initialize ISO Date instance for constructing Hebrew date."); + + let hebrew_date = iso_date.to_calendar(icu::calendar::hebrew::Hebrew); + let mut hebrew_month = hebrew_date.month().ordinal as u8; + let hebrew_day = hebrew_date.day_of_month().0 as u8; - // Jewish weekend (Friday, Saturday) - if (wd == Weekday::Friday || wd == Weekday::Saturday) { - return true; + if hebrew_date.is_in_leap_year() && hebrew_month > 7 { + hebrew_month -= 1; } - let hebrew_date: HebrewDate = Utc.with_ymd_and_hms(y.into(), m.into(), d.into(), 0, 0, 0).unwrap().try_into().unwrap(); - let month = match hebrew_date.month() { - HebrewMonth::Tishrei => 1, - HebrewMonth::Cheshvan => 2, - HebrewMonth::Kislev => 3, - HebrewMonth::Teves => 4, - HebrewMonth::Shvat => 5, - HebrewMonth::Adar => 6, - HebrewMonth::Adar1 => 6, - HebrewMonth::Adar2 => 100, // Adar2 is a leap-year month and never has a holiday. 100 is an arbitrary escape value. - HebrewMonth::Nissan => 7, - HebrewMonth::Iyar => 8, - HebrewMonth::Sivan => 9, - HebrewMonth::Tammuz => 10, - HebrewMonth::Av => 11, - HebrewMonth::Elul => 12, + let is_independence_or_memorial_day = match &(hebrew_month, hebrew_day, wd) { + (8, 3..=4, Weekday::Thursday) => true, + (8, 2..=3, Weekday::Wednesday) => true, + (8, 5, Weekday::Monday) => true, + (8, 6, Weekday::Tuesday) => true, + (8, 5, Weekday::Wednesday) => true, + (8, 4, Weekday::Tuesday) => true, + _ => false, }; - JEWISH_HOLIDAYS.contains(&(month, hebrew_date.day().get() as u8)) + let is_tisha_beav = match &(hebrew_month, hebrew_day, wd) { + (11, 10, Weekday::Sunday) => true, + (11, 9, Weekday::Saturday) => false, + (11, 9, _) => true, + _ => false, + }; + + JEWISH_HOLIDAYS.contains(&(hebrew_month, hebrew_day)) + || is_independence_or_memorial_day + || is_tisha_beav } } @@ -112,6 +124,7 @@ impl Calendar for IsraelCalendar { mod test_israel { use super::*; use time::macros::date; + use time::Month; // Test to verify the name() method. #[test] @@ -134,20 +147,68 @@ mod test_israel { #[test] fn test_is_public_holiday() { let calendar = IsraelCalendar; - let purim = date!(2024 - 03 - 24); // Purim holiday 2024 - let sukkot = date!(2024 - 10 - 17); // Sukkot holiday 2024 - let passover_23 = date!(2023 - 4 - 05); // Passover eve 2023 - let passover_24 = date!(2023 - 4 - 22); // Passover eve 2024 - let shavuot_26 = date!(2026 - 5 - 22); // Shavuot 2026 - let memorial_26 = date!(2026 - 4 - 21); // Memorial day 2026 - - assert!(!calendar.is_business_day(purim)); - assert!(!calendar.is_business_day(sukkot)); - assert!(!calendar.is_business_day(passover_23)); - assert!(!calendar.is_business_day(passover_24)); - assert!(!calendar.is_business_day(shavuot_26)); - assert!(!calendar.is_business_day(memorial_26)); - + let holidays = vec![ + (2024, 3, 24), // Purim + (2024, 4, 22), // Passover Eve + (2024, 4, 23), // Passover + (2024, 4, 28), // Passover II Eve + (2024, 4, 29), // Passover II + (2024, 5, 13), // Memorial Day + (2024, 5, 14), // Independence Day + (2024, 6, 11), // Pentecost (Shavuot) Eve + (2024, 6, 12), // Pentecost (Shavuot) + (2024, 8, 13), // Fast Day (Tisha B'Av) + (2024, 10, 3), // Jewish New Year I + (2024, 10, 4), // Jewish New Year II + (2024, 10, 11), // Yom Kippur Eve + (2024, 10, 17), // Feast of Tabernacles (Sukkoth) + (2024, 10, 24), // Rejoicing of the Law (Simchat Tora) + (2025, 3, 14), // Purim + (2025, 4, 13), // Passover + (2025, 6, 2), // Pentecost (Shavuot) + (2025, 8, 3), // Fast Day (Tisha B'Av) + (2025, 9, 23), // Jewish New Year I + (2025, 9, 24), // Jewish New Year II + (2025, 10, 2), // Yom Kippur + (2025, 10, 7), // Feast of Tabernacles (Sukkoth) + (2025, 10, 14), // Rejoicing of the Law (Simchat Tora) + (2015, 3, 5), // Purim + (2015, 4, 10), // Passover II + (2015, 4, 23), // Independence Day + (2015, 5, 24), // Pentecost (Shavuot) + (2015, 7, 26), // Fast Day + (2015, 9, 14), // Jewish New Year I + (2015, 9, 15), // Jewish New Year II + (2015, 9, 23), // Yom Kippur + (2015, 9, 28), // Feast of Tabernacles (Sukkoth) + (2015, 10, 5), // Rejoicing of the Law (Simchat Tora) + (2018, 3, 1), // Purim + (2018, 4, 6), // Passover II + (2018, 4, 19), // Independence Day + (2018, 5, 20), // Pentecost (Shavuot) + (2018, 7, 22), // Fast Day + (2018, 9, 10), // Jewish New Year I + (2018, 9, 11), // Jewish New Year II + (2018, 9, 18), // Yom Kippur Eve + (2018, 9, 19), // Yom Kippur + (2018, 9, 24), // Feast of Tabernacles (Sukkoth) + (2018, 10, 1), // Rejoicing of the Law (Simchat Tora) + (2017, 3, 12), // Purim + (2017, 4, 11), // Passover 1 + (2017, 4, 17), // Passover II + (2017, 5, 2), // Independence Day + (2017, 5, 31), // Pentecost (Shavuot) + (2017, 8, 1), // Fast Day + (2017, 9, 21), // Jewish New Year I + (2017, 9, 22), // Jewish New Year II + (2017, 9, 29), // Yom Kippur Eve + (2017, 10, 5), // Feast of Tabernacles (Sukkoth) + (2017, 10, 12), // Rejoicing of the Law (Simchat Tora) + ]; + for (y, m, d) in holidays { + let date = Date::from_calendar_date(y, Month::try_from(m).unwrap(), d).unwrap(); + assert!(!calendar.is_business_day(date)); + } } // Test to verify if the is_business_day() method properly accounts for regular business days.