Intervals.NET 0.0.0-rc.1

This is a prerelease version of Intervals.NET.
There is a newer version of this package available.
See the version list below for details.
dotnet add package Intervals.NET --version 0.0.0-rc.1
                    
NuGet\Install-Package Intervals.NET -Version 0.0.0-rc.1
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Intervals.NET" Version="0.0.0-rc.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Intervals.NET" Version="0.0.0-rc.1" />
                    
Directory.Packages.props
<PackageReference Include="Intervals.NET" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Intervals.NET --version 0.0.0-rc.1
                    
#r "nuget: Intervals.NET, 0.0.0-rc.1"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Intervals.NET@0.0.0-rc.1
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Intervals.NET&version=0.0.0-rc.1&prerelease
                    
Install as a Cake Addin
#tool nuget:?package=Intervals.NET&version=0.0.0-rc.1&prerelease
                    
Install as a Cake Tool

Intervals.NET

.NET License NuGet

A high-performance, zero-allocation .NET library for working with mathematical intervals and ranges. Built with modern C# features including structs, spans, and records for optimal performance and type safety.

๐ŸŽฏ Purpose

Intervals.NET provides a robust, type-safe way to represent and manipulate intervals (ranges) of comparable values. Whether you're working with numeric ranges, date intervals, or any comparable type, this library offers:

  • Type Safety: Generic constraints ensure you only work with comparable types
  • Zero Allocation: Struct-based design for optimal performance
  • Infinite Ranges: First-class support for unbounded intervals
  • Rich API: Comprehensive set of operations (intersection, union, contains, overlaps, etc.)
  • Flexible Parsing: Parse from strings with ReadOnlySpan<char> or interpolated strings for zero-allocation performance
  • Extensibility: Extension methods for custom range operations

๐Ÿ“ฆ Installation

dotnet add package Intervals.NET

๐Ÿš€ Quick Start

using Intervals.NET.Factories;

// Create a closed range [10, 20]
var range = Range.Closed(10, 20);

// Check if value is in range
bool contains = range.Contains(15); // true

// Create ranges with different inclusivity
var openRange = Range.Open(0, 100);        // (0, 100)
var halfOpen = Range.ClosedOpen(1, 10);    // [1, 10)
var halfClosed = Range.OpenClosed(1, 10);  // (1, 10]

// Unbounded ranges
var greaterThan = Range.ClosedOpen(0, RangeValue<int>.PositiveInfinity);  // [0, โˆž)
var lessThan = Range.Open(RangeValue<int>.NegativeInfinity, 100);         // (-โˆž, 100)
var unbounded = Range.Open(RangeValue<int>.NegativeInfinity, 
                           RangeValue<int>.PositiveInfinity);              // (-โˆž, โˆž)

๐Ÿ”‘ Core Concepts

Range Notation

Intervals.NET uses standard mathematical interval notation:

  • [a, b] - Closed: includes both a and b
  • (a, b) - Open: excludes both a and b
  • [a, b) - Half-open: includes a, excludes b
  • (a, b] - Half-closed: excludes a, includes b

Infinity Support

Represent unbounded ranges naturally:

// Values greater than or equal to 18
var adults = Range.Closed(18, RangeValue<int>.PositiveInfinity);

// All dates before 2024
var historical = Range.Open(
    RangeValue<DateTime>.NegativeInfinity, 
    new DateTime(2024, 1, 1)
);

// Parse infinity symbols
var range = Range.FromString<int>("[-โˆž, 100]");  // or "[, 100]"

๐Ÿ’ก Usage Examples

Basic Operations

using Intervals.NET.Factories;

var range1 = Range.Closed(10, 30);
var range2 = Range.Closed(20, 40);

// Check overlap
if (range1.Overlaps(range2))
{
    Console.WriteLine("Ranges overlap!");
}

// Get intersection
var intersection = range1.Intersect(range2);
// Result: [20, 30]

// Get union (if ranges overlap or are adjacent)
var union = range1.Union(range2);
// Result: [10, 40]

// Check containment
bool contains = range1.Contains(Range.Closed(15, 25)); // true
bool containsValue = range1.Contains(25);               // true

Set Operations

var range = Range.Closed(10, 50);
var exclude = Range.Closed(20, 30);

// Subtract a range
var remaining = range.Except(exclude).ToList();
// Result: [10, 20) and (30, 50]

// Check if ranges are adjacent
bool adjacent = Range.ClosedOpen(10, 20).IsAdjacent(Range.Closed(20, 30));
// Result: true

// Check ordering
bool before = Range.Closed(10, 20).IsBefore(Range.Closed(30, 40));
// Result: true

Range Properties

