range_overlap/
lib.rs

1//! Determine how two ranges overlap, given their end points.
2//! 
3//! There are two pairs of terms used throughout this crate: *exclusive* vs.
4//! *inclusive* and *closed* vs. *open*.
5//! 
6//! - *exclusive* vs. *inclusive* refers to whether the end of the range is
7//! considered part of it. This is the distinction between `1..3` and `1..=3`
8//! in Rust's range notation: the former will only represent the sequence `[1, 2]`
9//! and thus is *exclusive*, while the latter represents `[1, 2, 3]` and so is
10//! *inclusive*.
11//! 
12//! - *closed* vs. *open* refers to whether the range has finite end points.
13//! You could have a range that starts at `1` and goes one forever, ends at `10`
14//! but has infinitely many values less than that, or that has no end points whatsoever.
15//! These are all *open* ranges. Conversely, a range that starts at `1` and ends at `5`
16//! is closed. When this crate needs to represent ranges that may be closed, half-open,
17//! or fully open, it uses `Option<T>` values, where `Some(T)` represents a closed side
18//! and `None` an open side. For example, `(Some(1), None)` represent the range starting
19//! at `1` and going on forever.
20//! 
21//! The [`RangeOverlap`] enum of this crate specifies 6 possible ways
22//! in which two ranges may overlap. In the following schematics, `|`
23//! indicates an end point included in the range, `-` indicates an internal
24//! part of the range, and `o` indicates an exclusive end point. For simplicity,
25//! we'll only cover fully closed ranges first.
26//! 
27//! The simplest is no overlap, represented by [`RangeOverlap::None`]:
28//! 
29//! ```text
30//! // B is wholly after A
31//! A: |----|
32//! B:         |----|
33//! 
34//! // A is wholly after B
35//! A:         |--|
36//! B: |---|
37//! 
38//! // B starts on A's excluded end point:
39//! A: |----o
40//! B:      |----o
41//! ```
42//! 
43//! The next simplest is [`RangeOverlap::AEqualsB`], when A and B represent the same range:
44//! ```text
45//! // This holds true for inclusive...
46//! A: |----|
47//! B: |----|
48//! 
49//! // ...and exclusive ranges:
50//! A: |----o
51//! B: |----o
52//! ```
53//! 
54//! The next two variants indicate when one range is entirely within
55//! another. [`RangeOverlap::AContainsB`] means that the first range 
56//! specified (`A`) contains all the points in `B`, while [`RangeOverlap::AInsideB`]
57//! indicates the reverse:
58//! 
59//! ```text
60//! // AContainsB:
61//! A: |------|
62//! B:   |--|
63//! 
64//! // These are also AContainsB, because no point in B is outside A, even though
65//! // the ends line up:
66//! A: |------|
67//! B: |----|
68//! 
69//! A: |------|
70//! B:   |----|
71//! 
72//! A: |------o
73//! B:   |----o
74//! 
75//! And vice versa, these are AInsideB:
76//! A:   |---|
77//! B: |-------|
78//! 
79//! A:   |---o
80//! B: |-----o
81//! 
82//! A: |---|
83//! B: |-----|
84//! ```
85//! 
86//! Finally, [`RangeOverlap::AEndsInB`] and [`RangeOverlap::AStartsInB`] indicate
87//! cases where only one end of A falls within the limits of B:
88//! 
89//! ```text
90//! // AEndsInB:
91//! 
92//! A: |------|
93//! B:    |-------|
94//! 
95//! // This is AEndsInB because the end of A is included. If it were
96//! // exclusive this would be None:
97//! A: |------|
98//! B:        |-----|
99//! 
100//! // AStartsInB:
101//! A:    |------|
102//! B: |----|
103//! 
104//! A:    |------|
105//! B: |--|
106//! ```
107//! 
108//! The main function that can classify any type of range is [`classify_any`]. This can handle
109//! closed, half-open, or fully open ranges and interpret the end points as inclusive or exclusive.
110//! If you know your ranges will be closed, you can use [`excl_classify`] or [`incl_classify`] to
111//! determine the [`RangeOverlap`] without needing to wrap the range ends in `Some()`.
112//! 
113//! One assumption this crate makes is that an end being open means the same thing for both ranges.
114//! For example:
115//! 
116//! ```
117//! # use range_overlap::{classify_any, RangeOverlap};
118//! 
119//! let overlap = classify_any(None, Some(5), None, Some(10), false);
120//! assert_eq!(overlap, RangeOverlap::AInsideB);
121//! ```
122//! 
123//! Here, `None` is assumed to mean the same thing for both ranges, essentially negative infinity.
124//! Since this means there can be no point in A not also in B, the result is `AInsideB`. Another
125//! interesting case is when both ranges are fully open:
126//! 
127//! ```
128//! # use range_overlap::{classify_any, RangeOverlap};
129//! 
130//! // With literal `None`s we had to give the type T as i32, since there was nothing
131//! // for Rust to infer T from. You will almost never need to do this in practice.
132//! let overlap = classify_any::<i32>(None, None, None, None, false);
133//! assert_eq!(overlap, RangeOverlap::AEqualsB);
134//! ```
135//! 
136//! Again, since `None` is taken to mean the same thing for both A and B, all points in one
137//! must be in the other, so these two are equal ranges.
138//! 
139//! If you only need to determine if two ranges overlap, but not how, you can either use the 
140//! [`RangeOverlap::has_overlap`] method, or call one of the convenience methods:
141//! 
142//! - [`has_excl_overlap`]
143//! - [`has_incl_overlap`]
144//! - [`has_open_excl_overlap`]
145//! - [`has_open_incl_overlap`]
146//! 
147//! Finally, note that all of these method are defined for any type that implements [`PartialOrd`].
148//! This means you can use them for integers, floats, `chrono` times, and many other types. This includes
149//! types such as [`std::string::String`], which may not produce intuitive behavior unless you are very
150//! clear on how they are ordered.
151
152/// An enum describing the kind of overlap between two ranges.
153#[derive(Debug, PartialEq, Eq, Clone, Copy)]
154pub enum RangeOverlap {
155    /// The second range is fully within the first, meaning that all values from the second are also in the first
156    AContainsB,
157
158    /// The first range is fully within the second, meaning that all values from the first are also in the second
159    AInsideB,
160
161    /// The end of the first range overlaps with the start of the second. One case that depends on whether the ranges
162    /// are considered inclusive or exclusive is when the end of A equals the start of B. That case will only be `AEndsInB`
163    /// if the ranges are inclusive, otherwise it will be `None`.
164    AEndsInB,
165
166    /// The start of the first range overlaps the end of the second. One case that depends on whether the ranges
167    /// are considered inclusive or exclusive is when the start of A equals the end of B. That case will only be `AStartsInB`
168    /// if the ranges are inclusive, otherwise it will be `None`.
169    AStartsInB,
170
171    /// The bounds of both ranges are exactly the same.
172    AEqualsB,
173
174    /// There is no overlap between the two ranges.
175    None
176}
177
178impl RangeOverlap {
179    /// Returns `true` if there was any overlap between the ranges
180    pub fn has_overlap(&self) -> bool {
181        if let Self::None = self {
182            false
183        } else {
184            true
185        }
186    }
187}
188
189/// Classify the kind of overlap between two fully closed ranges with the ends
190/// considered exclusive.
191pub fn excl_classify<T: PartialOrd>(a_start: T, a_end: T, b_start: T, b_end: T) -> RangeOverlap {
192    if a_start == b_start && a_end == b_end {
193        RangeOverlap::AEqualsB
194    } else if a_start <= b_start && a_end >= b_end {
195        RangeOverlap::AContainsB
196    } else if a_start < b_start && a_end > b_start && a_end <= b_end {
197        RangeOverlap::AEndsInB
198    } else if a_start > b_start && a_start < b_end && a_end > b_end {
199        RangeOverlap::AStartsInB
200    } else if a_start >= b_end || b_start >= a_end {
201        RangeOverlap::None
202    } else {
203        RangeOverlap::AInsideB
204    }
205}
206
207/// Classify the kind of overlap between two fully closed ranges with the ends
208/// considered inclusive.
209pub fn incl_classify<T: PartialOrd>(a_start: T, a_end: T, b_start: T, b_end: T) -> RangeOverlap {
210    if a_start == b_start && a_end == b_end {
211        RangeOverlap::AEqualsB
212    } else if a_start <= b_start && a_end >= b_end {
213        RangeOverlap::AContainsB
214    } else if a_start < b_start && a_end >= b_start && a_end <= b_end {
215        RangeOverlap::AEndsInB
216    } else if a_start > b_start && a_start <= b_end && a_end > b_end {
217        RangeOverlap::AStartsInB
218    } else if a_start >= b_end || b_start >= a_end {
219        RangeOverlap::None
220    } else {
221        RangeOverlap::AInsideB
222    }
223}
224
225/// Classify the kind of overlap between two ranges that can be closed, half-open,
226/// or fully open. Specifying `a_start`, `a_end`, `b_start`, or `b_end` as `None`
227/// indicates that that side of the range is open. The final parameter, `inclusive`,
228/// can be `true` to indicate that `a_end` and `b_end` are part of their ranges, or
229/// `false` if they are not.
230pub fn classify_any<T: PartialOrd>(a_start: Option<T>, a_end: Option<T>, b_start: Option<T>, b_end: Option<T>, inclusive: bool) -> RangeOverlap {
231    match (a_start, a_end, b_start, b_end, inclusive) {
232        (None, None, None, None, _) => RangeOverlap::AEqualsB,
233        (None, None, None, Some(_), _) => RangeOverlap::AContainsB,
234        (None, None, Some(_), None, _) => RangeOverlap::AContainsB,
235        (None, None, Some(_), Some(_), _) => RangeOverlap::AContainsB,
236        (None, Some(_), None, None, _) => RangeOverlap::AInsideB,
237        (None, Some(ea), None, Some(eb), _) => {
238            // Doesn't matter here if we are looking for inclusive or exclusive,
239            // since we only compare ends
240            if ea == eb {
241                RangeOverlap::AEqualsB
242            } else if ea < eb {
243                RangeOverlap::AInsideB
244            } else {
245                RangeOverlap::AContainsB
246            }
247        },
248        (None, Some(ea), Some(sb), None, false) => {
249            if ea <= sb {
250                RangeOverlap::None
251            } else {
252                RangeOverlap::AEndsInB
253            }
254        },
255        (None, Some(ea), Some(sb), None, true) => {
256            if ea < sb {
257                RangeOverlap::None
258            } else {
259                RangeOverlap::AEndsInB
260            }
261        },
262        (None, Some(ea), Some(sb), Some(eb), false) => {
263            if ea <= sb {
264                RangeOverlap::None
265            } else if ea > sb && ea < eb {
266                RangeOverlap::AEndsInB
267            } else {
268                RangeOverlap::AContainsB
269            }
270        },
271        (None, Some(ea), Some(sb), Some(eb), true) => {
272            if ea < sb {
273                RangeOverlap::None
274            } else if ea >= sb && ea < eb {
275                RangeOverlap::AEndsInB
276            } else {
277                RangeOverlap::AContainsB
278            }
279        },
280        (Some(_), None, None, None, _) => RangeOverlap::AInsideB,
281        (Some(sa), None, None, Some(eb), false) => {
282            if sa >= eb {
283                RangeOverlap::None
284            } else {
285                RangeOverlap::AStartsInB
286            }
287        },
288        (Some(sa), None, None, Some(eb), true) => {
289            if sa > eb {
290                RangeOverlap::None
291            } else {
292                RangeOverlap::AStartsInB
293            }
294        },
295        (Some(sa), None, Some(sb), None, _) => {
296            if sa == sb {
297                RangeOverlap::AEqualsB
298            } else if sa < sb {
299                RangeOverlap::AContainsB
300            } else {
301                RangeOverlap::AInsideB
302            }
303        },
304        (Some(sa), None, Some(sb), Some(eb), false) => {
305            if sa <= sb {
306                RangeOverlap::AContainsB
307            } else if sa < eb {
308                RangeOverlap::AStartsInB
309            } else {
310                RangeOverlap::None
311            }
312        },
313        (Some(sa), None, Some(sb), Some(eb), true) => {
314            if sa <= sb {
315                RangeOverlap::AContainsB
316            } else if sa <= eb {
317                RangeOverlap::AStartsInB
318            } else {
319                RangeOverlap::None
320            }
321        },
322        (Some(_), Some(_), None, None, _) => RangeOverlap::AInsideB,
323        (Some(sa), Some(ea), None, Some(eb), false) => {
324            if eb <= sa {
325                RangeOverlap::None
326            } else if ea <= eb {
327                RangeOverlap::AInsideB
328            } else {
329                RangeOverlap::AStartsInB
330            }
331        },
332        (Some(sa), Some(ea), None, Some(eb), true) => {
333            if eb < sa {
334                RangeOverlap::None
335            } else if ea <= eb {
336                RangeOverlap::AInsideB
337            } else {
338                RangeOverlap::AStartsInB
339            }
340        },
341        (Some(sa), Some(ea), Some(sb), None, false) => {
342            if sb >= ea {
343                RangeOverlap::None
344            } else if sa >= sb {
345                RangeOverlap::AInsideB
346            } else {
347                RangeOverlap::AEndsInB
348            }
349        },
350        (Some(sa), Some(ea), Some(sb), None, true) => {
351            if sb > ea {
352                RangeOverlap::None
353            } else if sa >= sb {
354                RangeOverlap::AInsideB
355            } else {
356                RangeOverlap::AEndsInB
357            }
358        },
359        (Some(sa), Some(ea), Some(sb), Some(eb), false) => {
360            excl_classify(sa, ea, sb, eb)
361        },
362        (Some(sa), Some(ea), Some(sb), Some(eb), true) => {
363            incl_classify(sa, ea, sb, eb)
364        },
365    }
366}
367
368/// A convenience function that directly returns `true` if the two closed ranges given
369/// have overlap, with `a_end` and `b_end` not included in the range.
370pub fn has_excl_overlap<T: PartialOrd>(a_start: T, a_end: T, b_start: T, b_end: T) -> bool {
371    excl_classify(a_start, a_end, b_start, b_end).has_overlap()
372}
373
374/// A convenience function that directly returns `true` if the two closed ranges given
375/// have overlap, with `a_end` and `b_end` included in the range.
376pub fn has_incl_overlap<T: PartialOrd>(a_start: T, a_end: T, b_start: T, b_end: T) -> bool {
377    incl_classify(a_start, a_end, b_start, b_end).has_overlap()
378}
379
380/// A convenience function that directly returns `true` if the two ranges given
381/// (which may be closed, half-open, or fully open) have overlap, with `a_end` and `b_end` 
382/// not included in the range.
383pub fn has_open_excl_overlap<T: PartialOrd>(a_start: Option<T>, a_end: Option<T>, b_start: Option<T>, b_end: Option<T>) -> bool {
384    classify_any(a_start, a_end, b_start, b_end, false).has_overlap()
385}
386
387/// A convenience function that directly returns `true` if the two ranges given
388/// (which may be closed, half-open, or fully open) have overlap, with `a_end` and `b_end` 
389/// included in the range.
390pub fn has_open_incl_overlap<T: PartialOrd>(a_start: Option<T>, a_end: Option<T>, b_start: Option<T>, b_end: Option<T>) -> bool {
391    classify_any(a_start, a_end, b_start, b_end, true).has_overlap()
392}
393
394#[cfg(test)]
395mod tests {
396    use super::*;
397
398    #[test]
399    fn test_open_range_exclusive_bool() {
400        let r1_start = 1;
401        let r1_end = 20;
402        let r2_before = -20;
403        let r2_before2 = -10;
404        let r2_between = 10;
405        let r2_after = 30;
406        let r2_after2 = 40;
407
408        // Test when both ranges are open ended, making sure that the result is symmetrical
409        assert_eq!(has_open_excl_overlap(Some(r1_start), None, Some(r2_before), None), true);
410        assert_eq!(has_open_excl_overlap(Some(r1_start), None, Some(r2_between), None), true);
411        assert_eq!(has_open_excl_overlap(Some(r1_start), None, Some(r2_after), None), true);
412
413        assert_eq!(has_open_excl_overlap(Some(r2_before), None, Some(r1_start), None), true);
414        assert_eq!(has_open_excl_overlap(Some(r2_between), None, Some(r1_start), None), true);
415        assert_eq!(has_open_excl_overlap(Some(r2_after), None, Some(r1_start), None), true);
416
417        // Test when one range has an end date - the only non-overlapping cases should be
418        // when the start date of the open ended range is after the end date of the closed
419        // range.
420        assert_eq!(has_open_excl_overlap(Some(r1_start), Some(r1_end), Some(r2_before), None), true);
421        assert_eq!(has_open_excl_overlap(Some(r1_start), Some(r1_end), Some(r2_between), None), true);
422        assert_eq!(has_open_excl_overlap(Some(r1_start), Some(r1_end), Some(r2_after), None), false);
423
424        assert_eq!(has_open_excl_overlap(Some(r2_before), None, Some(r1_start), Some(r1_end)), true);
425        assert_eq!(has_open_excl_overlap(Some(r2_between), None, Some(r1_start), Some(r1_end)), true);
426        assert_eq!(has_open_excl_overlap(Some(r2_after), None, Some(r1_start), Some(r1_end)), false);
427
428        // Test when both ranges have end dates - the non-overlapping cases should be 
429        // when either ranges' start date is after the other one's end date
430        assert_eq!(has_open_excl_overlap(Some(r1_start), Some(r1_end), Some(r2_before), Some(r2_before2)), false);
431        assert_eq!(has_open_excl_overlap(Some(r1_start), Some(r1_end), Some(r2_before), Some(r2_between)), true);
432        assert_eq!(has_open_excl_overlap(Some(r1_start), Some(r1_end), Some(r2_between), Some(r2_after)), true);
433        assert_eq!(has_open_excl_overlap(Some(r1_start), Some(r1_end), Some(r2_after), Some(r2_after2)), false);
434
435        assert_eq!(has_open_excl_overlap(Some(r2_before), Some(r2_before2), Some(r1_start), Some(r1_end)), false);
436        assert_eq!(has_open_excl_overlap(Some(r2_before), Some(r2_between), Some(r1_start), Some(r1_end)), true);
437        assert_eq!(has_open_excl_overlap(Some(r2_between), Some(r2_after), Some(r1_start), Some(r1_end)), true);
438        assert_eq!(has_open_excl_overlap(Some(r2_after), Some(r2_after2), Some(r1_start), Some(r1_end)), false);
439    }
440
441    #[test]
442    fn test_open_range_exclusive_classification() {
443        // A == B
444        assert_eq!(classify_any(Some(1), Some(10), Some(1), Some(10), false), RangeOverlap::AEqualsB);
445        assert_eq!(classify_any(None, Some(10), None, Some(10), false), RangeOverlap::AEqualsB);
446        assert_eq!(classify_any(Some(1), None, Some(1), None, false), RangeOverlap::AEqualsB);
447        assert_eq!(classify_any::<i32>(None, None, None, None, false), RangeOverlap::AEqualsB);
448
449        // A contains B
450        assert_eq!(classify_any(Some(1), Some(100), Some(50), Some(60), false), RangeOverlap::AContainsB);
451        assert_eq!(classify_any(None, Some(100), Some(50), Some(60), false), RangeOverlap::AContainsB);
452        assert_eq!(classify_any(Some(1), None, Some(50), Some(60), false), RangeOverlap::AContainsB);
453        assert_eq!(classify_any(None, None, Some(50), Some(60), false), RangeOverlap::AContainsB);
454        assert_eq!(classify_any(None, Some(100), None, Some(60), false), RangeOverlap::AContainsB);
455        assert_eq!(classify_any(Some(1), None, Some(50), None, false), RangeOverlap::AContainsB);
456        assert_eq!(classify_any(None, None, None, Some(60), false), RangeOverlap::AContainsB);
457        assert_eq!(classify_any(None, None, Some(50), None, false), RangeOverlap::AContainsB);
458
459        // (edge cases with equal start or end values)
460        assert_eq!(classify_any(None, Some(50), Some(1), Some(50), false), RangeOverlap::AContainsB);
461        assert_eq!(classify_any(Some(1), None, Some(1), Some(50), false), RangeOverlap::AContainsB);
462        assert_eq!(classify_any(Some(10), Some(50), Some(10), Some(20), false), RangeOverlap::AContainsB);
463        assert_eq!(classify_any(Some(10), Some(50), Some(40), Some(50), false), RangeOverlap::AContainsB);
464
465        // A inside B
466        assert_eq!(classify_any(None, Some(75), None, None, false), RangeOverlap::AInsideB);
467        assert_eq!(classify_any(Some(50), None, None, None, false), RangeOverlap::AInsideB);
468        assert_eq!(classify_any(Some(50), Some(60), Some(1), Some(100), false), RangeOverlap::AInsideB);
469        assert_eq!(classify_any(Some(50), Some(60), None, Some(100), false), RangeOverlap::AInsideB);
470        assert_eq!(classify_any(Some(50), Some(60), Some(1), None, false), RangeOverlap::AInsideB);
471        assert_eq!(classify_any(None, Some(60), None, Some(100), false), RangeOverlap::AInsideB);
472        assert_eq!(classify_any(Some(50), None, Some(1), None, false), RangeOverlap::AInsideB);
473
474        // (edge cases with equal start or end values)
475        assert_eq!(classify_any(Some(1), Some(50), Some(1), None, false), RangeOverlap::AInsideB);
476        assert_eq!(classify_any(Some(1), Some(50), None, Some(50), false), RangeOverlap::AInsideB);
477
478        // These are cases that showed up when using similar code with dates,
479        // so converted dates to day-of-year for this test
480        //  Original dates: 2017-1-1, 2017-12-01, 2017-1-1, None
481        assert_eq!(classify_any(Some(1), Some(335), Some(1), None, false), RangeOverlap::AInsideB);
482        //  Original date: 2004-12-01, 2005-01-01, 2004-07-01, 2005-01-01
483        assert_eq!(classify_any(Some(336), Some(366), Some(183), Some(366), false), RangeOverlap::AInsideB);
484
485        // A ends in B
486        assert_eq!(classify_any(Some(1), Some(75), Some(25), Some(99), false), RangeOverlap::AEndsInB);
487        assert_eq!(classify_any(None, Some(75), Some(25), Some(99), false), RangeOverlap::AEndsInB);
488        assert_eq!(classify_any(None, Some(75), Some(25), None, false), RangeOverlap::AEndsInB);
489
490        // A starts in B
491        assert_eq!(classify_any(Some(50), Some(99), Some(1), Some(75), false), RangeOverlap::AStartsInB);
492        assert_eq!(classify_any(Some(50), None, Some(1), Some(75), false), RangeOverlap::AStartsInB);
493        assert_eq!(classify_any(Some(50), None, None, Some(75), false), RangeOverlap::AStartsInB);
494
495        // No overlap
496        assert_eq!(classify_any(Some(1), Some(25), Some(50), Some(75), false), RangeOverlap::None);
497        assert_eq!(classify_any(Some(50), Some(75), Some(1), Some(25), false), RangeOverlap::None);
498        assert_eq!(classify_any(None, Some(25), Some(50), Some(99), false), RangeOverlap::None);
499        assert_eq!(classify_any(Some(1), Some(25), Some(50), None, false), RangeOverlap::None);
500
501    }
502
503    #[test]
504    fn test_open_range_inclusive_classification() {
505        // A == B
506        assert_eq!(classify_any(Some(1), Some(10), Some(1), Some(10), true), RangeOverlap::AEqualsB);
507        assert_eq!(classify_any(None, Some(10), None, Some(10), true), RangeOverlap::AEqualsB);
508        assert_eq!(classify_any(Some(1), None, Some(1), None, true), RangeOverlap::AEqualsB);
509        assert_eq!(classify_any::<i32>(None, None, None, None, true), RangeOverlap::AEqualsB);
510
511        // A contains B
512        assert_eq!(classify_any(Some(1), Some(100), Some(50), Some(60), true), RangeOverlap::AContainsB);
513        assert_eq!(classify_any(None, Some(100), Some(50), Some(60), true), RangeOverlap::AContainsB);
514        assert_eq!(classify_any(Some(1), None, Some(50), Some(60), true), RangeOverlap::AContainsB);
515        assert_eq!(classify_any(None, None, Some(50), Some(60), true), RangeOverlap::AContainsB);
516        assert_eq!(classify_any(None, Some(100), None, Some(60), true), RangeOverlap::AContainsB);
517        assert_eq!(classify_any(Some(1), None, Some(50), None, true), RangeOverlap::AContainsB);
518        assert_eq!(classify_any(None, None, None, Some(60), true), RangeOverlap::AContainsB);
519        assert_eq!(classify_any(None, None, Some(50), None, true), RangeOverlap::AContainsB);
520
521        // (edge cases with equal start or end values)
522        assert_eq!(classify_any(None, Some(50), Some(1), Some(50), true), RangeOverlap::AContainsB);
523        assert_eq!(classify_any(Some(1), None, Some(1), Some(50), true), RangeOverlap::AContainsB);
524        assert_eq!(classify_any(Some(10), Some(50), Some(10), Some(20), true), RangeOverlap::AContainsB);
525        assert_eq!(classify_any(Some(10), Some(50), Some(40), Some(50), true), RangeOverlap::AContainsB);
526
527        // A inside B
528        assert_eq!(classify_any(None, Some(75), None, None, true), RangeOverlap::AInsideB);
529        assert_eq!(classify_any(Some(50), None, None, None, true), RangeOverlap::AInsideB);
530        assert_eq!(classify_any(Some(50), Some(60), Some(1), Some(100), true), RangeOverlap::AInsideB);
531        assert_eq!(classify_any(Some(50), Some(60), None, Some(100), true), RangeOverlap::AInsideB);
532        assert_eq!(classify_any(Some(50), Some(60), Some(1), None, true), RangeOverlap::AInsideB);
533        assert_eq!(classify_any(None, Some(60), None, Some(100), true), RangeOverlap::AInsideB);
534        assert_eq!(classify_any(Some(50), None, Some(1), None, true), RangeOverlap::AInsideB);
535
536        // (edge cases with equal start or end values)
537        assert_eq!(classify_any(Some(1), Some(50), Some(1), None, true), RangeOverlap::AInsideB);
538        assert_eq!(classify_any(Some(1), Some(50), None, Some(50), true), RangeOverlap::AInsideB);
539
540        // These are cases that showed up when using similar code with dates,
541        // so converted dates to day-of-year for this test
542        //  Original dates: 2017-1-1, 2017-12-01, 2017-1-1, None
543        assert_eq!(classify_any(Some(1), Some(335), Some(1), None, true), RangeOverlap::AInsideB);
544        //  Original date: 2004-12-01, 2005-01-01, 2004-07-01, 2005-01-01
545        assert_eq!(classify_any(Some(336), Some(366), Some(183), Some(366), true), RangeOverlap::AInsideB);
546
547        // A ends in B
548        assert_eq!(classify_any(Some(1), Some(75), Some(25), Some(99), true), RangeOverlap::AEndsInB);
549        assert_eq!(classify_any(None, Some(75), Some(25), Some(99), true), RangeOverlap::AEndsInB);
550        assert_eq!(classify_any(None, Some(75), Some(25), None, true), RangeOverlap::AEndsInB);
551
552        // A starts in B
553        assert_eq!(classify_any(Some(50), Some(99), Some(1), Some(75), true), RangeOverlap::AStartsInB);
554        assert_eq!(classify_any(Some(50), None, Some(1), Some(75), true), RangeOverlap::AStartsInB);
555        assert_eq!(classify_any(Some(50), None, None, Some(75), true), RangeOverlap::AStartsInB);
556
557        // No overlap
558        assert_eq!(classify_any(Some(1), Some(25), Some(50), Some(75), true), RangeOverlap::None);
559        assert_eq!(classify_any(Some(50), Some(75), Some(1), Some(25), true), RangeOverlap::None);
560        assert_eq!(classify_any(None, Some(25), Some(50), Some(99), true), RangeOverlap::None);
561        assert_eq!(classify_any(Some(1), Some(25), Some(50), None, true), RangeOverlap::None);
562
563    }
564
565    #[test]
566    fn test_exclusive_vs_inclusive() {
567        assert_eq!(excl_classify(1, 5, 5, 10), RangeOverlap::None);
568        assert_eq!(incl_classify(1, 5, 5, 10), RangeOverlap::AEndsInB);
569
570        assert_eq!(excl_classify(10, 15, 5, 10), RangeOverlap::None);
571        assert_eq!(incl_classify(10, 15, 5, 10), RangeOverlap::AStartsInB);
572
573        assert_eq!(classify_any(None, Some(10), Some(10), None, false), RangeOverlap::None);
574        assert_eq!(classify_any(None, Some(10), Some(10), None, true), RangeOverlap::AEndsInB);
575
576        assert_eq!(classify_any(None, Some(10), Some(10), Some(20), false), RangeOverlap::None);
577        assert_eq!(classify_any(None, Some(10), Some(10), Some(20), true), RangeOverlap::AEndsInB);
578
579        assert_eq!(classify_any(Some(1), None, None, Some(1), false), RangeOverlap::None);
580        assert_eq!(classify_any(Some(1), None, None, Some(1), true), RangeOverlap::AStartsInB);
581
582        assert_eq!(classify_any(Some(1), None, Some(-5), Some(1), false), RangeOverlap::None);
583        assert_eq!(classify_any(Some(1), None, Some(-5), Some(1), true), RangeOverlap::AStartsInB);
584
585        assert_eq!(classify_any(Some(1), Some(10), None, Some(1), false), RangeOverlap::None);
586        assert_eq!(classify_any(Some(1), Some(10), None, Some(1), true), RangeOverlap::AStartsInB);
587
588        assert_eq!(classify_any(Some(1), Some(10), Some(10), None, false), RangeOverlap::None);
589        assert_eq!(classify_any(Some(1), Some(10), Some(10), None, true), RangeOverlap::AEndsInB);
590
591
592    }
593
594}