Oregami
Repositories/oxedyne/fe2o3

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
4use oxedyne_fe2o3_core::prelude::*;
5use crate::{
6 calendar::{CalendarDate, CalendarDuration, CalendarMonth, CalendarYear, CalendarDay, DayIncrementor, MonthPeriod},
7 constant::MonthOfYear,
8 time::CalClockInterval,
9};
10use std::collections::BTreeSet;
11
12#[derive(Clone, Debug, PartialEq)]
13pub enum RuleType {
14 ByYears,
15 ByExplicitMonths,
16 ByRegularMonths,
17 ByDays,
18}
19
20#[derive(Clone, Debug, PartialEq)]
21enum GeneratorTask {
22 All,
23 AllOnOrBefore,
24 AllOnOrAfter,
25 NOnOrBefore,
26 NOnOrAfter,
27}
28
29#[derive(Clone, Debug)]
30pub 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
49impl 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
536impl 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}