var finiteRange = Range.Closed(1, 10);
var infiniteRange = Range.Open(
    RangeValue<int>.NegativeInfinity,
    RangeValue<int>.PositiveInfinity
);

// Check if range is bounded
bool isBounded = finiteRange.IsBounded();      // true
bool isUnbounded = infiniteRange.IsUnbounded(); // true

// Check if completely infinite
bool isInfinite = infiniteRange.IsInfinite();   // true

// Check if empty (always false - empty ranges can't be constructed)
bool isEmpty = finiteRange.IsEmpty();           // false

Parsing Ranges from Strings

using Intervals.NET.Parsers;

// Parse various formats
var range1 = Range.FromString<int>("[10, 20]");
var range2 = Range.FromString<int>("(0, 100)");
var range3 = Range.FromString<double>("[1.5, 9.5)");

// Parse with infinity
var range4 = Range.FromString<int>("[, 100]");        // (-โˆž, 100]
var range5 = Range.FromString<int>("[0, ]");          // [0, โˆž)
var range6 = Range.FromString<int>("(-โˆž, โˆž)");       // (-โˆž, โˆž)

// Parse with custom culture
var culture = new System.Globalization.CultureInfo("de-DE");
var range7 = Range.FromString<double>("[1,5; 9,5]", culture);

// TryParse for safe parsing
if (RangeParser.TryParse<int>("[10, 20)", out var range))
{
    Console.WriteLine($"Parsed: {range}");
}

Zero-Allocation Interpolated String Parsing

For maximum performance when building ranges from runtime values, Range.FromString<T>() supports interpolated strings with zero allocations:

// Traditional: allocates string first
string str = $"[{start}, {end}]";  // Allocation
var range1 = Range.FromString<int>(str);

// Optimized: values parsed directly, no string allocation
int start = 10, end = 20;
var range2 = Range.FromString<int>($"[{start}, {end}]");  // Zero allocations! โšก

// Works with expressions, different types, and infinity
var computed = Range.FromString<int>($"[{start * 2}, {end * 3})");
var dateRange = Range.FromString<DateTime>($"[{DateTime.Today}, {DateTime.Today.AddDays(7)})");
var unbounded = Range.FromString<int>($"[{RangeValue<int>.NegativeInfinity}, {100}]");

When to use: Building ranges from variables/expressions where performance matters.
How it works: Custom InterpolatedStringHandler processes values directly without materializing the string.

Working with Different Types

// Numeric ranges
var intRange = Range.Closed(1, 100);
var doubleRange = Range.Open(0.0, 1.0);
var decimalRange = Range.Closed(0m, 100m);

// Date and time ranges
var dateRange = Range.ClosedOpen(
    new DateTime(2024, 1, 1),
    new DateTime(2025, 1, 1)
);

var timeRange = Range.Closed(
    TimeSpan.FromHours(9),
    TimeSpan.FromHours(17)
);

// String ranges (lexicographic ordering)
var letterRange = Range.Closed("A", "Z");

// Custom comparable types
public record Temperature(double Celsius) : IComparable<Temperature>
{
    public int CompareTo(Temperature? other) =>
        Celsius.CompareTo(other?.Celsius ?? double.NegativeInfinity);
}

var tempRange = Range.Closed(
    new Temperature(-10),
    new Temperature(30)
);

Advanced Patterns

Building Complex Conditions
// Age categories
var children = Range.ClosedOpen(0, 13);
var teenagers = Range.ClosedOpen(13, 18);
var adults = Range.Closed(18, RangeValue<int>.PositiveInfinity);

int age = 25;
if (adults.Contains(age))
{
    Console.WriteLine("Adult");
}
Date Range Operations
// Business hours
var businessHours = Range.Closed(
    TimeSpan.FromHours(9),
    TimeSpan.FromHours(17)
);

// Check if time falls within business hours
bool isDuringBusinessHours = businessHours.Contains(DateTime.Now.TimeOfDay);

// Year-to-date range
var ytd = Range.Closed(
    new DateTime(DateTime.Now.Year, 1, 1),
    DateTime.Now
);
Validation Scenarios
// Validate configuration values
var validPort = Range.Closed(1, 65535);
var validPercentage = Range.Closed(0.0, 100.0);
var validAge = Range.Closed(0, 150);

public void ValidatePort(int port)
{
    if (!validPort.Contains(port))
        throw new ArgumentOutOfRangeException(nameof(port), 
            $"Port must be in range {validPort}");
}
Price Ranges and Discounts
var budgetRange = Range.Closed(0m, 50m);
var midRange = Range.Open(50m, 200m);
var premiumRange = Range.Closed(200m, RangeValue<decimal>.PositiveInfinity);

decimal price = 149.99m;

