C# Library to handle intervals (composite start and end)
- Targets
.NET Standard 2.0for wide compatibility. - Support for generic types in intervals (e.g.
DateTimeOffset,int,double). - Support for open and closed intervals.
- Support for common operations like Covers(), Intersect(), Join(), etc. via extension methods.
- Support for disjoint collections of intervals via
DisjointIntervalSet. - Makes use of Optional to avoid returning
nullin many of the built-in operations.
The API-documentation is hosted at albertogregorio.com/ephemeral.
Starting from version 1, Ephemeral introduces generic intervals allowing boundaries of any type that implements IComparable<TBoundary>. It defines the interfaces IBasicInterval<TBoundary> and IInterval<TBoundary, TLength>, as well as base implementations BasicInterval<TBoundary> and FullInterval<TBoundary, TLength>.
To facilitate migration, Ephemeral provides several utility classes corresponding to standard types:
DateTimeOffsetInterval(forDateTimeOffset)IntInterval(forint)DoubleInterval(fordouble)DateOnlyIntervalandTimeOnlyInterval(available in .NET 6+ targets)
These utility classes extend FullInterval to offer standard duration logic right out of the box, similar to how Interval worked in older versions.
Here are graphical representations of common interval operations over the real line (using DoubleInterval).
Checks if an interval completely contains another interval (or point).
Real Line: 0---1---2---3---4---5---6---7---8---9---10
Interval A: [=======================] (A = [2, 8])
Interval B: [=======] (B = [4, 6])
A.Covers(B) -> true
Checks if two intervals overlap. Intersect() returns the overlapping interval.
Real Line: 0---1---2---3---4---5---6---7---8---9---10
Interval A: [===============] (A = [2, 6])
Interval B: [===============] (B = [4, 8])
A.Intersects(B) -> true
A.Intersect(B) -> [=======] ([4, 6])
Merges two overlapping or contiguous intervals into a single interval.
Real Line: 0---1---2---3---4---5---6---7---8---9---10
Interval A: [===============] (A = [2, 6])
Interval B: [===============] (B = [4, 8])
A.Join(B) -> [=======================] ([2, 8])
Subtracts one interval from another, returning a disjoint set of intervals.
Real Line: 0---1---2---3---4---5---6---7---8---9---10
Interval A: [=======================] (A = [2, 8])
Interval B: [=======] (B = [4, 6])
A.Subtract(B) -> [=======) (=======] ([2, 4) and (6, 8])
Combines two intervals into a collection (DisjointIntervalSet).
Real Line: 0---1---2---3---4---5---6---7---8---9---10
Interval A: [=======] (A = [2, 4])
Interval B: [=======] (B = [6, 8])
A.Union(B) -> [=======] [=======] ([2, 4] and [6, 8])
Checks if one interval starts exactly where another ends (without overlap, typically open/closed).
Real Line: 0---1---2---3---4---5---6---7---8---9---10
Interval A: [=======) (A = [2, 4))
Interval B: [=======] (B = [4, 6])
A.IsContiguouslyFollowedBy(B) -> true
Checks if an interval starts before another interval.
Real Line: 0---1---2---3---4---5---6---7---8---9---10
Interval A: [=======] (A = [2, 4])
Interval B: [=======] (B = [4, 6])
A.StartsBefore(B) -> true
var now = DateTimeOffset.UtcNow;
var yesterday = DateTimeOffsetInterval.CreateOpen(now.AddDays(-1), now);
var today = yesterday.Shift(TimeSpan.FromDays(1));
yesterday.Intersects(today); // returns false (because they are open/closed contiguously)var firstTen = IntInterval.CreateClosed(1, 10);
var nextTen = IntInterval.CreateClosed(11, 20);
firstTen.IsContiguouslyFollowedBy(nextTen); // returns false if both are closed.var lengthOperator = DateTimeOffsetStandardLengthOperator.Instance;
var collection = new DisjointIntervalSet<DateTimeOffset, TimeSpan>(lengthOperator);
collection.Add(yesterday);
collection.Add(today);
collection.Start == yesterday.Start; // true
collection.End == today.End; // true
var collection2 = yesterday.Union(today, lengthOperator);
var consolidatedCollection = collection2.Consolidate();
collection2.Count(); // 2
consolidatedCollection.Count(); // 1