---
title: Evaluate date-range overlap and validity
summary: Determine whether `DateRange` values are individually valid, overlap pairwise or across a sequence, are contiguous, or contain/are matched by a given date.
status: draft
owner: Framework.NCore maintainers
last_reviewed: 2026-10-02
library: SAIF.NetCore.Common
reference_type: package
operation_id: OP-02
trigger: A consumer calls `DateRangeExtensions.IsOverlapping`, `IsValidRange`/`IsOpenEnded`/`IsFutureRange`/`IsPastRange`, `IsContiguous`, `IsOverLapping` (sequence form), `IsInRange`, `AsOf<T>`, `ContainsDateRange`, or `ContainsBeginDate`.
---

# Evaluate date-range overlap and validity

Determine whether `DateRange` values are individually valid, overlap pairwise or across an ordered sequence, form a contiguous sequence, or contain/are matched by a given date. This operation covers every member of `DateRangeExtensions` that compares, validates, or filters `DateRange` values.

| Field | Value |
| --- | --- |
| **Operation ID** | OP-02 |
| **Trigger** | A consumer calls `DateRangeExtensions.IsOverlapping`, `IsValidRange`/`IsOpenEnded`/`IsFutureRange`/`IsPastRange`, `IsContiguous`, `IsOverLapping` (sequence form), `IsInRange`, `AsOf<T>`, `ContainsDateRange`, or `ContainsBeginDate`. |
| **Inputs** | `range1`, `range2` (`DateRange`), optional `inAllowSameDateOverlap` (bool, default false); an `IEnumerable<DateRange>`/`IEnumerable<T> where T : DateRange` plus a `ContiguousOptions`/`OverlappingOptions`/`DateTime`/`DateTime?` as appropriate |
| **Outputs** | A `bool` result for the validity/overlap/contiguity/membership members; `IEnumerable<T>` for `AsOf<T>`. |
| **Primary components** | `DateRangeExtensions.IsOverlapping`, `.IsValidRange`, `.IsOpenEnded`, `.IsFutureRange`, `.IsPastRange`, `.IsContiguous`, `.IsOverLapping`, `.IsInRange`, `.AsOf<T>`, `.ContainsDateRange`, `.ContainsBeginDate` |
| **Entry point** | `src/SAIF.NetCore.Common/SAIF.NetCore.Common/SAIF.NetCore.Common/Extensions/DateRangeExtensions.cs` |

## Steps

### Pairwise overlap (IsOverlapping)