if (budgetRange.Contains(price))
    ApplyDiscount(0.05m);
else if (midRange.Contains(price))
    ApplyDiscount(0.10m);
else if (premiumRange.Contains(price))
    ApplyDiscount(0.15m);

๐ŸŽฏ Extension Methods

Intervals.NET provides rich extension methods for common operations:

using Intervals.NET.Extensions;

var range1 = Range.Closed(10, 30);
var range2 = Range.Closed(20, 40);

// Overlap detection
bool overlaps = range1.Overlaps(range2);

// Containment
bool contains = range1.Contains(range2);
bool containsValue = range1.Contains(25);

// Set operations
var intersection = range1.Intersect(range2);  // Returns Range<T>?
var union = range1.Union(range2);             // Returns Range<T>?
var except = range1.Except(range2);           // Returns IEnumerable<Range<T>>

// Adjacency and ordering
bool adjacent = range1.IsAdjacent(range2);
bool before = range1.IsBefore(range2);
bool after = range1.IsAfter(range2);

// Range properties
bool bounded = range1.IsBounded();
bool unbounded = range1.IsUnbounded();
bool infinite = range1.IsInfinite();
bool empty = range1.IsEmpty();

๐Ÿ”ง Operators

Ranges support intuitive operators:

var range1 = Range.Closed(10, 30);
var range2 = Range.Closed(20, 40);

// Intersection operator
var intersection = range1 & range2;  // [20, 30]

// Union operator
var union = range1 | range2;         // [10, 40]

โšก Performance

Intervals.NET is designed for maximum performance:

  • Zero Allocation: All types are structs - no heap allocations
  • Stack-based: Ranges live on the stack
  • Inline-friendly: Small methods optimized for inlining
  • No Boxing: Generic constraints avoid boxing
  • Span-based Parsing: Uses ReadOnlySpan<char> for efficient string parsing
  • Interpolated String Support: Zero-allocation parsing from interpolated strings when using variables

Performance Characteristics

// O(1) - Constant time operations
range.Contains(value)
range.IsEmpty()
range.IsBounded()
range.ToString()

// O(1) - Intersection and union when ranges overlap
range1.Intersect(range2)
range1.Union(range2)

// O(1) - Comparison operations
range1.Overlaps(range2)
range1.IsBefore(range2)
range1.Contains(range2)

// O(n) where n โ‰ค 2 - Except operation
range1.Except(range2)  // Returns 0, 1, or 2 ranges

๐Ÿ†š Why Use Intervals.NET?

vs. Manual Range Implementation

Aspect Intervals.NET Manual Implementation
Type Safety โœ… Generic constraints โš ๏ธ Must implement
Edge Cases โœ… All handled โŒ Often forgotten
Infinity โœ… Built-in โŒ Manual handling
String Parsing โœ… Included โŒ Must implement
Set Operations โœ… Rich API โŒ Must implement
Testing โœ… Thoroughly tested โš ๏ธ Your responsibility

๐Ÿ’Ž Best Practices

โœ… Do's

// DO: Use appropriate inclusivity for your domain
var age = Range.ClosedOpen(0, 18);  // 0 โ‰ค age < 18

// DO: Use infinity for unbounded ranges
var positive = Range.Open(0, RangeValue<int>.PositiveInfinity);

// DO: Check for null when operations may not produce a result
var intersection = range1.Intersect(range2);
if (intersection.HasValue)
{
    ProcessRange(intersection.Value);
}

// DO: Use TryParse for user input
if (RangeParser.TryParse<int>(userInput, out var range))
{
    // Use range
}

// DO: Use factory methods for clarity
var range = Range.Closed(1, 10);  // Clear intent

โŒ Don'ts

// DON'T: Try to create invalid ranges (will throw)
// var invalid = Range.Closed(20, 10);  // ArgumentException

// DON'T: Assume union always succeeds
var union = range1.Union(range2);
// Check union.HasValue before using!

// DON'T: Ignore culture when parsing
// var range = Range.FromString<double>("[1,5, 9,5]");  
// Use appropriate CultureInfo for comma as decimal separator

// DON'T: Box ranges unnecessarily
// IComparable boxed = range;  // Avoid boxing

๐ŸŽฏ Common Patterns

Safe Range Operations
public Range<T>? SafeIntersect<T>(Range<T> r1, Range<T> r2) 
    where T : IComparable<T>
{
    return r1.Overlaps(r2) ? r1.Intersect(r2) : null;
}
Range Validation
public static class ValidationRanges
{
    public static readonly Range<int> ValidPort = Range.Closed(1, 65535);
    public static readonly Range<int> ValidPercentage = Range.Closed(0, 100);
    public static readonly Range<double> ValidLatitude = Range.Closed(-90.0, 90.0);
    public static readonly Range<double> ValidLongitude = Range.Closed(-180.0, 180.0);
}

