Oregami
Repositories/oxedyne/fe2o3

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

12.0 KiB, 54 runs

created by r1870400018:6373, 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//! Spans of time, in three flavours: a CalClockRange between two instants, a
2//! DateRange over whole days, and a TimeRange within a day.
3//!
4//! They do not agree on their end bounds, so check each one.
5//!
6//! [Written with AI entirely](https://need2know.ai/entirely-ai/code)\
7//! Anthropic Claude
8
9use crate::{
10 calendar::CalendarDate,
11 clock::ClockTime,
12 core::Duration,
13 time::{CalClock, CalClockDuration},
14};
15
16use oxedyne_fe2o3_core::prelude::*;
17
18#[derive(Clone, Debug, PartialEq)]
19pub struct CalClockRange {
20 start: CalClock,
21 end: CalClock,
22}
23
24impl CalClockRange {
25 /// The start is inclusive and the end exclusive.
26 pub fn new(start: CalClock, end: CalClock) -> Outcome<Self> {
27 if start > end {
28 return Err(err!("Start time cannot be after end time"; Invalid, Input));
29 }
30 Ok(Self { start, end })
31 }
32
33 pub fn from_start_and_duration(start: CalClock, duration: CalClockDuration) -> Outcome<Self> {
34 let end = res!(start.add_duration(&duration));
35 Self::new(start, end)
36 }
37
38 pub fn start(&self) -> &CalClock {
39 &self.start
40 }
41
42 pub fn end(&self) -> &CalClock {
43 &self.end
44 }
45
46 pub fn duration(&self) -> Outcome<CalClockDuration> {
47 self.start.duration_until(&self.end)
48 }
49
50 pub fn contains(&self, time: &CalClock) -> Outcome<bool> {
51 Ok(time >= &self.start && time < &self.end)
52 }
53
54 pub fn overlaps(&self, other: &Self) -> Outcome<bool> {
55 Ok(!(self.end <= other.start || other.end <= self.start))
56 }
57
58 pub fn intersection(&self, other: &Self) -> Outcome<Option<Self>> {
59 if !res!(self.overlaps(other)) {
60 return Ok(None);
61 }
62
63 let start = if self.start > other.start { self.start.clone() } else { other.start.clone() };
64 let end = if self.end < other.end { self.end.clone() } else { other.end.clone() };
65
66 Ok(Some(res!(Self::new(start, end))))
67 }
68
69 /// Adjacent ranges unite; disjoint ones give None.
70 pub fn union(&self, other: &Self) -> Outcome<Option<Self>> {
71 // Check if ranges overlap or are adjacent
72 let gap_duration = if self.end <= other.start {
73 res!(self.end.duration_until(&other.start))
74 } else if other.end <= self.start {
75 res!(other.end.duration_until(&self.start))
76 } else {
77 // Ranges overlap
78 CalClockDuration::from_nanos(0)
79 };
80
81 // Only union if ranges overlap or are adjacent (no gap)
82 if res!(gap_duration.to_nanos()) > 0 {
83 return Ok(None);
84 }
85
86 let start = if self.start < other.start { self.start.clone() } else { other.start.clone() };
87 let end = if self.end > other.end { self.end.clone() } else { other.end.clone() };
88
89 Ok(Some(res!(Self::new(start, end))))
90 }
91
92 /// A split point outside the range is an error, and one at either end
93 /// gives a single range rather than two.
94 pub fn split_at(&self, split_time: &CalClock) -> Outcome<Vec<Self>> {
95 if !res!(self.contains(split_time)) {
96 return Err(err!("Split time is not within the range"; Invalid, Input));
97 }
98
99 let mut result = Vec::new();
100
101 // First range: start to split_time
102 if split_time > &self.start {
103 result.push(res!(Self::new(self.start.clone(), split_time.clone())));
104 }
105
106 // Second range: split_time to end
107 if split_time < &self.end {
108 result.push(res!(Self::new(split_time.clone(), self.end.clone())));
109 }
110
111 Ok(result)
112 }
113
114 pub fn is_empty(&self) -> bool {
115 self.start == self.end
116 }
117
118 pub fn extend_to_include(&mut self, time: &CalClock) -> Outcome<()> {
119 if time < &self.start {
120 self.start = time.clone();
121 } else if time >= &self.end {
122 self.end = res!(time.add_millis(1)); // Make end exclusive
123 }
124 Ok(())
125 }
126}
127
128#[derive(Clone, Debug, PartialEq)]
129pub struct DateRange {
130 start: CalendarDate,
131 end: CalendarDate,
132}
133
134impl DateRange {
135 /// Both ends are inclusive, unlike CalClockRange.
136 pub fn new(start: CalendarDate, end: CalendarDate) -> Outcome<Self> {
137 if start > end {
138 return Err(err!("Start date cannot be after end date"; Invalid, Input));
139 }
140 Ok(Self { start, end })
141 }
142
143 pub fn start(&self) -> &CalendarDate {
144 &self.start
145 }
146
147 pub fn end(&self) -> &CalendarDate {
148 &self.end
149 }
150
151 pub fn days(&self) -> Outcome<i32> {
152 let start_day_number = res!(self.start.to_day_number());
153 let end_day_number = res!(self.end.to_day_number());
154 Ok((end_day_number - start_day_number + 1) as i32)
155 }
156
157 pub fn contains(&self, date: &CalendarDate) -> bool {
158 date >= &self.start && date <= &self.end
159 }
160
161 pub fn overlaps(&self, other: &Self) -> bool {
162 !(self.end < other.start || other.end < self.start)
163 }
164
165 pub fn all_dates(&self) -> Outcome<Vec<CalendarDate>> {
166 let mut dates = Vec::new();
167 let mut current = self.start.clone();
168
169 while current <= self.end {
170 dates.push(current.clone());
171 current = res!(current.add_days(1));
172 }
173
174 Ok(dates)
175 }
176
177 pub fn business_days(&self) -> Outcome<Vec<CalendarDate>> {
178 let all_dates = res!(self.all_dates());
179 Ok(all_dates.into_iter().filter(|date| date.is_business_day()).collect())
180 }
181
182 pub fn weekends(&self) -> Outcome<Vec<CalendarDate>> {
183 let all_dates = res!(self.all_dates());
184 Ok(all_dates.into_iter().filter(|date| date.is_weekend()).collect())
185 }
186}
187
188#[derive(Clone, Debug, PartialEq)]
189pub struct TimeRange {
190 start: ClockTime,
191 end: ClockTime,
192}
193
194impl TimeRange {
195 /// Both ends are inclusive, and an end at or before the start means the
196 /// range wraps midnight.
197 pub fn new(start: ClockTime, end: ClockTime) -> Outcome<Self> {
198 // Allow ranges that span midnight (end < start)
199 Ok(Self { start, end })
200 }
201
202 pub fn start(&self) -> &ClockTime {
203 &self.start
204 }
205
206 pub fn end(&self) -> &ClockTime {
207 &self.end
208 }
209
210 pub fn spans_midnight(&self) -> bool {
211 self.end <= self.start
212 }
213
214 pub fn contains(&self, time: &ClockTime) -> bool {
215 if self.spans_midnight() {
216 // Range spans midnight, so time is either >= start OR <= end
217 time >= &self.start || time <= &self.end
218 } else {
219 // Normal range
220 time >= &self.start && time <= &self.end
221 }
222 }
223
224 pub fn duration(&self) -> Outcome<crate::clock::ClockDuration> {
225 use crate::clock::ClockDuration;
226
227 if self.spans_midnight() {
228 // Calculate duration across midnight
229 let nanos_to_midnight = (24 * 60 * 60 * 1_000_000_000) - self.start.to_nanos_of_day() as i64;
230 let nanos_from_midnight = self.end.to_nanos_of_day() as i64;
231 let total_nanos = nanos_to_midnight + nanos_from_midnight;
232 Ok(ClockDuration::from_nanos(total_nanos))
233 } else {
234 let start_nanos = self.start.to_nanos_of_day() as i64;
235 let end_nanos = self.end.to_nanos_of_day() as i64;
236 Ok(ClockDuration::from_nanos(end_nanos - start_nanos))
237 }
238 }
239
240 pub fn split_at(&self, split_time: &ClockTime) -> Outcome<Vec<Self>> {
241 if !self.contains(split_time) {
242 return Err(err!("Split time is not within the range"; Invalid, Input));
243 }
244
245 let mut result = Vec::new();
246
247 if self.spans_midnight() {
248 // Handle midnight-spanning ranges
249 if split_time >= &self.start {
250 // Split is in the first part (before midnight)
251 result.push(res!(Self::new(self.start.clone(), split_time.clone())));
252 if split_time != &self.start {
253 // Add midnight-spanning part
254 result.push(res!(Self::new(split_time.clone(), self.end.clone())));
255 }
256 } else {
257 // Split is in the second part (after midnight)
258 result.push(res!(Self::new(self.start.clone(), self.end.clone()))); // This will span midnight
259 result.push(res!(Self::new(split_time.clone(), self.end.clone())));
260 }
261 } else {
262 // Normal range
263 if split_time > &self.start {
264 result.push(res!(Self::new(self.start.clone(), split_time.clone())));
265 }
266 if split_time < &self.end {
267 result.push(res!(Self::new(split_time.clone(), self.end.clone())));
268 }
269 }
270
271 Ok(result)
272 }
273}
274
275// ========================================================================
276// Range Collection Operations
277// ========================================================================
278
279#[derive(Clone, Debug)]
280pub struct CalClockRangeSet {
281 ranges: Vec<CalClockRange>,
282}
283
284impl CalClockRangeSet {
285 pub fn new() -> Self {
286 Self {
287 ranges: Vec::new(),
288 }
289 }
290
291 pub fn from_ranges(ranges: Vec<CalClockRange>) -> Outcome<Self> {
292 let mut set = Self::new();
293 for range in ranges {
294 res!(set.add_range(range));
295 }
296 Ok(set)
297 }
298
299 /// Overlapping and adjacent ranges are merged on the way in, so the set
300 /// never holds two ranges that touch.
301 pub fn add_range(&mut self, new_range: CalClockRange) -> Outcome<()> {
302 // Find overlapping ranges
303 let mut merged = new_range;
304 let mut to_remove = Vec::new();
305
306 for (i, existing) in self.ranges.iter().enumerate() {
307 if let Some(union) = res!(merged.union(existing)) {
308 merged = union;
309 to_remove.push(i);
310 }
311 }
312
313 // Remove merged ranges (in reverse order to maintain indices)
314 for &i in to_remove.iter().rev() {
315 self.ranges.remove(i);
316 }
317
318 // Add the merged range
319 self.ranges.push(merged);
320
321 // Sort ranges by start time
322 self.ranges.sort_by(|a, b| a.start().cmp(b.start()));
323
324 Ok(())
325 }
326
327 pub fn ranges(&self) -> &[CalClockRange] {
328 &self.ranges
329 }
330
331 pub fn contains(&self, time: &CalClock) -> Outcome<bool> {
332 for range in &self.ranges {
333 if res!(range.contains(time)) {
334 return Ok(true);
335 }
336 }
337 Ok(false)
338 }
339
340 pub fn gaps(&self) -> Outcome<Vec<CalClockRange>> {
341 if self.ranges.len() <= 1 {
342 return Ok(Vec::new());
343 }
344
345 let mut gaps = Vec::new();
346
347 for i in 0..self.ranges.len() - 1 {
348 let current_end = self.ranges[i].end();
349 let next_start = self.ranges[i + 1].start();
350
351 if current_end < next_start {
352 gaps.push(res!(CalClockRange::new(current_end.clone(), next_start.clone())));
353 }
354 }
355
356 Ok(gaps)
357 }
358
359 pub fn total_duration(&self) -> Outcome<CalClockDuration> {
360 let mut total_nanos = 0i64;
361
362 for range in &self.ranges {
363 let duration = res!(range.duration());
364 total_nanos += res!(duration.to_nanos());
365 }
366
367 Ok(CalClockDuration::from_nanos(total_nanos))
368 }
369
370 pub fn intersection(&self, other: &Self) -> Outcome<Self> {
371 let mut result = Self::new();
372
373 for range1 in &self.ranges {
374 for range2 in &other.ranges {
375 if let Some(intersection) = res!(range1.intersection(range2)) {
376 res!(result.add_range(intersection));
377 }
378 }
379 }
380
381 Ok(result)
382 }
383
384 pub fn is_empty(&self) -> bool {
385 self.ranges.is_empty()
386 }
387
388 pub fn len(&self) -> usize {
389 self.ranges.len()
390 }
391}
392
393impl Default for CalClockRangeSet {
394 fn default() -> Self {
395 Self::new()
396 }
397}