1. Evaluate the leading guard `range2 != null`, which invokes `DateRange`'s custom `!=` operator with the second operand bound to the `null` literal; the operator body dereferences that operand's `BeginDate` unconditionally, so this guard throws `NullReferenceException` for essentially every call — with a null `range2` or a perfectly valid one — before any other step runs. ([BR-013](../business-rules.md#br-013-overlapping-ranges-require-both-ranges-valid-and-respect-the-same-date-overlap-flag))
2. *(Intended algorithm, unreached in ordinary use because of step 1.)* Verify both ranges pass `IsValidRange` (begin <= end). ([BR-012](../business-rules.md#br-012-a-date-range-is-valid-only-when-begin-does-not-exceed-end))
   - If either range is invalid: `IsOverlapping` returns `false`.
3. *(Intended algorithm, unreached in ordinary use.)* Select the comparison mode: `Exclusive` when `inAllowSameDateOverlap` is `true`, otherwise `Inclusive`.
4. *(Intended algorithm, unreached in ordinary use.)* Test whether either range's begin or end date `IsBetween` the other range's dates, using the selected comparison mode, or whether the two ranges are exactly equal with begin not equal to end.

### Single-range validity (IsValidRange/IsOpenEnded/IsFutureRange/IsPastRange)

1. `IsValidRange` returns `BeginDate <= EndDate` directly; this comparison itself cannot throw, but calling `IsValidRange` on a `null` `DateRange` receiver throws `NullReferenceException` when `BeginDate` is dereferenced, since the extension method does not null-check `range`. ([BR-012](../business-rules.md#br-012-a-date-range-is-valid-only-when-begin-does-not-exceed-end))
2. `IsOpenEnded` returns whether `BeginDate == DateTime.MinValue || EndDate == DateTime.MaxValue`; the same null-receiver caveat applies — a `null` `DateRange` throws `NullReferenceException` on the first property access rather than returning a `bool`.
3. `IsFutureRange`/`IsPastRange` call `IsValidRange` first, then compare against `DateTime.Now`; results depend on the server's current clock and time zone.*

### Sequence contiguity (IsContiguous)

1. Fewer than 2 ranges in the input sequence: return `true` immediately. ([BR-014](../business-rules.md#br-014-contiguous-range-evaluation-has-three-selectable-policies-and-a-short-circuit-for-fewer-than-two-ranges))
2. Order the remaining ranges by `BeginDate`.
3. Test each consecutive pair against the selected `ContiguousOptions` policy:
   - `StartMatchesEnd`: the current range's begin must equal the previous range's end.
   - `StartIsNextDay` (default): the previous range's end plus one day must equal the current range's begin.
   - `AllowGaps`: the current range's begin must be at or after the previous range's end.
4. Return `false` as soon as any consecutive pair fails the selected policy; otherwise return `true` after the last pair.

### Sequence overlap (IsOverLapping)

1. Fewer than 2 ranges in the input sequence: return `false` immediately.
2. Order the remaining ranges by `BeginDate`.
3. On the first iteration, skip the "ignore null date" branch check and fall through directly into the unconditional `switch (option)` block below — `previous` is still `null` at this point.
4. The `switch (option)` block dereferences `previous.EndDate` (`DatesMayMatch`: `range.BeginDate < previous.EndDate`; `DatesMayNotMatch`: `range.BeginDate <= previous.EndDate`) regardless of loop position, so this dereference of a `null` `previous` throws `NullReferenceException` on the first iteration for any input of 2 or more ranges — the method cannot return normally for its only valid input size.*
5. *(Intended algorithm, unreached because of step 4.)* For later iterations, an open-ended previous range followed by a non-`MinValue`/`MaxValue` begin date returns `true`; otherwise the selected `OverlappingOptions` policy decides whether consecutive ranges overlap.

### Range membership and date filtering (IsInRange, AsOf-T, ContainsDateRange, ContainsBeginDate)

1. `IsInRange(DateTime, IncludeComparisons)` tests whether `date` falls within `range.BeginDate`/`EndDate` using `IComparableExtensions.IsBetween` under the selected boundary-inclusion mode (default `FirstOnly`: begin inclusive, end exclusive). ([BR-015](../business-rules.md#br-015-in-range-tests-default-to-include-begin-exclude-end))
2. `IsInRange(DateTime?, IncludeComparisons)` returns `false` immediately for a `null` date, otherwise delegates to step 1.
3. `AsOf<T>(ranges, date, rangeOption)` filters `ranges` with `.Where(r => r.IsInRange(date, rangeOption))` and returns every matching item, not just the first. ([BR-015](../business-rules.md#br-015-in-range-tests-default-to-include-begin-exclude-end))
4. `ContainsDateRange(ranges, range)` returns whether any element of `ranges` has a `BeginDate` and `EndDate` both exactly equal to `range`'s, via `Any` — an exact begin/end membership test, not geometric containment.
5. `ContainsBeginDate(ranges, beginDate)` returns whether any element of `ranges` has a `BeginDate` exactly equal to `beginDate`, via `Any`.

## Process flow

The following diagrams visualize each branch family's control flow separately, since they are independent code paths with distinct short-circuits and error conditions.

The first diagram covers `IsOverlapping`'s pairwise-overlap control flow, showing the unconditional guard failure before the intended validity/overlap branches.

```mermaid
flowchart TD
	Start([Call IsOverlapping]) --> NullGuard["Evaluate range2 != null<br>(DateRange operator !=)"]
	NullGuard -->|"Always dereferences<br>the null-literal operand"| Throws([Throws NullReferenceException])
	NullGuard -.->|"Intended, unreached"| ValidCheck{Both ranges<br>IsValidRange?}
	ValidCheck -.->|No| ReturnFalse([Return false])
	ValidCheck -.->|Yes| ModeCheck{inAllowSameDateOverlap?}
	ModeCheck -.->|Yes| Exclusive[Use Exclusive comparison]
	ModeCheck -.->|No| Inclusive[Use Inclusive comparison]
	Exclusive -.-> Test["Test begin/end IsBetween<br>other range"]
	Inclusive -.-> Test
	Test -.-> End([Return bool result])
```

The second diagram covers the sequence-overlap (`IsOverLapping`) control flow, showing its own unconditional first-iteration failure, separate from the pairwise `IsOverlapping` guard above.

```mermaid
flowchart TD
	S_Start([Call IsOverLapping]) --> S_CountCheck{Fewer than<br>2 ranges?}
	S_CountCheck -->|Yes| S_False([Return false])
	S_CountCheck -->|No| S_Order["Order ranges<br>by BeginDate"]
	S_Order --> S_First["First iteration:<br>previous is still null"]
	S_First --> S_Switch["Evaluate switch(option)<br>on previous.EndDate"]
	S_Switch -->|"Always dereferences<br>null previous"| S_Throws([Throws NullReferenceException])
	S_Switch -.->|"Intended, unreached"| S_OpenCheck{Previous open-ended<br>and current not<br>MinValue/MaxValue?}
	S_OpenCheck -.->|Yes| S_True([Return true])
	S_OpenCheck -.->|No| S_PolicyCheck{OverlappingOptions<br>policy satisfied?}
	S_PolicyCheck -.->|Yes| S_True
	S_PolicyCheck -.->|No| S_Continue(["Continue to<br>next range"])
```

The third diagram covers sequence contiguity (`IsContiguous`), which short-circuits cleanly and completes end-to-end without the null-dereference defects above.

```mermaid
flowchart TD
	C_Start([Call IsContiguous]) --> C_CountCheck{Fewer than<br>2 ranges?}
	C_CountCheck -->|Yes| C_True([Return true])
	C_CountCheck -->|No| C_Order["Order ranges<br>by BeginDate"]
	C_Order --> C_PairCheck{Each pair satisfies<br>selected policy?}
	C_PairCheck -->|No, any pair| C_False([Return false])
	C_PairCheck -->|Yes, all pairs| C_True
```

The fourth diagram covers single-range validity (`IsValidRange`/`IsOpenEnded`) and range membership/date filtering (`IsInRange`, `AsOf<T>`, `ContainsDateRange`, `ContainsBeginDate`), all of which complete normally for a non-null receiver/sequence.

```mermaid
flowchart TD
	M_Start(["Call validity or<br>membership member"]) --> M_Which{Which member?}
	M_Which -->|IsValidRange| M_Compare1(["Return BeginDate<br><= EndDate"])
	M_Which -->|IsOpenEnded| M_Compare2(["Return BeginDate==Min<br>or EndDate==Max"])
	M_Which -->|IsInRange| M_Between(["Return date.IsBetween<br>(begin, end, mode)"])
	M_Which -->|AsOf-T| M_Filter(["Return Where filter<br>of matching items"])
	M_Which -->|ContainsDateRange<br>or ContainsBeginDate| M_Any(["Return Any exact-<br>match test"])
```

## Failure and edge-case behavior

- `IsOverlapping` throws `NullReferenceException` from the `range2 != null` guard for essentially every call, whether `range2` is `null` or a valid, non-null `DateRange`; the `IsValidRange`/comparison-mode/`IsBetween` logic described in the steps above is the intended algorithm but is not reachable in ordinary use.*
- `IsOverLapping` (sequence form) throws `NullReferenceException` for any input of 2 or more ranges, because the `switch (option)` block dereferences `previous.EndDate` unconditionally on the first iteration while `previous` is still `null`.*
- `IsContiguous` returns `true` (not an exception) for fewer than 2 ranges; this is the one short-circuit case in this operation group that behaves as intended end-to-end.
- `IsValidRange`/`IsOpenEnded` perform direct date comparisons on `range`'s own properties with no null check of their own, so they return a normal `bool` for any non-null `DateRange` receiver but throw `NullReferenceException` if the receiver itself is `null`.
- `IsFutureRange`/`IsPastRange` depend on `DateTime.Now`, so results vary with server clock and time zone.*
- `IsInRange(DateTime?, IncludeComparisons)` returns `false` for a `null` date rather than throwing.

## 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/Extensions/DateRangeExtensions.cs`
- `src/SAIF.NetCore.Common/SAIF.NetCore.Common/SAIF.NetCore.Common/Types/DateRange.cs`
