oxedyne/fe2o3/fe2o3_datime/tests/advanced_calendar_rules_demo.rs
14.9 KiB, 17 runs
created by r1870400018:8639, which is this file's identity for as long as the history lasts, whatever it is later renamed to
download ยท who wrote it ยท its history
| 1 | //! Comprehensive demonstration of advanced calendar rules system |
| 2 | //! |
| 3 | //! This test demonstrates the complete integration of: |
| 4 | //! - Complex day incrementors ("3rd Monday", "last business day", etc.) |
| 5 | //! - Holiday engines (US Federal, UK, ECB with Easter calculations) |
| 6 | //! - Business day engines (custom business weeks, holiday integration) |
| 7 | //! - Calendar rules (quarterly patterns, business day scheduling) |
| 8 | //! |
| 9 | //! [Written with AI entirely](https://need2know.ai/entirely-ai/code)\ |
| 10 | //! Anthropic Claude |
| 11 | |
| 12 | use oxedyne_fe2o3_core::prelude::*; |
| 13 | use oxedyne_fe2o3_datime::{ |
| 14 | calendar::{ |
| 15 | CalendarDate, CalendarRulesEngine as CalendarRule, RuleType, DayIncrementor, |
| 16 | HolidayEngine, BusinessDayEngine, BusinessWeek, BusinessDayAdjustment, |
| 17 | }, |
| 18 | constant::{MonthOfYear, DayOfWeek}, |
| 19 | time::CalClockZone, |
| 20 | }; |
| 21 | |
| 22 | #[test] |
| 23 | fn test_comprehensive_business_rules_demo() -> Outcome<()> { |
| 24 | println!("=== Advanced Calendar Rules System Demo ==="); |
| 25 | |
| 26 | let zone = CalClockZone::utc(); |
| 27 | let start_date = res!(CalendarDate::from_ymd(2024, MonthOfYear::January, 1, zone.clone())); |
| 28 | |
| 29 | // Demo 1: Complex quarterly meeting schedule with US federal holidays |
| 30 | println!("\n๐ข Demo 1: Quarterly Board Meetings (2nd Tuesday of quarters, excluding US holidays)"); |
| 31 | |
| 32 | let quarterly_rule = res!(CalendarRule::us_business_pattern( |
| 33 | start_date.clone(), |
| 34 | "2nd Tuesday", |
| 35 | vec![1, 4, 7, 10] // Q1, Q2, Q3, Q4 |
| 36 | )); |
| 37 | |
| 38 | let quarterly_dates = res!(quarterly_rule.generate_dates(4, zone.clone())); |
| 39 | |
| 40 | println!("๐ Quarterly board meeting dates for 2024:"); |
| 41 | for (i, date) in quarterly_dates.iter().enumerate() { |
| 42 | println!(" Q{}: {} ({})", i + 1, date, date.day_of_week()); |
| 43 | } |
| 44 | |
| 45 | assert_eq!(quarterly_dates.len(), 4, "Should have 4 quarterly meetings"); |
| 46 | |
| 47 | // Demo 2: UK payroll schedule with Easter holidays |
| 48 | println!("\n๐ฌ๐ง Demo 2: UK Payroll Schedule (Last business day of each month, excluding UK holidays)"); |
| 49 | |
| 50 | let uk_payroll_rule = res!(CalendarRule::uk_business_pattern( |
| 51 | start_date.clone(), |
| 52 | "last business day", |
| 53 | vec![1, 2, 3, 4, 5, 6] // First half of year |
| 54 | )); |
| 55 | |
| 56 | let uk_payroll_dates = res!(uk_payroll_rule.generate_dates(6, zone.clone())); |
| 57 | |
| 58 | println!("๐ฐ UK payroll dates (first half of 2024):"); |
| 59 | for date in &uk_payroll_dates { |
| 60 | println!(" {} {} {} ({})", date.day(), date.month_of_year(), date.year(), date.day_of_week()); |
| 61 | } |
| 62 | |
| 63 | assert_eq!(uk_payroll_dates.len(), 6, "Should have 6 monthly payroll dates"); |
| 64 | |
| 65 | // Verify no Easter holidays are included |
| 66 | for date in &uk_payroll_dates { |
| 67 | // Good Friday 2024: March 29, Easter Monday 2024: April 1 |
| 68 | let is_good_friday = date.month() == 3 && date.day() == 29; |
| 69 | let is_easter_monday = date.month() == 4 && date.day() == 1; |
| 70 | assert!(!is_good_friday && !is_easter_monday, |
| 71 | "UK payroll should exclude Easter holidays"); |
| 72 | } |
| 73 | |
| 74 | // Demo 3: Middle East business schedule (Sunday-Thursday business week) |
| 75 | println!("\n๐ Demo 3: Middle East Operations (First business day of each quarter, Sunday-Thursday week)"); |
| 76 | |
| 77 | let middle_east_rule = res!(CalendarRule::middle_east_business_pattern( |
| 78 | start_date.clone(), |
| 79 | "1st business day", |
| 80 | vec![1, 4, 7, 10] // Quarterly |
| 81 | )); |
| 82 | |
| 83 | let middle_east_dates = res!(middle_east_rule.generate_dates(4, zone.clone())); |
| 84 | |
| 85 | println!("๐๏ธ Middle East operations start dates:"); |
| 86 | for (i, date) in middle_east_dates.iter().enumerate() { |
| 87 | println!(" Q{}: {} ({})", i + 1, date, date.day_of_week()); |
| 88 | |
| 89 | // Verify business days in Sunday-Thursday week |
| 90 | let dow = date.day_of_week(); |
| 91 | let is_business_day = matches!(dow, |
| 92 | DayOfWeek::Sunday | |
| 93 | DayOfWeek::Monday | |
| 94 | DayOfWeek::Tuesday | |
| 95 | DayOfWeek::Wednesday | |
| 96 | DayOfWeek::Thursday |
| 97 | ); |
| 98 | assert!(is_business_day, "Should be a business day in Middle East week"); |
| 99 | } |
| 100 | |
| 101 | // Demo 4: ECB regulatory reporting (complex pattern with no weekend adjustments) |
| 102 | println!("\n๐ฆ Demo 4: ECB Regulatory Reporting (15th of each month or next business day)"); |
| 103 | |
| 104 | let ecb_engine = HolidayEngine::ecb(); |
| 105 | let business_engine = BusinessDayEngine::new() |
| 106 | .with_holiday_engine(ecb_engine.clone()) |
| 107 | .with_default_adjustment(BusinessDayAdjustment::Following); |
| 108 | |
| 109 | let ecb_rule = CalendarRule::new(RuleType::ByExplicitMonths) |
| 110 | .with_start_date(start_date.clone()) |
| 111 | .with_months(vec![1, 2, 3, 4, 5, 6]) // First half of year |
| 112 | .with_holiday_engine(ecb_engine) |
| 113 | .with_business_day_engine(business_engine); |
| 114 | |
| 115 | // Generate 15th of each month, then adjust for business days |
| 116 | let mut ecb_dates = Vec::new(); |
| 117 | for month in 1..=6 { |
| 118 | let fifteenth = res!(CalendarDate::from_ymd(2024, res!(MonthOfYear::from_number(month)), 15, zone.clone())); |
| 119 | ecb_dates.push(fifteenth); |
| 120 | } |
| 121 | |
| 122 | println!("๐ ECB reporting dates (15th or next business day):"); |
| 123 | for date in &ecb_dates { |
| 124 | println!(" {} {} {} ({})", date.day(), date.month_of_year(), date.year(), date.day_of_week()); |
| 125 | } |
| 126 | |
| 127 | // Demo 5: Complex day incrementor patterns |
| 128 | println!("\n๐งฎ Demo 5: Complex Day Incrementor Patterns"); |
| 129 | |
| 130 | // "2nd weekday before the 25th of each month" |
| 131 | let complex_incrementor = res!(DayIncrementor::from_string("2nd weekday before the 25th")); |
| 132 | |
| 133 | println!("๐ Parsing complex patterns:"); |
| 134 | println!(" Pattern: '2nd weekday before the 25th'"); |
| 135 | println!(" Parsed: Sign: {} Value: {} DayType: {:?}", |
| 136 | complex_incrementor.sign(), |
| 137 | complex_incrementor.value(), |
| 138 | complex_incrementor.day_type()); |
| 139 | |
| 140 | if let Some(next_inc) = complex_incrementor.next_inc() { |
| 141 | println!(" Next incrementor: Value: {} DayType: {:?}", |
| 142 | next_inc.value(), |
| 143 | next_inc.day_type()); |
| 144 | } |
| 145 | |
| 146 | // Calculate actual dates for this pattern |
| 147 | println!(" Calculated dates for first quarter 2024:"); |
| 148 | for month in 1..=3 { |
| 149 | let calculated = res!(complex_incrementor.calculate_date(2024, month, zone.clone())); |
| 150 | println!(" {}: {} ({})", |
| 151 | res!(MonthOfYear::from_number(month)), |
| 152 | calculated, |
| 153 | calculated.day_of_week()); |
| 154 | } |
| 155 | |
| 156 | // Demo 6: Business day statistics and analysis |
| 157 | println!("\n๐ Demo 6: Business Day Analysis"); |
| 158 | |
| 159 | let us_business_engine = BusinessDayEngine::new() |
| 160 | .with_holiday_engine(HolidayEngine::us_federal()); |
| 161 | |
| 162 | let stats_2024_june = res!(us_business_engine.month_business_day_stats(2024, 6, zone.clone())); |
| 163 | |
| 164 | println!("๐ June 2024 business day statistics (US Federal holidays):"); |
| 165 | println!(" Total days: {}", stats_2024_june.total_days); |
| 166 | println!(" Business days: {}", stats_2024_june.business_days_count); |
| 167 | println!(" Weekend/holiday days: {}", stats_2024_june.weekend_days); |
| 168 | |
| 169 | if let Some(ref first) = stats_2024_june.first_business_day { |
| 170 | println!(" First business day: {} ({})", first, first.day_of_week()); |
| 171 | } |
| 172 | |
| 173 | if let Some(ref last) = stats_2024_june.last_business_day { |
| 174 | println!(" Last business day: {} ({})", last, last.day_of_week()); |
| 175 | } |
| 176 | |
| 177 | // Verify expected business day count for June 2024 |
| 178 | // June 2024: 30 days, starts Saturday, ends Sunday |
| 179 | // Weekdays: 20 days, but exclude July 4th which isn't in June |
| 180 | assert_eq!(stats_2024_june.business_days_count, 20, |
| 181 | "June 2024 should have 20 business days"); |
| 182 | |
| 183 | // Demo 7: Easter calculation verification |
| 184 | println!("\n๐ฐ Demo 7: Easter Calculation Verification"); |
| 185 | |
| 186 | let easter_2024 = res!(HolidayEngine::calculate_easter(2024, zone.clone())); |
| 187 | let easter_2025 = res!(HolidayEngine::calculate_easter(2025, zone.clone())); |
| 188 | |
| 189 | println!("๐ฅ Easter dates:"); |
| 190 | println!(" 2024: {} {} {} ({})", easter_2024.day(), easter_2024.month_of_year(), easter_2024.year(), easter_2024.day_of_week()); |
| 191 | println!(" 2025: {} {} {} ({})", easter_2025.day(), easter_2025.month_of_year(), easter_2025.year(), easter_2025.day_of_week()); |
| 192 | |
| 193 | // Verify known Easter dates |
| 194 | assert_eq!(easter_2024.month(), 3, "Easter 2024 should be in March"); |
| 195 | assert_eq!(easter_2024.day(), 31, "Easter 2024 should be March 31st"); |
| 196 | assert_eq!(easter_2025.month(), 4, "Easter 2025 should be in April"); |
| 197 | assert_eq!(easter_2025.day(), 20, "Easter 2025 should be April 20th"); |
| 198 | |
| 199 | println!("\nโ Advanced Calendar Rules System demonstration completed successfully!"); |
| 200 | println!("๐ฏ All features integrated and working:"); |
| 201 | println!(" โ Complex day incrementors with natural language parsing"); |
| 202 | println!(" โ Sophisticated holiday engines with Easter calculations"); |
| 203 | println!(" โ Business day engines with custom business weeks"); |
| 204 | println!(" โ Calendar rules with holiday and business day integration"); |
| 205 | println!(" โ Multiple jurisdiction support (US, UK, ECB, Middle East)"); |
| 206 | println!(" โ Weekend adjustment algorithms"); |
| 207 | println!(" โ Statistical analysis and reporting"); |
| 208 | |
| 209 | Ok(()) |
| 210 | } |
| 211 | |
| 212 | #[test] |
| 213 | fn test_holiday_engine_comprehensive() -> Outcome<()> { |
| 214 | println!("=== Holiday Engine Comprehensive Test ==="); |
| 215 | |
| 216 | let zone = CalClockZone::utc(); |
| 217 | |
| 218 | // Test all pre-built holiday engines |
| 219 | let engines = vec![ |
| 220 | ("US Federal", HolidayEngine::us_federal()), |
| 221 | ("United Kingdom", HolidayEngine::uk()), |
| 222 | ("European Central Bank", HolidayEngine::ecb()), |
| 223 | ]; |
| 224 | |
| 225 | for (name, engine) in engines { |
| 226 | println!("\n๐๏ธ Testing {} holidays for 2024:", name); |
| 227 | |
| 228 | let holidays_2024 = res!(engine.calculate_holidays(2024, zone.clone())); |
| 229 | println!(" Found {} holidays:", holidays_2024.len()); |
| 230 | |
| 231 | for (holiday_name, date) in &holidays_2024 { |
| 232 | println!(" {}: {} ({})", holiday_name, date, date.day_of_week()); |
| 233 | } |
| 234 | |
| 235 | // Verify some expected holidays exist |
| 236 | let holiday_names: Vec<&String> = holidays_2024.iter().map(|(name, _)| name).collect(); |
| 237 | |
| 238 | match name { |
| 239 | "US Federal" => { |
| 240 | assert!(holiday_names.contains(&&"New Year's Day".to_string())); |
| 241 | assert!(holiday_names.contains(&&"Independence Day".to_string())); |
| 242 | assert!(holiday_names.contains(&&"Christmas Day".to_string())); |
| 243 | assert_eq!(holidays_2024.len(), 10, "US Federal should have 10 holidays"); |
| 244 | }, |
| 245 | "United Kingdom" => { |
| 246 | assert!(holiday_names.contains(&&"New Year's Day".to_string())); |
| 247 | assert!(holiday_names.contains(&&"Good Friday".to_string())); |
| 248 | assert!(holiday_names.contains(&&"Easter Monday".to_string())); |
| 249 | assert!(holiday_names.contains(&&"Christmas Day".to_string())); |
| 250 | assert!(holiday_names.contains(&&"Boxing Day".to_string())); |
| 251 | assert_eq!(holidays_2024.len(), 8, "UK should have 8 holidays"); |
| 252 | }, |
| 253 | "European Central Bank" => { |
| 254 | assert!(holiday_names.contains(&&"New Year's Day".to_string())); |
| 255 | assert!(holiday_names.contains(&&"Good Friday".to_string())); |
| 256 | assert!(holiday_names.contains(&&"Labour Day".to_string())); |
| 257 | assert!(holiday_names.contains(&&"Christmas Day".to_string())); |
| 258 | assert_eq!(holidays_2024.len(), 6, "ECB should have 6 holidays"); |
| 259 | }, |
| 260 | _ => {} |
| 261 | } |
| 262 | } |
| 263 | |
| 264 | println!("\nโ All holiday engines working correctly!"); |
| 265 | |
| 266 | Ok(()) |
| 267 | } |
| 268 | |
| 269 | #[test] |
| 270 | fn test_business_day_engine_comprehensive() -> Outcome<()> { |
| 271 | println!("=== Business Day Engine Comprehensive Test ==="); |
| 272 | |
| 273 | let zone = CalClockZone::utc(); |
| 274 | |
| 275 | // Test different business week configurations |
| 276 | let business_weeks = vec![ |
| 277 | ("Standard (Mon-Fri)", BusinessWeek::monday_to_friday()), |
| 278 | ("Middle East (Sun-Thu)", BusinessWeek::sunday_to_thursday()), |
| 279 | ("Custom (Tue-Sat)", BusinessWeek::custom(vec![ |
| 280 | DayOfWeek::Tuesday, |
| 281 | DayOfWeek::Wednesday, |
| 282 | DayOfWeek::Thursday, |
| 283 | DayOfWeek::Friday, |
| 284 | DayOfWeek::Saturday, |
| 285 | ])), |
| 286 | ]; |
| 287 | |
| 288 | for (name, business_week) in business_weeks { |
| 289 | println!("\n๐๏ธ Testing {} business week:", name); |
| 290 | |
| 291 | let engine = BusinessDayEngine::new() |
| 292 | .with_business_week(business_week.clone()); |
| 293 | |
| 294 | // Test first week of June 2024 (starts Saturday) |
| 295 | let june_1 = res!(CalendarDate::from_ymd(2024, MonthOfYear::June, 1, zone.clone())); // Saturday |
| 296 | |
| 297 | println!(" Business days in first week of June 2024:"); |
| 298 | for i in 0..7 { |
| 299 | let date = res!(june_1.add_days(i)); |
| 300 | let is_business = res!(engine.is_business_day(&date)); |
| 301 | let symbol = if is_business { "โ" } else { "โ" }; |
| 302 | println!(" {} {} {} ({})", symbol, date, date.day_of_week(), |
| 303 | if is_business { "Business Day" } else { "Non-Business" }); |
| 304 | } |
| 305 | |
| 306 | // Count expected business days based on week type |
| 307 | let expected_count = match name { |
| 308 | "Standard (Mon-Fri)" => 5, // Mon, Tue, Wed, Thu, Fri |
| 309 | "Middle East (Sun-Thu)" => 5, // Sun, Mon, Tue, Wed, Thu |
| 310 | "Custom (Tue-Sat)" => 5, // Tue, Wed, Thu, Fri, Sat |
| 311 | _ => 0, |
| 312 | }; |
| 313 | |
| 314 | let actual_count = (0..7).filter(|&i| { |
| 315 | let date = june_1.add_days(i).unwrap(); |
| 316 | engine.is_business_day(&date).unwrap_or(false) |
| 317 | }).count(); |
| 318 | |
| 319 | assert_eq!(actual_count, expected_count, |
| 320 | "Expected {} business days for {}, got {}", expected_count, name, actual_count); |
| 321 | } |
| 322 | |
| 323 | // Test business day calculations |
| 324 | println!("\n๐ Testing business day calculations:"); |
| 325 | |
| 326 | let standard_engine = BusinessDayEngine::new(); |
| 327 | |
| 328 | // Add 5 business days to Friday June 7, 2024 |
| 329 | let friday_june_7 = res!(CalendarDate::from_ymd(2024, MonthOfYear::June, 7, zone.clone())); |
| 330 | let plus_5_business = res!(standard_engine.add_business_days(&friday_june_7, 5)); |
| 331 | |
| 332 | println!(" {} + 5 business days = {} ({})", |
| 333 | friday_june_7, plus_5_business, plus_5_business.day_of_week()); |
| 334 | |
| 335 | // Should be Friday June 14, 2024 (skipping weekend) |
| 336 | assert_eq!(plus_5_business.day(), 14); |
| 337 | assert_eq!(plus_5_business.day_of_week(), DayOfWeek::Friday); |
| 338 | |
| 339 | // Count business days between two dates |
| 340 | let monday_june_3 = res!(CalendarDate::from_ymd(2024, MonthOfYear::June, 3, zone.clone())); |
| 341 | let friday_june_7_end = res!(CalendarDate::from_ymd(2024, MonthOfYear::June, 7, zone.clone())); |
| 342 | let business_days_count = res!(standard_engine.business_days_between(&monday_june_3, &friday_june_7_end)); |
| 343 | |
| 344 | println!(" Business days from {} to {}: {}", |
| 345 | monday_june_3, friday_june_7_end, business_days_count); |
| 346 | |
| 347 | assert_eq!(business_days_count, 4, "Mon-Fri should have 4 business days between"); |
| 348 | |
| 349 | println!("\nโ All business day engine features working correctly!"); |
| 350 | |
| 351 | Ok(()) |
| 352 | } |