public void Validate(int port)
{
    if (!ValidationRanges.ValidPort.Contains(port))
        throw new ArgumentOutOfRangeException(nameof(port));
}
Range-Based Configuration
public class ServiceConfiguration
{
    public Range<int> AllowedPortRange { get; init; } = Range.Closed(8000, 9000);
    public Range<TimeSpan> MaintenanceWindow { get; init; } = Range.Closed(
        TimeSpan.FromHours(2),
        TimeSpan.FromHours(4)
    );
    
    public bool IsMaintenanceTime(DateTime now)
    {
        return MaintenanceWindow.Contains(now.TimeOfDay);
    }
}

๐Ÿ“š API Reference

Factory Methods

// Create ranges with different inclusivity
Range.Closed<T>(start, end)      // [start, end]
Range.Open<T>(start, end)        // (start, end)
Range.ClosedOpen<T>(start, end)  // [start, end)
Range.OpenClosed<T>(start, end)  // (start, end]

// Parse from string
Range.FromString<T>(string input, IFormatProvider? provider = null)

Extension Methods

// Overlap and containment
range.Overlaps(other)
range.Contains(value)
range.Contains(other)

// Set operations
range.Intersect(other)  // Returns Range<T>?
range.Union(other)      // Returns Range<T>?
range.Except(other)     // Returns IEnumerable<Range<T>>

// Adjacency and ordering
range.IsAdjacent(other)
range.IsBefore(other)
range.IsAfter(other)

// Properties
range.IsBounded()
range.IsUnbounded()
range.IsInfinite()
range.IsEmpty()

Range Properties

range.Start            // RangeValue<T>
range.End              // RangeValue<T>
range.IsStartInclusive // bool
range.IsEndInclusive   // bool

RangeValue Properties

value.IsFinite
value.IsPositiveInfinity
value.IsNegativeInfinity
value.Value           // T (throws for infinity)
value.TryGetValue(out T val)  // Safe extraction

๐Ÿงช Testing

The library includes comprehensive unit tests that serve as interactive documentation:

// See RangeTests.cs for Range<T> examples
// See RangeValueTests.cs for RangeValue<T> examples
// See RangeExtensionsTests.cs for extension method examples
// See RangeParserTests.cs for parsing examples
// See RangeFactoryTests.cs for factory method examples

๐Ÿค Contributing

Contributions are welcome! Please feel free to submit issues and pull requests.

Development Requirements

  • .NET 8.0 SDK
  • Any IDE supporting C# (Visual Studio, Rider, VS Code)

Building

dotnet build

Running Tests

dotnet test

๐Ÿ“„ License

This project is licensed under the MIT License - see the LICENSE file for details.

๐Ÿ™ Acknowledgments

  • Built with modern C# and .NET 8.0 features
  • Designed to solve real-world interval manipulation needs in .NET applications

๐Ÿ“– Further Reading


Made with โค๏ธ for the .NET community

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • net8.0

    • No dependencies.

NuGet packages (2)

Showing the top 2 NuGet packages that depend on Intervals.NET:

Package Downloads
Intervals.NET.Domain.Extensions

Extension methods for domain-aware range operations in Intervals.NET. Provides Span (count steps), Expand, ExpandByRatio, and Shift operations. Clearly separated into Fixed (O(1)) and Variable (O(N)) namespaces for explicit performance semantics. Works seamlessly with all domain implementations.

Intervals.NET.Data

RangeData<TRange, TData, TDomain> โ€” a lazy, immutable abstraction that couples a logical Range<TRange>; a data sequence (IEnumerable<TData>); and a discrete IRangeDomain<TRange>. Designed so the domain-measured range length matches the data sequence length (optionally validated via the IsValid extension), enables right-biased union/intersection, and provides efficient, low-allocation operations for time-series, event streams, and incremental datasets.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.0.4 231 2/3/2026
0.0.3 94 1/30/2026
0.0.2 104 1/13/2026
0.0.1 102 1/8/2026
0.0.0-rc.1 63 1/8/2026

Initial release candidate (v0.0.0-rc.1):
- Core range operations with all bracket types: [], (), [), (]
- Rich API: Overlaps, Contains, Intersect, Union, Except, IsAdjacent, IsBefore, IsAfter
- First-class infinity support with RangeValue<T>
- Zero-allocation struct-based design
- String parsing with ReadOnlySpan<char> and culture support
- Interpolated string parsing with custom handler for zero allocations
- Infinity symbol support (โˆž, -โˆž) in parsing
- Comprehensive extension methods
- Full XML documentation
- .NET 8.0 target