Oregami
Repositories/oxedyne/fe2o3

oxedyne/fe2o3/fe2o3_datime/src/interval/schedule.rs

12.4 KiB, 59 runs

created by r1870400018:6377, 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//! Schedules: named collections of events, each an interval with an optional
2//! recurrence, that can be searched by date and checked for conflicts.
3//!
4//! [Written with AI entirely](https://need2know.ai/entirely-ai/code)\
5//! Anthropic Claude
6
7use crate::{
8 calendar::CalendarDate,
9 interval::{CalClockRange, RecurrencePattern},
10 time::CalClock,
11};
12
13use oxedyne_fe2o3_core::prelude::*;
14
15
16#[derive(Clone, Debug)]
17pub struct ScheduleEvent {
18 id: String,
19 range: CalClockRange,
20 title: String,
21 description: Option<String>,
22 recurrence: Option<RecurrencePattern>,
23 priority: u8, // higher number wins
24 flexible: bool, // may be moved to clear a conflict
25}
26
27impl ScheduleEvent {
28 pub fn new<S: Into<String>>(
29 id: S,
30 range: CalClockRange,
31 title: S,
32 ) -> Self {
33 Self {
34 id: id.into(),
35 range,
36 title: title.into(),
37 description: None,
38 recurrence: None,
39 priority: 0,
40 flexible: false,
41 }
42 }
43
44 pub fn description<S: Into<String>>(mut self, description: S) -> Self {
45 self.description = Some(description.into());
46 self
47 }
48
49 pub fn recurrence(mut self, pattern: RecurrencePattern) -> Self {
50 self.recurrence = Some(pattern);
51 self
52 }
53
54 pub fn priority(mut self, priority: u8) -> Self {
55 self.priority = priority;
56 self
57 }
58
59 pub fn flexible(mut self, flexible: bool) -> Self {
60 self.flexible = flexible;
61 self
62 }
63
64 pub fn id(&self) -> &str {
65 &self.id
66 }
67
68 pub fn range(&self) -> &CalClockRange {
69 &self.range
70 }
71
72 pub fn title(&self) -> &str {
73 &self.title
74 }
75
76 pub fn get_description(&self) -> Option<&str> {
77 self.description.as_deref()
78 }
79
80 pub fn get_recurrence(&self) -> Option<&RecurrencePattern> {
81 self.recurrence.as_ref()
82 }
83
84 pub fn get_priority(&self) -> u8 {
85 self.priority
86 }
87
88 pub fn is_flexible(&self) -> bool {
89 self.flexible
90 }
91
92 pub fn occurrences_in_range(
93 &self,
94 start_date: &CalendarDate,
95 end_date: &CalendarDate,
96 ) -> Outcome<Vec<CalClockRange>> {
97 if let Some(ref recurrence) = self.get_recurrence() {
98 let occurrences = res!(recurrence.occurrences_in_range(start_date, end_date));
99 let duration = res!(self.range.duration());
100
101 let mut ranges = Vec::new();
102 for occurrence in occurrences {
103 let end_time = res!(occurrence.add_duration(&duration));
104 ranges.push(res!(CalClockRange::new(occurrence, end_time)));
105 }
106
107 Ok(ranges)
108 } else {
109 // Single occurrence
110 if self.range.start().date() >= start_date && self.range.start().date() <= end_date {
111 Ok(vec![self.range.clone()])
112 } else {
113 Ok(vec![])
114 }
115 }
116 }
117}
118
119#[derive(Debug)]
120pub struct Schedule {
121 events: Vec<ScheduleEvent>,
122 name: String,
123}
124
125impl Schedule {
126 pub fn new<S: Into<String>>(name: S) -> Self {
127 Self {
128 events: Vec::new(),
129 name: name.into(),
130 }
131 }
132
133 pub fn name(&self) -> &str {
134 &self.name
135 }
136
137 pub fn add_event(&mut self, event: ScheduleEvent) {
138 self.events.push(event);
139 }
140
141 pub fn remove_event(&mut self, event_id: &str) -> bool {
142 if let Some(pos) = self.events.iter().position(|e| e.id() == event_id) {
143 self.events.remove(pos);
144 true
145 } else {
146 false
147 }
148 }
149
150 pub fn events(&self) -> &[ScheduleEvent] {
151 &self.events
152 }
153
154 pub fn find_event(&self, event_id: &str) -> Option<&ScheduleEvent> {
155 self.events.iter().find(|e| e.id() == event_id)
156 }
157
158 pub fn find_event_mut(&mut self, event_id: &str) -> Option<&mut ScheduleEvent> {
159 self.events.iter_mut().find(|e| e.id() == event_id)
160 }
161
162 pub fn events_in_range(
163 &self,
164 start_date: &CalendarDate,
165 end_date: &CalendarDate,
166 ) -> Outcome<Vec<(String, CalClockRange)>> {
167 let mut result = Vec::new();
168
169 for event in &self.events {
170 let occurrences = res!(event.occurrences_in_range(start_date, end_date));
171 for occurrence in occurrences {
172 result.push((event.id().to_string(), occurrence));
173 }
174 }
175
176 // Sort by start time
177 result.sort_by(|a, b| a.1.start().cmp(b.1.start()));
178
179 Ok(result)
180 }
181
182 pub fn events_on_date(&self, date: &CalendarDate) -> Outcome<Vec<(String, CalClockRange)>> {
183 self.events_in_range(date, date)
184 }
185
186 /// Overlapping events are gathered into groups, so three events that all
187 /// clash appear once rather than as three pairs.
188 pub fn detect_conflicts(&self, start_date: &CalendarDate, end_date: &CalendarDate) -> Outcome<Vec<ConflictGroup>> {
189 let events_in_range = res!(self.events_in_range(start_date, end_date));
190 let mut conflicts: Vec<ConflictGroup> = Vec::new();
191
192 for i in 0..events_in_range.len() {
193 for j in (i + 1)..events_in_range.len() {
194 let (ref event1_id, ref range1) = &events_in_range[i];
195 let (ref event2_id, ref range2) = &events_in_range[j];
196
197 if res!(range1.overlaps(range2)) {
198 // Check if we already have a conflict group containing these events
199 let mut found_group = None;
200 for (idx, group) in conflicts.iter_mut().enumerate() {
201 if group.contains_event(&event1_id) || group.contains_event(&event2_id) {
202 found_group = Some(idx);
203 break;
204 }
205 }
206
207 if let Some(idx) = found_group {
208 conflicts[idx].add_event(event1_id.clone(), range1.clone());
209 conflicts[idx].add_event(event2_id.clone(), range2.clone());
210 } else {
211 let mut group = ConflictGroup::new();
212 group.add_event(event1_id.clone(), range1.clone());
213 group.add_event(event2_id.clone(), range2.clone());
214 conflicts.push(group);
215 }
216 }
217 }
218 }
219
220 Ok(conflicts)
221 }
222
223 pub fn event_count(&self) -> usize {
224 self.events.len()
225 }
226
227 pub fn is_empty(&self) -> bool {
228 self.events.is_empty()
229 }
230
231 pub fn clear(&mut self) {
232 self.events.clear();
233 }
234}
235
236#[derive(Debug)]
237pub struct ConflictGroup {
238 events: Vec<(String, CalClockRange)>, // id and range of each
239}
240
241impl ConflictGroup {
242 pub fn new() -> Self {
243 Self {
244 events: Vec::new(),
245 }
246 }
247
248 pub fn add_event(&mut self, event_id: String, range: CalClockRange) {
249 // Only add if not already present
250 if !self.events.iter().any(|(id, _)| id == &event_id) {
251 self.events.push((event_id, range));
252 }
253 }
254
255 pub fn contains_event(&self, event_id: &str) -> bool {
256 self.events.iter().any(|(id, _)| id == event_id)
257 }
258
259 pub fn events(&self) -> &[(String, CalClockRange)] {
260 &self.events
261 }
262
263 pub fn event_count(&self) -> usize {
264 self.events.len()
265 }
266
267 pub fn overall_range(&self) -> Outcome<Option<CalClockRange>> {
268 if self.events.is_empty() {
269 return Ok(None);
270 }
271
272 let mut min_start = self.events[0].1.start().clone();
273 let mut max_end = self.events[0].1.end().clone();
274
275 for (_, range) in &self.events[1..] {
276 if range.start() < &min_start {
277 min_start = range.start().clone();
278 }
279 if range.end() > &max_end {
280 max_end = range.end().clone();
281 }
282 }
283
284 Ok(Some(res!(CalClockRange::new(min_start, max_end))))
285 }
286}
287
288impl Default for ConflictGroup {
289 fn default() -> Self {
290 Self::new()
291 }
292}
293
294#[derive(Debug)]
295pub struct ScheduleBuilder {
296 schedule: Schedule,
297}
298
299impl ScheduleBuilder {
300 pub fn new<S: Into<String>>(name: S) -> Self {
301 Self {
302 schedule: Schedule::new(name),
303 }
304 }
305
306 pub fn add_event(mut self, event: ScheduleEvent) -> Self {
307 self.schedule.add_event(event);
308 self
309 }
310
311 pub fn add_simple_event<S: Into<String>>(
312 mut self,
313 id: S,
314 range: CalClockRange,
315 title: S,
316 ) -> Self {
317 let event = ScheduleEvent::new(id, range, title);
318 self.schedule.add_event(event);
319 self
320 }
321
322 pub fn add_recurring_event<S: Into<String>>(
323 mut self,
324 id: S,
325 range: CalClockRange,
326 title: S,
327 recurrence: RecurrencePattern,
328 ) -> Self {
329 let event = ScheduleEvent::new(id, range, title).recurrence(recurrence);
330 self.schedule.add_event(event);
331 self
332 }
333
334 pub fn build(self) -> Schedule {
335 self.schedule
336 }
337}
338
339// ========================================================================
340// Common Schedule Patterns
341// ========================================================================
342
343impl Schedule {
344 pub fn work_schedule(name: String, start_date: CalendarDate) -> Outcome<Self> {
345 use crate::interval::RecurrenceRule;
346 use std::collections::HashSet;
347
348 let mut weekdays = HashSet::new();
349 weekdays.insert(crate::constant::DayOfWeek::Monday);
350 weekdays.insert(crate::constant::DayOfWeek::Tuesday);
351 weekdays.insert(crate::constant::DayOfWeek::Wednesday);
352 weekdays.insert(crate::constant::DayOfWeek::Thursday);
353 weekdays.insert(crate::constant::DayOfWeek::Friday);
354
355 let work_start = res!(CalClock::new(
356 start_date.year(),
357 start_date.month(),
358 start_date.day(),
359 9, 0, 0, 0,
360 start_date.zone().clone()
361 ));
362
363 let work_end = res!(CalClock::new(
364 start_date.year(),
365 start_date.month(),
366 start_date.day(),
367 17, 0, 0, 0,
368 start_date.zone().clone()
369 ));
370
371 let work_range = res!(CalClockRange::new(work_start.clone(), work_end));
372
373 let recurrence_rule = RecurrenceRule::new(crate::interval::Frequency::Weekly)
374 .by_weekday(weekdays);
375
376 let recurrence_pattern = RecurrencePattern::new(work_start, recurrence_rule);
377
378 let work_event = ScheduleEvent::new("work", work_range, "Work Hours")
379 .recurrence(recurrence_pattern)
380 .priority(5);
381
382 let mut schedule = Schedule::new(name);
383 schedule.add_event(work_event);
384
385 Ok(schedule)
386 }
387
388 pub fn class_schedule(name: String, start_date: CalendarDate) -> Outcome<Self> {
389 let mut schedule = Schedule::new(name);
390
391 // Add some example classes
392 let zone = start_date.zone().clone();
393
394 // Monday, Wednesday, Friday - Math class
395 let math_start = res!(CalClock::new(
396 start_date.year(), start_date.month(), start_date.day(),
397 10, 0, 0, 0, zone.clone()
398 ));
399 let math_end = res!(CalClock::new(
400 start_date.year(), start_date.month(), start_date.day(),
401 11, 30, 0, 0, zone.clone()
402 ));
403 let math_range = res!(CalClockRange::new(math_start.clone(), math_end));
404
405 use std::collections::HashSet;
406 let mut mwf_days = HashSet::new();
407 mwf_days.insert(crate::constant::DayOfWeek::Monday);
408 mwf_days.insert(crate::constant::DayOfWeek::Wednesday);
409 mwf_days.insert(crate::constant::DayOfWeek::Friday);
410
411 let math_rule = crate::interval::RecurrenceRule::new(crate::interval::Frequency::Weekly)
412 .by_weekday(mwf_days);
413 let math_pattern = RecurrencePattern::new(math_start, math_rule);
414
415 let math_event = ScheduleEvent::new("math", math_range, "Mathematics")
416 .recurrence(math_pattern)
417 .description("Advanced Calculus")
418 .priority(8);
419
420 schedule.add_event(math_event);
421
422 Ok(schedule)
423 }
424}