oxedyne/fe2o3/fe2o3_datime/src/calendar/rule_engine.rs
14.2 KiB, 75 runs
created by r1870400018:8407, 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 | //! [Written with AI entirely](https://need2know.ai/entirely-ai/code)\ |
| 2 | //! Anthropic Claude |
| 3 | |
| 4 | use oxedyne_fe2o3_core::prelude::*; |
| 5 | use crate::{ |
| 6 | calendar::{CalendarDate, CalendarDuration, CalendarMonth, CalendarYear, CalendarDay, DayIncrementor, MonthPeriod}, |
| 7 | constant::MonthOfYear, |
| 8 | time::CalClockInterval, |
| 9 | }; |
| 10 | use std::collections::BTreeSet; |
| 11 | |
| 12 | #[derive(Clone, Debug, PartialEq)] |
| 13 | pub enum RuleType { |
| 14 | ByYears, |
| 15 | ByExplicitMonths, |
| 16 | ByRegularMonths, |
| 17 | ByDays, |
| 18 | } |
| 19 | |
| 20 | #[derive(Clone, Debug, PartialEq)] |
| 21 | enum GeneratorTask { |
| 22 | All, |
| 23 | AllOnOrBefore, |
| 24 | AllOnOrAfter, |
| 25 | NOnOrBefore, |
| 26 | NOnOrAfter, |
| 27 | } |
| 28 | |
| 29 | #[derive(Clone, Debug)] |
| 30 | pub struct CalendarRule { |
| 31 | rule_type: RuleType, |
| 32 | duration: Option<CalendarDuration>, |
| 33 | count_limit: Option<i32>, |
| 34 | start_date: Option<CalendarDate>, |
| 35 | |
| 36 | // By years fields |
| 37 | skip_years: Option<CalendarYear>, |
| 38 | |
| 39 | // By months fields |
| 40 | day_incrementor: Option<DayIncrementor>, |
| 41 | month_set: Option<BTreeSet<MonthOfYear>>, |
| 42 | start_month: Option<MonthPeriod>, |
| 43 | skip_months: Option<CalendarMonth>, |
| 44 | |
| 45 | // By days fields |
| 46 | skip_days: Option<CalendarDay>, |
| 47 | } |
| 48 | |
| 49 | impl CalendarRule { |
| 50 | pub fn new(rule_type: RuleType) -> Self { |
| 51 | Self { |
| 52 | rule_type, |
| 53 | duration: None, |
| 54 | count_limit: None, |
| 55 | start_date: None, |
| 56 | skip_years: None, |
| 57 | day_incrementor: None, |
| 58 | month_set: None, |
| 59 | start_month: None, |
| 60 | skip_months: None, |
| 61 | skip_days: None, |
| 62 | } |
| 63 | } |
| 64 | |
| 65 | pub fn new_comprehensive( |
| 66 | rule_type: RuleType, |
| 67 | duration: Option<CalendarDuration>, |
| 68 | count_limit: Option<i32>, |
| 69 | start_date: Option<CalendarDate>, |
| 70 | skip_years: Option<CalendarYear>, |
| 71 | day_incrementor: Option<DayIncrementor>, |
| 72 | start_month: Option<MonthPeriod>, |
| 73 | month_set: Option<BTreeSet<MonthOfYear>>, |
| 74 | skip_months: Option<CalendarMonth>, |
| 75 | skip_days: Option<CalendarDay>, |
| 76 | ) -> Outcome<Self> { |
| 77 | let rule = Self { |
| 78 | rule_type, |
| 79 | duration, |
| 80 | count_limit, |
| 81 | start_date, |
| 82 | skip_years, |
| 83 | day_incrementor, |
| 84 | month_set, |
| 85 | start_month, |
| 86 | skip_months, |
| 87 | skip_days, |
| 88 | }; |
| 89 | |
| 90 | // Validate the rule configuration |
| 91 | res!(rule.validate()); |
| 92 | Ok(rule) |
| 93 | } |
| 94 | |
| 95 | fn validate(&self) -> Outcome<()> { |
| 96 | match self.rule_type { |
| 97 | RuleType::ByYears => { |
| 98 | if self.skip_years.is_none() && self.start_date.is_none() { |
| 99 | return Err(err!("By-years rule requires either skip_years or start_date"; Invalid, Input)); |
| 100 | } |
| 101 | }, |
| 102 | RuleType::ByExplicitMonths => { |
| 103 | if self.month_set.is_none() { |
| 104 | return Err(err!("By-explicit-months rule requires month_set"; Invalid, Input)); |
| 105 | } |
| 106 | }, |
| 107 | RuleType::ByRegularMonths => { |
| 108 | if self.skip_months.is_none() { |
| 109 | return Err(err!("By-regular-months rule requires skip_months"; Invalid, Input)); |
| 110 | } |
| 111 | }, |
| 112 | RuleType::ByDays => { |
| 113 | if self.skip_days.is_none() { |
| 114 | return Err(err!("By-days rule requires skip_days"; Invalid, Input)); |
| 115 | } |
| 116 | }, |
| 117 | } |
| 118 | Ok(()) |
| 119 | } |
| 120 | |
| 121 | pub fn with_duration(mut self, duration: CalendarDuration) -> Self { |
| 122 | self.duration = Some(duration); |
| 123 | self |
| 124 | } |
| 125 | |
| 126 | pub fn with_count_limit(mut self, count_limit: i32) -> Self { |
| 127 | self.count_limit = Some(count_limit); |
| 128 | self |
| 129 | } |
| 130 | |
| 131 | pub fn with_start_date(mut self, start_date: CalendarDate) -> Self { |
| 132 | self.start_date = Some(start_date); |
| 133 | self |
| 134 | } |
| 135 | |
| 136 | pub fn with_skip_years(mut self, skip_years: CalendarYear) -> Self { |
| 137 | self.skip_years = Some(skip_years); |
| 138 | self |
| 139 | } |
| 140 | |
| 141 | pub fn with_day_incrementor(mut self, day_incrementor: DayIncrementor) -> Self { |
| 142 | self.day_incrementor = Some(day_incrementor); |
| 143 | self |
| 144 | } |
| 145 | |
| 146 | pub fn with_month_set(mut self, month_set: BTreeSet<MonthOfYear>) -> Self { |
| 147 | self.month_set = Some(month_set); |
| 148 | self |
| 149 | } |
| 150 | |
| 151 | pub fn with_start_month(mut self, start_month: MonthPeriod) -> Self { |
| 152 | self.start_month = Some(start_month); |
| 153 | self |
| 154 | } |
| 155 | |
| 156 | pub fn with_skip_months(mut self, skip_months: CalendarMonth) -> Self { |
| 157 | self.skip_months = Some(skip_months); |
| 158 | self |
| 159 | } |
| 160 | |
| 161 | pub fn with_skip_days(mut self, skip_days: CalendarDay) -> Self { |
| 162 | self.skip_days = Some(skip_days); |
| 163 | self |
| 164 | } |
| 165 | |
| 166 | pub fn to_dates(&self, from_date: &CalendarDate, duration: &CalendarDuration) -> Outcome<Vec<CalendarDate>> { |
| 167 | self.generate_dates(GeneratorTask::All, from_date, Some(duration), None, None) |
| 168 | } |
| 169 | |
| 170 | pub fn to_dates_before(&self, before_date: &CalendarDate) -> Outcome<Vec<CalendarDate>> { |
| 171 | self.generate_dates(GeneratorTask::AllOnOrBefore, before_date, None, None, None) |
| 172 | } |
| 173 | |
| 174 | pub fn to_dates_after(&self, after_date: &CalendarDate) -> Outcome<Vec<CalendarDate>> { |
| 175 | self.generate_dates(GeneratorTask::AllOnOrAfter, after_date, None, None, None) |
| 176 | } |
| 177 | |
| 178 | pub fn next_n_dates(&self, after_date: &CalendarDate, n: i32) -> Outcome<Vec<CalendarDate>> { |
| 179 | self.generate_dates(GeneratorTask::NOnOrAfter, after_date, None, Some(n), None) |
| 180 | } |
| 181 | |
| 182 | pub fn previous_n_dates(&self, before_date: &CalendarDate, n: i32) -> Outcome<Vec<CalendarDate>> { |
| 183 | self.generate_dates(GeneratorTask::NOnOrBefore, before_date, None, Some(n), None) |
| 184 | } |
| 185 | |
| 186 | pub fn next(&self, after_date: &CalendarDate) -> Outcome<Option<CalendarDate>> { |
| 187 | let dates = res!(self.next_n_dates(after_date, 1)); |
| 188 | Ok(dates.first().cloned()) |
| 189 | } |
| 190 | |
| 191 | pub fn previous(&self, before_date: &CalendarDate) -> Outcome<Option<CalendarDate>> { |
| 192 | let dates = res!(self.previous_n_dates(before_date, 1)); |
| 193 | Ok(dates.first().cloned()) |
| 194 | } |
| 195 | |
| 196 | fn generate_dates( |
| 197 | &self, |
| 198 | task: GeneratorTask, |
| 199 | reference_date: &CalendarDate, |
| 200 | duration: Option<&CalendarDuration>, |
| 201 | count: Option<i32>, |
| 202 | _holidays: Option<&Vec<CalClockInterval>>, |
| 203 | ) -> Outcome<Vec<CalendarDate>> { |
| 204 | let mut results = Vec::new(); |
| 205 | |
| 206 | match self.rule_type { |
| 207 | RuleType::ByYears => { |
| 208 | res!(self.generate_by_years(&mut results, task, reference_date, duration, count)); |
| 209 | }, |
| 210 | RuleType::ByExplicitMonths => { |
| 211 | res!(self.generate_by_explicit_months(&mut results, task, reference_date, duration, count, _holidays)); |
| 212 | }, |
| 213 | RuleType::ByRegularMonths => { |
| 214 | res!(self.generate_by_regular_months(&mut results, task, reference_date, duration, count, _holidays)); |
| 215 | }, |
| 216 | RuleType::ByDays => { |
| 217 | res!(self.generate_by_days(&mut results, task, reference_date, duration, count)); |
| 218 | }, |
| 219 | } |
| 220 | |
| 221 | // Apply count limit if specified |
| 222 | if let Some(limit) = self.count_limit { |
| 223 | results.truncate(limit as usize); |
| 224 | } |
| 225 | |
| 226 | Ok(results) |
| 227 | } |
| 228 | |
| 229 | fn generate_by_years( |
| 230 | &self, |
| 231 | results: &mut Vec<CalendarDate>, |
| 232 | task: GeneratorTask, |
| 233 | reference_date: &CalendarDate, |
| 234 | duration: Option<&CalendarDuration>, |
| 235 | count: Option<i32>, |
| 236 | ) -> Outcome<()> { |
| 237 | let start_date = ok!(self.start_date.as_ref() |
| 238 | .ok_or_else(|| err!("By-years rule requires start_date"; Invalid, Input))); |
| 239 | |
| 240 | let skip_years = self.skip_years.as_ref().map(|y| y.of()).unwrap_or(1); |
| 241 | let mut current_year = start_date.year(); |
| 242 | |
| 243 | // Adjust starting year based on task |
| 244 | match task { |
| 245 | GeneratorTask::AllOnOrAfter | GeneratorTask::NOnOrAfter => { |
| 246 | while current_year < reference_date.year() { |
| 247 | current_year += skip_years; |
| 248 | } |
| 249 | }, |
| 250 | GeneratorTask::AllOnOrBefore | GeneratorTask::NOnOrBefore => { |
| 251 | while current_year > reference_date.year() { |
| 252 | current_year -= skip_years; |
| 253 | } |
| 254 | }, |
| 255 | _ => {} |
| 256 | } |
| 257 | |
| 258 | let max_count = count.unwrap_or(1000); // Reasonable default limit |
| 259 | let mut generated = 0; |
| 260 | |
| 261 | loop { |
| 262 | if generated >= max_count { |
| 263 | break; |
| 264 | } |
| 265 | |
| 266 | // Create date for this year |
| 267 | let candidate_date = res!(CalendarDate::from_ymd( |
| 268 | current_year, |
| 269 | start_date.month_of_year(), |
| 270 | start_date.day(), |
| 271 | start_date.zone().clone() |
| 272 | )); |
| 273 | |
| 274 | // Check if this date meets the task criteria |
| 275 | let should_include = match task { |
| 276 | GeneratorTask::All => { |
| 277 | if let Some(dur) = duration { |
| 278 | let end_date = res!(reference_date.add_calendar_duration(dur)); |
| 279 | candidate_date >= *reference_date && candidate_date <= end_date |
| 280 | } else { |
| 281 | true |
| 282 | } |
| 283 | }, |
| 284 | GeneratorTask::AllOnOrBefore | GeneratorTask::NOnOrBefore => { |
| 285 | candidate_date <= *reference_date |
| 286 | }, |
| 287 | GeneratorTask::AllOnOrAfter | GeneratorTask::NOnOrAfter => { |
| 288 | candidate_date >= *reference_date |
| 289 | }, |
| 290 | }; |
| 291 | |
| 292 | if should_include { |
| 293 | results.push(candidate_date); |
| 294 | generated += 1; |
| 295 | } |
| 296 | |
| 297 | // Move to next/previous year |
| 298 | match task { |
| 299 | GeneratorTask::AllOnOrBefore | GeneratorTask::NOnOrBefore => { |
| 300 | current_year -= skip_years; |
| 301 | if current_year < 1 { break; } // Reasonable lower bound |
| 302 | }, |
| 303 | _ => { |
| 304 | current_year += skip_years; |
| 305 | if current_year > 3000 { break; } // Reasonable upper bound |
| 306 | }, |
| 307 | } |
| 308 | } |
| 309 | |
| 310 | Ok(()) |
| 311 | } |
| 312 | |
| 313 | fn generate_by_explicit_months( |
| 314 | &self, |
| 315 | results: &mut Vec<CalendarDate>, |
| 316 | task: GeneratorTask, |
| 317 | reference_date: &CalendarDate, |
| 318 | duration: Option<&CalendarDuration>, |
| 319 | count: Option<i32>, |
| 320 | _holidays: Option<&Vec<CalClockInterval>>, |
| 321 | ) -> Outcome<()> { |
| 322 | let month_set = ok!(self.month_set.as_ref() |
| 323 | .ok_or_else(|| err!("By-explicit-months rule requires month_set"; Invalid, Input))); |
| 324 | |
| 325 | let day_incrementor = ok!(self.day_incrementor.as_ref() |
| 326 | .ok_or_else(|| err!("By-explicit-months rule requires day_incrementor"; Invalid, Input))); |
| 327 | |
| 328 | let start_year = reference_date.year(); |
| 329 | let max_count = count.unwrap_or(1000); |
| 330 | let mut generated = 0; |
| 331 | |
| 332 | // Generate for multiple years if needed |
| 333 | for year_offset in -5..=5 { // Search 5 years before and after |
| 334 | if generated >= max_count { |
| 335 | break; |
| 336 | } |
| 337 | |
| 338 | let current_year = start_year + year_offset; |
| 339 | |
| 340 | for &month in month_set { |
| 341 | if generated >= max_count { |
| 342 | break; |
| 343 | } |
| 344 | |
| 345 | // Use day incrementor to find the target date in this month |
| 346 | let _month_start = res!(CalendarDate::from_ymd(current_year, month, 1, reference_date.zone().clone())); |
| 347 | |
| 348 | if let Ok(target_date) = day_incrementor.calculate_date(current_year, month.of(), reference_date.zone().clone()) { |
| 349 | let should_include = match task { |
| 350 | GeneratorTask::All => { |
| 351 | if let Some(dur) = duration { |
| 352 | let end_date = res!(reference_date.add_calendar_duration(dur)); |
| 353 | target_date >= *reference_date && target_date <= end_date |
| 354 | } else { |
| 355 | true |
| 356 | } |
| 357 | }, |
| 358 | GeneratorTask::AllOnOrBefore | GeneratorTask::NOnOrBefore => { |
| 359 | target_date <= *reference_date |
| 360 | }, |
| 361 | GeneratorTask::AllOnOrAfter | GeneratorTask::NOnOrAfter => { |
| 362 | target_date >= *reference_date |
| 363 | }, |
| 364 | }; |
| 365 | |
| 366 | if should_include { |
| 367 | results.push(target_date); |
| 368 | generated += 1; |
| 369 | } |
| 370 | } |
| 371 | } |
| 372 | } |
| 373 | |
| 374 | // Sort results chronologically |
| 375 | results.sort(); |
| 376 | |
| 377 | Ok(()) |
| 378 | } |
| 379 | |
| 380 | fn generate_by_regular_months( |
| 381 | &self, |
| 382 | results: &mut Vec<CalendarDate>, |
| 383 | task: GeneratorTask, |
| 384 | reference_date: &CalendarDate, |
| 385 | duration: Option<&CalendarDuration>, |
| 386 | count: Option<i32>, |
| 387 | _holidays: Option<&Vec<CalClockInterval>>, |
| 388 | ) -> Outcome<()> { |
| 389 | let skip_months = ok!(self.skip_months.as_ref() |
| 390 | .ok_or_else(|| err!("By-regular-months rule requires skip_months"; Invalid, Input))); |
| 391 | |
| 392 | let day_incrementor = ok!(self.day_incrementor.as_ref() |
| 393 | .ok_or_else(|| err!("By-regular-months rule requires day_incrementor"; Invalid, Input))); |
| 394 | |
| 395 | let start_month = self.start_month.as_ref() |
| 396 | .map(|m| m.get_month_of_year()) |
| 397 | .unwrap_or(reference_date.month_of_year()); |
| 398 | |
| 399 | let skip_interval = skip_months.of() as i32; |
| 400 | let max_count = count.unwrap_or(1000); |
| 401 | let mut generated = 0; |
| 402 | |
| 403 | let mut current_date = reference_date.clone(); |
| 404 | |
| 405 | // Adjust to start month if needed |
| 406 | if current_date.month_of_year() != start_month { |
| 407 | let target_month = start_month as i32; |
| 408 | let current_month = current_date.month_of_year() as i32; |
| 409 | let month_diff = target_month - current_month; |
| 410 | |
| 411 | current_date = res!(current_date.add_months(month_diff)); |
| 412 | } |
| 413 | |
| 414 | loop { |
| 415 | if generated >= max_count { |
| 416 | break; |
| 417 | } |
| 418 | |
| 419 | // Calculate target date using day incrementor |
| 420 | if let Ok(target_date) = day_incrementor.calculate_date(current_date.year(), current_date.month_of_year().of(), current_date.zone().clone()) { |
| 421 | let should_include = match task { |
| 422 | GeneratorTask::All => { |
| 423 | if let Some(dur) = duration { |
| 424 | let end_date = res!(reference_date.add_calendar_duration(dur)); |
| 425 | target_date >= *reference_date && target_date <= end_date |
| 426 | } else { |
| 427 | true |
| 428 | } |
| 429 | }, |
| 430 | GeneratorTask::AllOnOrBefore | GeneratorTask::NOnOrBefore => { |
| 431 | target_date <= *reference_date |
| 432 | }, |
| 433 | GeneratorTask::AllOnOrAfter | GeneratorTask::NOnOrAfter => { |
| 434 | target_date >= *reference_date |
| 435 | }, |
| 436 | }; |
| 437 | |
| 438 | if should_include { |
| 439 | results.push(target_date); |
| 440 | generated += 1; |
| 441 | } |
| 442 | } |
| 443 | |
| 444 | // Move to next interval |
| 445 | current_date = res!(current_date.add_months(skip_interval)); |
| 446 | |
| 447 | // Bounds check |
| 448 | if current_date.year() > 3000 || current_date.year() < 1 { |
| 449 | break; |
| 450 | } |
| 451 | } |
| 452 | |
| 453 | Ok(()) |
| 454 | } |
| 455 | |
| 456 | fn generate_by_days( |
| 457 | &self, |
| 458 | results: &mut Vec<CalendarDate>, |
| 459 | task: GeneratorTask, |
| 460 | reference_date: &CalendarDate, |
| 461 | duration: Option<&CalendarDuration>, |
| 462 | count: Option<i32>, |
| 463 | ) -> Outcome<()> { |
| 464 | let skip_days = ok!(self.skip_days.as_ref() |
| 465 | .ok_or_else(|| err!("By-days rule requires skip_days"; Invalid, Input))); |
| 466 | |
| 467 | let skip_interval = skip_days.of() as i32; |
| 468 | let max_count = count.unwrap_or(1000); |
| 469 | let mut generated = 0; |
| 470 | |
| 471 | let mut current_date = reference_date.clone(); |
| 472 | |
| 473 | loop { |
| 474 | if generated >= max_count { |
| 475 | break; |
| 476 | } |
| 477 | |
| 478 | let should_include = match task { |
| 479 | GeneratorTask::All => { |
| 480 | if let Some(dur) = duration { |
| 481 | let end_date = res!(reference_date.add_calendar_duration(dur)); |
| 482 | current_date >= *reference_date && current_date <= end_date |
| 483 | } else { |
| 484 | true |
| 485 | } |
| 486 | }, |
| 487 | GeneratorTask::AllOnOrBefore | GeneratorTask::NOnOrBefore => { |
| 488 | current_date <= *reference_date |
| 489 | }, |
| 490 | GeneratorTask::AllOnOrAfter | GeneratorTask::NOnOrAfter => { |
| 491 | current_date >= *reference_date |
| 492 | }, |
| 493 | }; |
| 494 | |
| 495 | if should_include { |
| 496 | results.push(current_date.clone()); |
| 497 | generated += 1; |
| 498 | } |
| 499 | |
| 500 | // Move to next interval |
| 501 | match task { |
| 502 | GeneratorTask::AllOnOrBefore | GeneratorTask::NOnOrBefore => { |
| 503 | current_date = res!(current_date.add_days(skip_interval)); |
| 504 | }, |
| 505 | _ => { |
| 506 | current_date = res!(current_date.add_days(skip_interval)); |
| 507 | }, |
| 508 | } |
| 509 | |
| 510 | // Bounds check |
| 511 | if current_date.year() > 3000 || current_date.year() < 1 { |
| 512 | break; |
| 513 | } |
| 514 | } |
| 515 | |
| 516 | Ok(()) |
| 517 | } |
| 518 | |
| 519 | pub fn rule_type(&self) -> &RuleType { |
| 520 | &self.rule_type |
| 521 | } |
| 522 | |
| 523 | pub fn duration(&self) -> Option<&CalendarDuration> { |
| 524 | self.duration.as_ref() |
| 525 | } |
| 526 | |
| 527 | pub fn count_limit(&self) -> Option<i32> { |
| 528 | self.count_limit |
| 529 | } |
| 530 | |
| 531 | pub fn start_date(&self) -> Option<&CalendarDate> { |
| 532 | self.start_date.as_ref() |
| 533 | } |
| 534 | } |
| 535 | |
| 536 | impl std::fmt::Display for CalendarRule { |
| 537 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
| 538 | write!(f, "CalendarRule({:?})", self.rule_type) |
| 539 | } |
| 540 | } |