---
title: Classify nullable date-range overlap state
summary: Classify the relationship between two open-ended-capable date ranges into one of ten specific overlap categories.
status: draft
owner: Framework.NCore maintainers
last_reviewed: 2026-10-02
library: SAIF.NetCore.Common
reference_type: package
operation_id: OP-03
trigger: A consumer calls `NullableDateRange.GetOverlapState(rangeA, rangeB)`.
---

# 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:

1. If neither range has an end date (`!rangeA.EndDate.HasValue && !rangeB.EndDate.HasValue`): return `NeitherEnds`, **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 return `NeitherEnds`.
2. 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`.
3. Else if range A has no end date, range B does, and A's start falls strictly before B's start: return `RangeADoesNotEnd_RangeAStartBeforeRangeBStart`.
4. 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`.
5. 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`.
6. 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`.
7. Else if A's start is strictly after B's start and A's end is strictly before B's end: return `RangeAStartAfterRangeBStart_RangeAEndBeforeRangeBEnd`.
8. Else if A's start is strictly before B's start and A's end is strictly after B's end: return `RangeAStartBeforeRangeBStart_RangeAEndAfterRangeBEnd`.
9. Else if A's start is strictly before B's start and A's end is strictly before B's end: return `RangeAStartBeforeRangeBStart_RangeAEndBeforeRangeBEnd`.
10. 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 returns `NeitherEnds` before 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.StartDate` but `A.EndDate == B.EndDate`, which satisfies neither branch 6 (`A.EndDate > B.EndDate`) nor branch 7 (`A.EndDate < B.EndDate`), so it returns `NoOverlap` even 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.StartDate` and `A.EndDate > B.EndDate`) and return `RangeAStartAfterRangeBStart_RangeAEndAfterRangeBEnd` directly, with no fallthrough at all.

**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.

```mermaid
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 == null` for both) always return `NeitherEnds`, 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 returns `NoOverlap`), 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 `DateRangeOverlapResult` member names do not constitute a reliable interval-intersection test: `NoOverlap` can be returned for overlapping ranges, and a compound name describing one range starting and ending after another (for example `RangeAStartAfterRangeBStart_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.*
- `GetOverlapState` does not null-check `rangeA`/`rangeB` themselves; a `null` argument throws `NullReferenceException` on the first property access.*

## Related documentation
- [Operations and processes](../operations-processes.md)
- [Business rules](../business-rules.md)

> **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`
