Oregami
Repositories/oxedyne/fe2o3

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

7.5 KiB, 25 runs

created by r1870400018:6319, 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 crate::{
5 core::{
6 Interval,
7 Time,
8 },
9 clock::{
10 ClockTime,
11 ClockDuration,
12 },
13 time::CalClockZone,
14};
15
16use oxedyne_fe2o3_core::prelude::*;
17
18use std::fmt;
19
20/// A span within one day, inclusive of both endpoints, and holding start ≤ finish
21/// from construction onwards.
22///
23/// ```ignore
24/// use oxedyne_fe2o3_datime::{
25/// clock::{ClockTime, ClockInterval},
26/// time::CalClockZone,
27/// }res!();
28///
29/// let zone = CalClockZone::utc()res!();
30/// let start = ClockTime::new(10, 30, 0, 0, zone.clone())?res!();
31/// let finish = ClockTime::new(14, 45, 30, 0, zone)?res!();
32///
33/// let interval = ClockInterval::new(start, finish)?res!();
34/// assert_eq!(interval.duration().total_hours(), 4)res!();
35/// ```
36#[derive(Clone, Debug, PartialEq, Eq)]
37pub struct ClockInterval {
38 start: ClockTime,
39 finish: ClockTime,
40}
41
42impl ClockInterval {
43 pub fn new(start: ClockTime, finish: ClockTime) -> Outcome<Self> {
44 if start.is_after(&finish) {
45 return Err(err!(
46 "Start time {} must not be after finish time {}",
47 start,
48 finish;
49 Invalid, Input
50 ));
51 }
52 Ok(Self { start, finish })
53 }
54
55 pub fn new_unchecked(start: ClockTime, finish: ClockTime) -> Self {
56 Self { start, finish }
57 }
58
59 pub fn from_start_and_duration(
60 start: ClockTime,
61 duration: &ClockDuration
62 ) -> Outcome<Self> {
63 let (finish, day_carry) = res!(start.plus(duration));
64
65 if day_carry != 0 {
66 return Err(err!(
67 "Duration extends beyond single day boundary";
68 Invalid, Input
69 ));
70 }
71
72 Self::new(start, finish)
73 }
74
75 pub fn from_finish_and_duration(
76 finish: ClockTime,
77 duration: &ClockDuration
78 ) -> Outcome<Self> {
79 let (start, day_carry) = res!(finish.minus(duration));
80
81 if day_carry != 0 {
82 return Err(err!(
83 "Duration extends beyond single day boundary";
84 Invalid, Input
85 ));
86 }
87
88 Self::new(start, finish)
89 }
90
91 pub fn start(&self) -> &ClockTime {
92 &self.start
93 }
94
95 pub fn finish(&self) -> &ClockTime {
96 &self.finish
97 }
98
99 pub fn duration(&self) -> ClockDuration {
100 self.start.duration_until(&self.finish)
101 }
102
103 pub fn contains_time(&self, time: &ClockTime) -> bool {
104 time.or_later(&self.start) && time.or_earlier(&self.finish)
105 }
106
107 pub fn overlaps_with(&self, other: &Self) -> bool {
108 self.start.is_before(&other.finish) && other.start.is_before(&self.finish)
109 }
110
111 pub fn intersect_with(&self, other: &Self) -> Option<Self> {
112 if !self.overlaps_with(other) {
113 return None;
114 }
115
116 let start = if self.start.or_later(&other.start) {
117 self.start.clone()
118 } else {
119 other.start.clone()
120 };
121
122 let finish = if self.finish.or_earlier(&other.finish) {
123 self.finish.clone()
124 } else {
125 other.finish.clone()
126 };
127
128 Some(Self::new_unchecked(start, finish))
129 }
130
131 pub fn union_with(&self, other: &Self) -> Option<Self> {
132 // Check if intervals are contiguous or overlapping
133 if !self.overlaps_with(other) &&
134 !self.finish.eq(&other.start) &&
135 !other.finish.eq(&self.start) {
136 return None;
137 }
138
139 let start = if self.start.or_earlier(&other.start) {
140 self.start.clone()
141 } else {
142 other.start.clone()
143 };
144
145 let finish = if self.finish.or_later(&other.finish) {
146 self.finish.clone()
147 } else {
148 other.finish.clone()
149 };
150
151 Some(Self::new_unchecked(start, finish))
152 }
153
154 pub fn is_instant(&self) -> bool {
155 self.start.eq(&self.finish)
156 }
157
158 pub fn is_valid(&self) -> bool {
159 self.start.or_earlier(&self.finish)
160 }
161
162 pub fn to_zone(&self, zone: CalClockZone) -> Outcome<Self> {
163 // For clock intervals, this is administrative conversion
164 // (no actual time transformation since we don't have date context)
165 let start = res!(self.start.to_zone(zone.clone()));
166 let finish = res!(self.finish.to_zone(zone));
167
168 Ok(Self::new_unchecked(start, finish))
169 }
170}
171
172impl Interval<ClockDuration> for ClockInterval {
173 fn get_duration(&self) -> Outcome<ClockDuration> {
174 Ok(self.duration())
175 }
176
177 fn contains<T: Time>(&self, _time: &T) -> bool {
178 // This is a compile-time error if T is not ClockTime
179 // In practice, we'd use more sophisticated type checking or associated types
180 false
181 }
182
183 fn overlaps(&self, other: &Self) -> bool {
184 self.overlaps_with(other)
185 }
186}
187
188impl PartialOrd for ClockInterval {
189 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
190 Some(self.cmp(other))
191 }
192}
193
194impl Ord for ClockInterval {
195 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
196 self.start.cmp(&other.start)
197 .then_with(|| self.finish.cmp(&other.finish))
198 }
199}
200
201impl fmt::Display for ClockInterval {
202 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
203 write!(f, "[{} - {}]", self.start, self.finish)
204 }
205}
206
207#[cfg(test)]
208mod tests {
209 use super::*;
210
211 fn test_zone() -> CalClockZone {
212 CalClockZone::utc()
213 }
214
215 #[test]
216 fn test_interval_creation() {
217 let start = ClockTime::new(10, 0, 0, 0, test_zone()).unwrap();
218 let finish = ClockTime::new(11, 0, 0, 0, test_zone()).unwrap();
219
220 let interval = ClockInterval::new(start.clone(), finish.clone());
221 assert!(interval.is_ok());
222
223 // Test invalid order
224 let invalid = ClockInterval::new(finish, start);
225 assert!(invalid.is_err());
226 }
227
228 #[test]
229 fn test_interval_duration() {
230 let start = ClockTime::new(10, 0, 0, 0, test_zone()).unwrap();
231 let finish = ClockTime::new(11, 30, 0, 0, test_zone()).unwrap();
232
233 let interval = ClockInterval::new(start, finish).unwrap();
234 let duration = interval.duration();
235
236 assert_eq!(duration.total_minutes(), 90);
237 }
238
239 #[test]
240 fn test_interval_contains() {
241 let start = ClockTime::new(10, 0, 0, 0, test_zone()).unwrap();
242 let finish = ClockTime::new(12, 0, 0, 0, test_zone()).unwrap();
243 let interval = ClockInterval::new(start, finish).unwrap();
244
245 let inside = ClockTime::new(11, 0, 0, 0, test_zone()).unwrap();
246 let outside = ClockTime::new(13, 0, 0, 0, test_zone()).unwrap();
247
248 assert!(interval.contains_time(&inside));
249 assert!(!interval.contains_time(&outside));
250
251 // Test boundaries
252 assert!(interval.contains_time(interval.start()));
253 assert!(interval.contains_time(interval.finish()));
254 }
255
256 #[test]
257 fn test_interval_overlap() {
258 let interval1 = ClockInterval::new(
259 ClockTime::new(10, 0, 0, 0, test_zone()).unwrap(),
260 ClockTime::new(12, 0, 0, 0, test_zone()).unwrap(),
261 ).unwrap();
262
263 let interval2 = ClockInterval::new(
264 ClockTime::new(11, 0, 0, 0, test_zone()).unwrap(),
265 ClockTime::new(13, 0, 0, 0, test_zone()).unwrap(),
266 ).unwrap();
267
268 let interval3 = ClockInterval::new(
269 ClockTime::new(13, 0, 0, 0, test_zone()).unwrap(),
270 ClockTime::new(14, 0, 0, 0, test_zone()).unwrap(),
271 ).unwrap();
272
273 assert!(interval1.overlaps_with(&interval2));
274 assert!(!interval1.overlaps_with(&interval3));
275 }
276
277 #[test]
278 fn test_interval_intersection() {
279 let interval1 = ClockInterval::new(
280 ClockTime::new(10, 0, 0, 0, test_zone()).unwrap(),
281 ClockTime::new(12, 0, 0, 0, test_zone()).unwrap(),
282 ).unwrap();
283
284 let interval2 = ClockInterval::new(
285 ClockTime::new(11, 0, 0, 0, test_zone()).unwrap(),
286 ClockTime::new(13, 0, 0, 0, test_zone()).unwrap(),
287 ).unwrap();
288
289 let intersection = interval1.intersect_with(&interval2).unwrap();
290 assert_eq!(intersection.start().hour().of(), 11);
291 assert_eq!(intersection.finish().hour().of(), 12);
292 }
293
294 #[test]
295 fn test_from_duration() {
296 let start = ClockTime::new(10, 0, 0, 0, test_zone()).unwrap();
297 let duration = ClockDuration::from_hours(2);
298
299 let interval = ClockInterval::from_start_and_duration(start, &duration).unwrap();
300 assert_eq!(interval.finish().hour().of(), 12);
301 assert_eq!(interval.duration().total_hours(), 2);
302 }
303}