Classify nullable date-range overlap state¶
Classify the relationship between two open-ended-capable date ranges into one of ten specific overlap categories. This operation is triggered when a consumer calls NullableDateRange.GetOverlapState(rangeA, rangeB).
| Field | Value |
|---|---|
| Operation ID | OP-03 |
| Trigger | A consumer calls NullableDateRange.GetOverlapState(rangeA, rangeB). |
| Inputs | rangeA, rangeB (NullableDateRange, each with a required StartDate and nullable EndDate) |
| Outputs | A DateRangeOverlapResult enum value. |
| Primary components | NullableDateRange.GetOverlapState |
| Entry point | src/SAIF.NetCore.Common/SAIF.NetCore.Common/SAIF.NetCore.Common/Types/NullableDateRange.cs |
Steps¶
The method tests nine branches in source order and returns on the first match, falling back to NoOverlap if none match:
- If neither range has an end date (
!rangeA.EndDate.HasValue && !rangeB.EndDate.HasValue): returnNeitherEnds, regardless of the start dates — this branch runs before any start-date comparison, so two mutually open-ended ranges with identical, differing, or either-order start dates all returnNeitherEnds. - Else if range A has no end date, range B does, and A's start falls strictly between B's start and end: return
RangeADoesNotEnd_RangeABetweenRangeBStartAndEnd. - Else if range A has no end date, range B does, and A's start falls strictly before B's start: return
RangeADoesNotEnd_RangeAStartBeforeRangeBStart. - Else if range A has an end date, range B does not, and A's start falls strictly after B's start: return
RangeBDoesNotEnd_RangeAStartAfterRangeBStart. - Else if range A has an end date, range B does not, A's start falls strictly before B's start, and A's end falls strictly after B's start: return
RangeBDoesNotEnd_RangeAStartBeforeRangeBStart_RangeAEndAfterRangeBStart. - Else if (both ranges have end dates, implicitly) A's start is strictly after B's start and A's end is strictly after B's end: return
RangeAStartAfterRangeBStart_RangeAEndAfterRangeBEnd. - Else if A's start is strictly after B's start and A's end is strictly before B's end: return
RangeAStartAfterRangeBStart_RangeAEndBeforeRangeBEnd. - Else if A's start is strictly before B's start and A's end is strictly after B's end: return
RangeAStartBeforeRangeBStart_RangeAEndAfterRangeBEnd. - Else if A's start is strictly before B's start and A's end is strictly before B's end: return
RangeAStartBeforeRangeBStart_RangeAEndBeforeRangeBEnd. -
Else: return
NoOverlap(the enum's default value) — reached whenever none of the nine predicates in branches 1–9 match. Because every branch from 2–9 uses strict</>comparisons (never<=/>=), several distinct shapes fall through to this default; the following are illustrative examples, not an exhaustive list:- Equal start dates on two closed (or one-open/one-closed) ranges, since no branch 2–9 tests
==on the start date — this does not apply when both ranges are open-ended, because branch 1 matches first and returnsNeitherEndsbefore any start-date comparison runs. - Equal end dates with different closed start dates — for example A = [Jan 2, Jan 5], B = [Jan 1, Jan 5] has
A.StartDate > B.StartDatebutA.EndDate == B.EndDate, which satisfies neither branch 6 (A.EndDate > B.EndDate) nor branch 7 (A.EndDate < B.EndDate), so it returnsNoOverlapeven though the two ranges visibly overlap. - Open/closed boundary ties — when one range is open-ended and its start date exactly equals the closed range's start or end date, branches 2–5's strict comparisons also fail to match, producing the same fallthrough.
- Neither range's start/end lines up with the other's at all — for example A = [Jan 10, null] (open-ended), B = [Jan 1, Jan 2] (closed) satisfies neither branch 2 (A's start is not strictly between B's start and end) nor branch 3 (A's start is not strictly before B's start), and falls through with no boundary tie present; the mirrored closed-before-open case falls through the same way.
Conversely, not every mismatched or even fully disjoint pair falls through: two disjoint closed ranges such as A = [Jan 10, Jan 11] and B = [Jan 1, Jan 2] satisfy branch 6 (
A.StartDate > B.StartDateandA.EndDate > B.EndDate) and returnRangeAStartAfterRangeBStart_RangeAEndAfterRangeBEnddirectly, with no fallthrough at all. - Equal start dates on two closed (or one-open/one-closed) ranges, since no branch 2–9 tests
The ten enum member names are not a reliable interval-intersection test. NoOverlap can be returned for ranges that do overlap (the equal-end example above), and a compound name such as RangeAStartAfterRangeBStart_RangeAEndAfterRangeBEnd is returned for genuinely disjoint ranges (the Jan 10–11 / Jan 1–2 example above) just as readily as for ranges that actually overlap. A caller that needs a true overlap/intersection test must not infer it from the result name alone.
The real observed behavior therefore diverges from a naive "same start date is always ambiguous" reading of interval semantics: when both ranges are open-ended, branch 1 wins first and the result is always NeitherEnds, irrespective of the start dates; the fallthrough shapes above are representative examples, not an exhaustive enumeration of every case that reaches branch 10.
Process flow¶
The following diagram visualizes the real ordered branch predicates, in source order.
flowchart TD
Start([Call GetOverlapState]) --> B1{Both EndDate null?}
B1 -->|Yes| NeitherEnds([Return NeitherEnds])
B1 -->|No| B2{A no-end, B ends,<br>B.Start < A.Start < B.End?}
B2 -->|Yes| R2(["Return RangeADoesNotEnd_<br>RangeABetweenRangeBStartAndEnd"])
B2 -->|No| B3{A no-end, B ends,<br>A.Start < B.Start?}
B3 -->|Yes| R3(["Return RangeADoesNotEnd_<br>RangeAStartBeforeRangeBStart"])
B3 -->|No| B4{A ends, B no-end,<br>A.Start > B.Start?}
B4 -->|Yes| R4(["Return RangeBDoesNotEnd_<br>RangeAStartAfterRangeBStart"])
B4 -->|No| B5{A ends, B no-end,<br>A.Start < B.Start < A.End?}
B5 -->|Yes| R5(["Return RangeBDoesNotEnd_RangeAStartBeforeRangeBStart_<br>RangeAEndAfterRangeBStart"])
B5 -->|No| B6{Both end,<br>A.Start > B.Start, A.End > B.End?}
B6 -->|Yes| R6(["Return RangeAStartAfterRangeBStart_<br>RangeAEndAfterRangeBEnd"])
B6 -->|No| B7{A.Start > B.Start, A.End < B.End?}
B7 -->|Yes| R7(["Return RangeAStartAfterRangeBStart_<br>RangeAEndBeforeRangeBEnd"])
B7 -->|No| B8{A.Start < B.Start, A.End > B.End?}
B8 -->|Yes| R8(["Return RangeAStartBeforeRangeBStart_<br>RangeAEndAfterRangeBEnd"])
B8 -->|No| B9{A.Start < B.Start, A.End < B.End?}
B9 -->|Yes| R9(["Return RangeAStartBeforeRangeBStart_<br>RangeAEndBeforeRangeBEnd"])
B9 -->|No| NoOverlap([Return NoOverlap - default])
Failure and edge-case behavior¶
- Two mutually open-ended ranges (
EndDate == nullfor both) always returnNeitherEnds, even when their start dates are identical — this is resolved by the first branch, before any start-date logic runs, and is not the "ambiguous" case a reader might assume. NoOverlap(the default value) is returned for a broader set of cases than "equal start dates" alone — reached whenever none of the nine predicates in branches 1–9 match. Illustrative (non-exhaustive) examples: equal start dates on two closed (or one-open/one-closed) ranges (not applicable when both ranges are open-ended, since branch 1 matches first), equal end dates with different closed start dates (for example A = [Jan 2, Jan 5], B = [Jan 1, Jan 5], which visibly overlaps but still returnsNoOverlap), open/closed boundary ties where the open range's start matches the closed range's start or end, and non-overlapping open/closed pairs with no boundary tie at all (for example A = [Jan 10, null], B = [Jan 1, Jan 2]) — every branch 2–9 uses strict</>comparisons.*- The ten
DateRangeOverlapResultmember names do not constitute a reliable interval-intersection test:NoOverlapcan be returned for overlapping ranges, and a compound name describing one range starting and ending after another (for exampleRangeAStartAfterRangeBStart_RangeAEndAfterRangeBEnd) is returned for fully disjoint closed ranges (A = [Jan 10, Jan 11], B = [Jan 1, Jan 2]) exactly as it would be for overlapping ones.* GetOverlapStatedoes not null-checkrangeA/rangeBthemselves; anullargument throwsNullReferenceExceptionon the first property access.*
Related documentation¶
Note: Statements marked with
*were inferred from code structure, naming conventions, or contextual evidence where direct confirmation was not available in source artifacts. Statements marked with**could not be confirmed; treat as unknown until verified.
Sources¶
src/SAIF.NetCore.Common/SAIF.NetCore.Common/SAIF.NetCore.Common/Types/NullableDateRange.cs