Published May 01, 2025
Working with intervals in .NET is not always as easy as one would like. In order to solve this the library Ephemeral was created. At the beginning, it was meant to solve problems related to temporal intervals. With version 0.2.x it supports now generic intervals. But we are getting ahead of ourselves. Let’s start with the basics. The package required is nuget: Ephemeral
and last published version is 0.2.0-beta.6
An “interval” is defined as the values between two boundaries.
For example, let’s say that somebody tells you one specific item in a shop costs between $20 and $25. For the sake of simplicity, assume that both 20 and 25 are valid prices as well. You could represent this in a mathematical notation like this $20 \leq x \leq 25$ or alternatively $x \in [20,25]$.
In Ephemeral, the same would be accomplished by creating a closed interval.
// This namespace gives you access to the main classes and methods
using Marsop.Ephemeral.Core;
var myFirstInterval = BasicInterval<double>.CreateClosed(20.0, 25.0);
Console.WriteLine(myFirstInterval); // [20, 25]
The data type used to define the boundaries of one interval must be comparable. In particular, this is used to check the validity of one interval.
One interval (IBasicInterval<T>
) is considered valid when one of the following conditions is met:
Start
is lower than the End
Start
and End
are equal, then both are included in the intervalThe second case is known as “degenerate” interval, and it only includes one single point inside.
There is also some utility classes defined to work with intervals of doubles to avoid starting with generics.
// Utility classes for numbers (double, int) are included in the Numerics namespace.
using Marsop.Ephemeral.Numerics;
var mySecondInterval = DoubleInterval.CreateClosed(20.0, 25.0);
Console.WriteLine(mySecondInterval); // [20, 25]
You may have realized that we are using CreateClosed()
as our factory method. This is due to the fact that both boundaries are included. If instead neither of them were included, we would write $x \in (20,25)$ or $20 \lt x \lt 25$. In Ephemeral this is accomplished using CreateOpen()
var myOpenInterval = DoubleInterval.CreateOpen(20.0, 25.0);
Console.WriteLine(myOpenInterval); // (20, 25)
As you could have imagined, there are also semi-open (also known as half-open) intervals. In this cases, we can use the constructor as follows.
var myOpenClosedInterval = new DoubleInterval(20, 25, false, true);
var myClosedOpenInterval = new DoubleInterval(20, 25, true, false);
Console.WriteLine($"Open start, closed end: {myOpenClosedInterval}"); // Open start, closed end: (20, 25]
Console.WriteLine($"Closed start, open end: {myClosedOpenInterval}"); // Closed start, open end: [20, 25)
One of the first operations that you would use an interval for is to test inclusion. For example checking if one point is included in the interval.
var realPrice = 22.0;
Console.WriteLine($"Is {realPrice} in {myFirstInterval}? {myFirstInterval.Covers(realPrice)}"); // Is 22 in [20, 25]? True
var wayTooExpensivePrice = 30.0;
Console.WriteLine($"Is {wayTooExpensivePrice} in {myFirstInterval}? {myFirstInterval.Covers(wayTooExpensivePrice)}"); // Is 30 in [20, 25]? False
When checking for coverage, the boundaries do matter, so take this into account.
var point = 20.0;
Console.WriteLine($"Is {point} in {myFirstInterval}? {myFirstInterval.Covers(point)}"); // Is 20 in [20, 25]? True
Console.WriteLine($"Is {point} in {myOpenInterval}? {myOpenInterval.Covers(point)}"); // Is 20 in (20, 25)? False
Check if one interval is inside another one is equally simple.
var myBigInterval = DoubleInterval.CreateClosed(0.0, 100.0);
var mySmallInterval = DoubleInterval.CreateClosed(20.0, 25.0);
Console.WriteLine($"Is {mySmallInterval} in {myBigInterval}? {myBigInterval.Covers(mySmallInterval)}"); // Is [20, 25] in [0, 100]? True
var negativeInterval = DoubleInterval.CreateClosed(-10.0, -5.0);
Console.WriteLine($"Is {mySmallInterval} in {negativeInterval}? {negativeInterval.Covers(mySmallInterval)}"); // Is [20, 25] in [-10, -5]? False
Coverage has to be complete, that means that if at least one point in one interval is not covered, then the method returns false.
Console.WriteLine($"Is {myOpenInterval} in {myFirstInterval}? {myFirstInterval.Covers(myOpenInterval)}"); // Is (20, 25) in [20, 25]? True
Console.WriteLine($"Is {myFirstInterval} in {myOpenInterval}? {myOpenInterval.Covers(myFirstInterval)}"); // Is [20, 25] in (20, 25)? False