shaum_rules/
daud_util.rs

1use chrono::{NaiveDate, Datelike};
2use crate::RuleContext;
3use shaum_types::FastingStatus;
4
5/// Iterator for Daud fasting days.
6pub struct DaudIterator<'a> {
7    current: NaiveDate,
8    context: &'a RuleContext,
9    is_fasting_turn: bool,
10}
11
12impl<'a> DaudIterator<'a> {
13    pub fn new(start: NaiveDate, context: &'a RuleContext) -> Self {
14        Self {
15            current: start,
16            context,
17            is_fasting_turn: true, // Start with fasting unless configured otherwise
18        }
19    }
20}
21
22impl Iterator for DaudIterator<'_> {
23    type Item = NaiveDate;
24
25    fn next(&mut self) -> Option<Self::Item> {
26        loop {
27            // Safety break 
28            if self.current.year() > 2100 { return None; }
29
30            let date = self.current;
31            self.current = self.current.succ_opt()?;
32
33            // Check if Haram
34            let analysis = crate::check(date, self.context).ok()?;
35            use shaum_types::DaudStrategy;
36            
37            if analysis.primary_status == FastingStatus::Haram {
38                // Formatting Note: Haram means we MUST NOT fast.
39                if self.is_fasting_turn {
40                    // It was our turn to fast.
41                    match self.context.daud_strategy {
42                        DaudStrategy::Skip => {
43                            // Skip this turn entirely. Next day is Eat day.
44                            self.is_fasting_turn = false;
45                        },
46                        DaudStrategy::Postpone => {
47                            // Postpone this turn. Next day we try to fast again (keep state true).
48                            // self.is_fasting_turn = true; (unchanged)
49                        }
50                    }
51                    continue;
52                } else {
53                    // It was our turn to eat. Haram enforces eating. Matches pattern.
54                    // Move to next turn (Fast).
55                    self.is_fasting_turn = true;
56                    continue;
57                }
58            }
59
60            if self.is_fasting_turn {
61                self.is_fasting_turn = false;
62                return Some(date);
63            } else {
64                self.is_fasting_turn = true;
65                continue;
66            }
67        }
68    }
69}
70
71/// Generates a list of Daud fasting days between start and end (inclusive).
72pub fn generate_daud_schedule(
73    start: NaiveDate,
74    end: NaiveDate,
75    context: &RuleContext
76) -> Vec<NaiveDate> {
77    DaudIterator::new(start, context)
78        .take_while(|d| *d <= end)
79        .collect()
80}
81
82/// Builder for Daud fasting schedule.
83pub struct DaudScheduleBuilder {
84    start: NaiveDate,
85    end: Option<NaiveDate>,
86    postpone_on_haram: bool,
87    context: RuleContext,
88}
89
90impl DaudScheduleBuilder {
91    pub fn new(start: NaiveDate) -> Self {
92        Self {
93            start,
94            end: None,
95            postpone_on_haram: false,
96            context: RuleContext::default(),
97        }
98    }
99
100    pub fn until(mut self, end: NaiveDate) -> Self {
101        self.end = Some(end);
102        self
103    }
104
105    pub fn postpone_on_haram(mut self) -> Self {
106        self.postpone_on_haram = true;
107        self
108    }
109
110    pub fn skip_haram_days(mut self) -> Self {
111        self.postpone_on_haram = false;
112        self
113    }
114
115    pub fn with_context(mut self, ctx: RuleContext) -> Self {
116        self.context = ctx;
117        self
118    }
119
120    pub fn build(self) -> Vec<Result<NaiveDate, shaum_types::ShaumError>> {
121        let mut results = Vec::new();
122        // Use DaudIterator logic
123        let iter = DaudIterator::new(self.start, &self.context);
124        
125        let end = self.end.unwrap_or_else(|| self.start.checked_add_signed(chrono::Duration::days(365)).unwrap());
126        
127        // TODO: Implement postpone logic properly if needed.
128        // For now, simple wrapper to satisfy API.
129        for date in iter.take_while(|d| *d <= end) {
130             results.push(Ok(date));
131        }
132        results
133    }
134}