1use std::{
6 cmp::{max, min},
7 fmt::Display,
8 ops::{Add, Sub},
9};
10
11use lox_core::time::deltas::TimeDelta;
12use lox_test_utils::approx_eq::ApproxEq;
13use lox_test_utils::approx_eq::results::ApproxEqResults;
14
15use crate::{
16 Time,
17 offsets::{DefaultOffsetProvider, Offset},
18 time_scales::{Tai, TimeScale},
19 utc::{Utc, transformations::ToUtc},
20};
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
25pub struct Interval<T> {
26 start: T,
27 end: T,
28}
29
30impl<T: ApproxEq + std::fmt::Debug> ApproxEq for Interval<T> {
31 fn approx_eq(&self, rhs: &Self, atol: f64, rtol: f64) -> ApproxEqResults {
32 let mut results = ApproxEqResults::new();
33 results.merge("start", self.start.approx_eq(&rhs.start, atol, rtol));
34 results.merge("end", self.end.approx_eq(&rhs.end, atol, rtol));
35 results
36 }
37}
38
39impl<T> Interval<T> {
40 pub fn new(start: T, end: T) -> Self {
42 Interval { start, end }
43 }
44
45 pub fn start(&self) -> T
47 where
48 T: Copy,
49 {
50 self.start
51 }
52
53 pub fn end(&self) -> T
55 where
56 T: Copy,
57 {
58 self.end
59 }
60
61 pub fn duration(&self) -> TimeDelta
63 where
64 T: Sub<Output = TimeDelta> + Copy,
65 {
66 self.end - self.start
67 }
68
69 pub fn is_empty(&self) -> bool
71 where
72 T: Ord,
73 {
74 self.start >= self.end
75 }
76
77 pub fn contains_time(&self, time: T) -> bool
79 where
80 T: Ord,
81 {
82 self.start <= time && time < self.end
83 }
84
85 pub fn intersect(&self, other: Self) -> Self
87 where
88 T: Ord + Copy,
89 {
90 Interval {
91 start: max(self.start, other.start),
92 end: min(self.end, other.end),
93 }
94 }
95
96 pub fn overlaps(&self, other: Self) -> bool
98 where
99 T: Ord + Copy,
100 {
101 !self.intersect(other).is_empty()
102 }
103
104 pub fn step_by(&self, step: TimeDelta) -> IntervalStepIter<T>
114 where
115 T: Copy + Add<TimeDelta, Output = T> + PartialOrd,
116 {
117 assert!(
118 step.is_positive() || step.is_negative(),
119 "step must be non-zero"
120 );
121 let forward = self.start <= self.end;
122 let step = if forward == step.is_positive() {
123 step
124 } else {
125 -step
126 };
127 IntervalStepIter {
128 current: self.start,
129 end: self.end,
130 step,
131 forward,
132 }
133 }
134
135 pub fn linspace(&self, n: usize) -> Vec<T>
139 where
140 T: Copy + Add<TimeDelta, Output = T> + Sub<Output = TimeDelta>,
141 {
142 assert!(n >= 2, "linspace requires at least 2 points");
143 let duration = self.end - self.start;
144 let step_secs = duration.to_seconds().to_f64() / (n - 1) as f64;
145 (0..n)
146 .map(|i| self.start + TimeDelta::from_seconds_f64(step_secs * i as f64))
147 .collect()
148 }
149
150 pub fn contains(&self, other: &Self) -> bool
152 where
153 T: Ord,
154 {
155 self.start <= other.start && self.end >= other.end
156 }
157}
158
159pub struct IntervalStepIter<T> {
161 current: T,
162 end: T,
163 step: TimeDelta,
164 forward: bool,
165}
166
167impl<T> Iterator for IntervalStepIter<T>
168where
169 T: Copy + Add<TimeDelta, Output = T> + PartialOrd,
170{
171 type Item = T;
172
173 fn next(&mut self) -> Option<Self::Item> {
174 let done = if self.forward {
175 self.current > self.end
176 } else {
177 self.current < self.end
178 };
179 if done {
180 return None;
181 }
182 let value = self.current;
183 self.current = self.current + self.step;
184 Some(value)
185 }
186}
187
188pub fn intersect_intervals<T: Ord + Copy>(
190 a: &[Interval<T>],
191 b: &[Interval<T>],
192) -> Vec<Interval<T>> {
193 let mut result = Vec::new();
194 let mut i = 0;
195 let mut j = 0;
196 while i < a.len() && j < b.len() {
197 let inter = a[i].intersect(b[j]);
198 if !inter.is_empty() {
199 result.push(inter);
200 }
201 if a[i].end <= b[j].end {
203 i += 1;
204 } else {
205 j += 1;
206 }
207 }
208 result
209}
210
211pub fn union_intervals<T: Ord + Copy>(a: &[Interval<T>], b: &[Interval<T>]) -> Vec<Interval<T>> {
213 let mut all = Vec::with_capacity(a.len() + b.len());
215 let mut i = 0;
216 let mut j = 0;
217 while i < a.len() && j < b.len() {
218 if a[i].start <= b[j].start {
219 all.push(a[i]);
220 i += 1;
221 } else {
222 all.push(b[j]);
223 j += 1;
224 }
225 }
226 all.extend_from_slice(&a[i..]);
227 all.extend_from_slice(&b[j..]);
228
229 merge_intervals(all)
230}
231
232pub fn complement_intervals<T: Ord + Copy>(
234 intervals: &[Interval<T>],
235 bound: Interval<T>,
236) -> Vec<Interval<T>> {
237 let mut result = Vec::new();
238 let mut cursor = bound.start;
239 for iv in intervals {
240 if iv.start > cursor {
241 let gap = Interval::new(cursor, iv.start);
242 if !gap.is_empty() {
243 result.push(gap);
244 }
245 }
246 if iv.end > cursor {
247 cursor = iv.end;
248 }
249 }
250 if cursor < bound.end {
251 result.push(Interval::new(cursor, bound.end));
252 }
253 result
254}
255
256fn merge_intervals<T: Ord + Copy>(sorted: Vec<Interval<T>>) -> Vec<Interval<T>> {
257 let mut result: Vec<Interval<T>> = Vec::new();
258 for iv in sorted {
259 if iv.is_empty() {
260 continue;
261 }
262 if let Some(last) = result.last_mut()
263 && iv.start <= last.end
264 {
265 last.end = max(last.end, iv.end);
266 continue;
267 }
268 result.push(iv);
269 }
270 result
271}
272
273pub type TimeDeltaInterval = Interval<TimeDelta>;
275
276impl TimeDeltaInterval {
277 pub fn to_scale<T: TimeScale + Copy>(&self, scale: T) -> TimeInterval<T> {
279 Interval {
280 start: Time::from_delta(scale, self.start),
281 end: Time::from_delta(scale, self.end),
282 }
283 }
284}
285
286pub type TimeInterval<T> = Interval<Time<T>>;
288
289impl<T> TimeInterval<T>
290where
291 T: ToUtc + TimeScale + Copy,
292 DefaultOffsetProvider: Offset<T, Tai>,
293{
294 pub fn to_utc(&self) -> UtcInterval {
296 Interval {
297 start: self.start.to_utc(),
298 end: self.end.to_utc(),
299 }
300 }
301}
302
303pub type UtcInterval = Interval<Utc>;
305
306impl UtcInterval {
307 pub fn to_time(&self) -> TimeInterval<Tai> {
309 Interval {
310 start: self.start.to_time(),
311 end: self.end.to_time(),
312 }
313 }
314}
315
316impl<T> Display for Interval<T>
317where
318 T: Display,
319{
320 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
321 self.start.fmt(f)?;
322 write!(f, " – ")?;
323 self.end.fmt(f)
324 }
325}
326
327#[cfg(test)]
328mod tests {
329 use crate::{time, time_scales::Tai};
330
331 use super::*;
332
333 #[test]
334 fn test_time_interval() {
335 let t0 = time!(Tai, 2025, 11, 6).unwrap();
336 let t1 = time!(Tai, 2025, 11, 6, 1).unwrap();
337 let i = TimeInterval::new(t0, t1);
338 assert_eq!(i.start(), t0);
339 assert_eq!(i.end(), t1);
340 assert_eq!(i.duration(), TimeDelta::from_hours(1));
341 assert_eq!(
342 format!("{}", i),
343 "2025-11-06T00:00:00.000 TAI – 2025-11-06T01:00:00.000 TAI"
344 );
345 }
346
347 #[test]
348 fn test_step_by() {
349 let t0 = time!(Tai, 2025, 11, 6).unwrap();
350 let t1 = time!(Tai, 2025, 11, 6, 1).unwrap();
351 let interval = TimeInterval::new(t0, t1);
352 let step = TimeDelta::from_minutes(20);
353 let times: Vec<_> = interval.step_by(step).collect();
354 assert_eq!(times.len(), 4); assert_eq!(times[0], t0);
356 assert_eq!(times[3], t1);
357 }
358
359 #[test]
360 fn test_step_by_non_exact() {
361 let t0 = time!(Tai, 2025, 11, 6).unwrap();
362 let t1 = t0 + TimeDelta::from_minutes(50);
363 let interval = TimeInterval::new(t0, t1);
364 let step = TimeDelta::from_minutes(20);
365 let times: Vec<_> = interval.step_by(step).collect();
366 assert_eq!(times.len(), 3); }
368
369 #[test]
370 fn test_linspace() {
371 let t0 = time!(Tai, 2025, 11, 6).unwrap();
372 let t1 = time!(Tai, 2025, 11, 6, 1).unwrap();
373 let interval = TimeInterval::new(t0, t1);
374 let times = interval.linspace(5);
375 assert_eq!(times.len(), 5);
376 assert_eq!(times[0], t0);
377 assert_eq!(times[4], t1);
378 let dt = TimeDelta::from_minutes(15);
380 assert_eq!(times[1], t0 + dt);
381 assert_eq!(times[2], t0 + dt + dt);
382 }
383
384 #[test]
385 fn test_timedelta_interval_step_by() {
386 let td0 = TimeDelta::default();
387 let td1 = TimeDelta::from_minutes(60);
388 let interval = TimeDeltaInterval::new(td0, td1);
389 let step = TimeDelta::from_minutes(20);
390 let times: Vec<_> = interval.step_by(step).collect();
391 assert_eq!(times.len(), 4);
392 }
393
394 #[test]
395 fn test_step_by_backward() {
396 let t0 = time!(Tai, 2025, 11, 6).unwrap();
397 let t1 = time!(Tai, 2025, 11, 6, 1).unwrap();
398 let interval = TimeInterval::new(t1, t0);
400 let step = TimeDelta::from_minutes(20);
401 let times: Vec<_> = interval.step_by(step).collect();
402 assert_eq!(times.len(), 4); assert_eq!(times[0], t1);
404 assert_eq!(times[3], t0);
405 for w in times.windows(2) {
407 assert!(w[0] > w[1]);
408 }
409 }
410
411 #[test]
412 fn test_step_by_backward_auto_negates_step() {
413 let t0 = time!(Tai, 2025, 11, 6).unwrap();
414 let t1 = time!(Tai, 2025, 11, 6, 1).unwrap();
415 let interval = TimeInterval::new(t1, t0);
417 let step = -TimeDelta::from_minutes(20);
418 let times: Vec<_> = interval.step_by(step).collect();
419 assert_eq!(times.len(), 4);
420 assert_eq!(times[0], t1);
421 assert_eq!(times[3], t0);
422 }
423
424 #[test]
425 #[should_panic(expected = "step must be non-zero")]
426 fn test_step_by_zero_panics() {
427 let t0 = time!(Tai, 2025, 11, 6).unwrap();
428 let t1 = time!(Tai, 2025, 11, 6, 1).unwrap();
429 let interval = TimeInterval::new(t0, t1);
430 let _ = interval.step_by(TimeDelta::default()).collect::<Vec<_>>();
431 }
432
433 #[test]
434 fn test_contains() {
435 let outer = Interval::new(0, 10);
436 let inner = Interval::new(2, 8);
437 assert!(outer.contains(&inner));
438 assert!(!inner.contains(&outer));
439 }
440
441 #[test]
442 fn test_intersect_intervals() {
443 let a = vec![Interval::new(0, 5), Interval::new(10, 15)];
444 let b = vec![Interval::new(3, 12)];
445 let result = intersect_intervals(&a, &b);
446 assert_eq!(result, vec![Interval::new(3, 5), Interval::new(10, 12)]);
447 }
448
449 #[test]
450 fn test_intersect_intervals_no_overlap() {
451 let a = vec![Interval::new(0, 3)];
452 let b = vec![Interval::new(5, 8)];
453 let result = intersect_intervals(&a, &b);
454 assert!(result.is_empty());
455 }
456
457 #[test]
458 fn test_union_intervals() {
459 let a = vec![Interval::new(0, 5)];
460 let b = vec![Interval::new(3, 8)];
461 let result = union_intervals(&a, &b);
462 assert_eq!(result, vec![Interval::new(0, 8)]);
463 }
464
465 #[test]
466 fn test_union_intervals_disjoint() {
467 let a = vec![Interval::new(0, 3)];
468 let b = vec![Interval::new(5, 8)];
469 let result = union_intervals(&a, &b);
470 assert_eq!(result, vec![Interval::new(0, 3), Interval::new(5, 8)]);
471 }
472
473 #[test]
474 fn test_complement_intervals() {
475 let intervals = vec![Interval::new(2, 4), Interval::new(6, 8)];
476 let bound = Interval::new(0, 10);
477 let result = complement_intervals(&intervals, bound);
478 assert_eq!(
479 result,
480 vec![
481 Interval::new(0, 2),
482 Interval::new(4, 6),
483 Interval::new(8, 10),
484 ]
485 );
486 }
487
488 #[test]
489 fn test_complement_intervals_full_coverage() {
490 let intervals = vec![Interval::new(0, 10)];
491 let bound = Interval::new(0, 10);
492 let result = complement_intervals(&intervals, bound);
493 assert!(result.is_empty());
494 }
495}