CodoMetis.ValueRanges
6.3.0
Prefix Reserved
See the version list below for details.
dotnet add package CodoMetis.ValueRanges --version 6.3.0
NuGet\Install-Package CodoMetis.ValueRanges -Version 6.3.0
<PackageReference Include="CodoMetis.ValueRanges" Version="6.3.0" />
<PackageVersion Include="CodoMetis.ValueRanges" Version="6.3.0" />
<PackageReference Include="CodoMetis.ValueRanges" />
paket add CodoMetis.ValueRanges --version 6.3.0
#r "nuget: CodoMetis.ValueRanges, 6.3.0"
#:package CodoMetis.ValueRanges@6.3.0
#addin nuget:?package=CodoMetis.ValueRanges&version=6.3.0
#tool nuget:?package=CodoMetis.ValueRanges&version=6.3.0
CodoMetis.ValueRanges
In-memory range and set types for .NET 10 — the complete PostgreSQL interval and membership algebra with no database dependency.
Two type families, both immutable and canonical at construction:
- Range types — the six value domains PostgreSQL ships as built-in range types, plus
TimeRange, each modelled as a discriminated union of five sealed variants, withRangeSet<TRange, T>as their always-normalized multirange counterpart. - Value sets (v6) — canonical sets of scalar values whose PostgreSQL storage shape is a native array.
Everything executes in process. The companion package CodoMetis.ValueRanges.EFCore.PostgreSQL maps both families to PostgreSQL columns and translates the algebra to SQL, so the same code works in memory and as a query.
dotnet add package CodoMetis.ValueRanges
Requires .NET 10 or later.
Range types
| .NET type | PostgreSQL equivalent | Element type | Discrete |
|---|---|---|---|
Int32Range |
int4range |
int |
✓ |
Int64Range |
int8range |
long |
✓ |
DecimalRange |
numrange |
decimal |
— |
DateRange |
daterange |
DateOnly |
✓ |
DateTimeRange |
tsrange |
DateTime |
— |
DateTimeOffsetRange |
tstzrange |
DateTimeOffset |
— |
TimeRange |
timerange (custom) |
TimeOnly |
— |
NodaTime equivalents — LocalDateRange, LocalDateTimeRange, InstantRange and the
month-granularity YearMonthRange — live in
CodoMetis.ValueRanges.NodaTime.
Unboundedness is a shape, not a bound value
Each range is a discriminated union of Finite, UnboundedStart, UnboundedEnd, EmptyRange and
Infinity. The shape lives in the static type: UnboundedEnd has no End property to put a
sentinel in, Finite has no flag to disown its End, and neither carries a nullable bound. Invalid
states are unrepresentable, and pattern matching is exhaustive with compiler-enforced coverage.
DateTimeRange.CreateUnboundedEnd(start) // genuinely open-ended
DateTimeRange.CreateFinite(start, DateTime.MaxValue) // ends at a specific instant
The two are not interchangeable and the compiler will not let them be confused — a distinction a
bounds-plus-flags representation has to reconcile at runtime, and one that still matters at the
database boundary, where DateTime.MaxValue maps to PostgreSQL infinity.
Operations
Containment, overlap and adjacency (Contains, Overlaps, IsContainedBy, IsAdjacentTo),
directional comparisons (IsStrictlyLeftOf/RightOf, DoesNotExtendLeftOf/RightOf), set
operations (Intersect, Union, Except, Complement, Merge), bound accessors
(LowerBound/UpperBound/LowerBoundInclusive/UpperBoundInclusive, matching PostgreSQL's
NULL semantics) and the RangeAgg/RangeIntersectAgg aggregates — all matching PostgreSQL's
results exactly, including its canonicalization of discrete ranges.
Why these element types
Interval algebra needs a total order the type's own comparisons agree with and, for adjacency, a
defined step. double and float have neither and fail quietly: double.CompareTo reports NaN
as less than every value and equal to itself, while the IEEE operators disagree, so a library
generic over IComparable<T> accepts double and answers containment against a NaN bound with a
straight face. Restricting the element types to a vetted set is what makes the algebra sound.
Value sets (v6)
A value set is an immutable, canonical set of scalar values — deduplicated, sorted, never
containing null, with structural equality. It relates to a PostgreSQL array column exactly as
RangeSet<DateRange, DateOnly> relates to datemultirange: the CLR type models the domain concept,
the column is its storage encoding.
| .NET type | Element type | PostgreSQL column | Wrapper arity |
|---|---|---|---|
StringSet |
string |
text[] |
StringSet<TElement> |
GuidSet |
Guid |
uuid[] |
GuidSet<TElement> |
Int16Set |
short |
smallint[] |
— |
Int32Set |
int |
integer[] |
Int32Set<TElement> |
Int64Set |
long |
bigint[] |
Int64Set<TElement> |
DecimalSet |
decimal |
numeric[] |
— |
DateSet |
DateOnly |
date[] |
— |
TimeSet |
TimeOnly |
time[] |
— |
DateTimeSet |
DateTime |
timestamp[] |
— |
DateTimeOffsetSet |
DateTimeOffset |
timestamptz[] |
— |
var tags = StringSet.From("beta", "alpha", "beta"); // {alpha,beta} — deduplicated, sorted
StringSet more = ["gamma", "alpha"]; // collection expressions work
tags.Contains("alpha"); // true
tags.Overlaps(more); // true — shares "alpha"
tags.IsProperSubsetOf(more); // false — proper containment excludes equality
tags.Union(more); // {alpha,beta,gamma}
tags.Count; // 2
The wrapper arities take generator-produced domain values — Vogen, Metalama, StronglyTypedId or hand-written — and are constrained only on BCL interfaces, so your domain types never reference this package.
Canonical form is the contract
Every construction path deduplicates and sorts: From, parsing, JSON, and materialization from the
database. That is load-bearing twice — the EF ValueComparer collapses to a cheap equality with no
false diffs, and SQL = on the stored array coincides with set equality.
String-backed sets sort ordinal, never culture-sensitive: canonical form is a cross-writer storage contract, not a display order, and a culture sort would make two machines disagree about the same set. Everything else sorts by the element's own comparison.
Intersect, Except and Add are client-side only — PostgreSQL's array type has no intersection,
difference or sorted insert.
Literals, parsing and JSON
Every type implements IParsable<T> and IFormattable using PostgreSQL literal syntax, and ships
System.Text.Json converters for direct use in ASP.NET Core APIs:
Int32Range.Parse("[1,10)", null).ToString(); // [1,10)
StringSet.Parse("{alpha,beta}", null); // {alpha,beta}
options.AddRangeConverters(); // registers both families
Documentation
The full README covers every operation with examples, the multirange operator matrix, the parsing and JSON rules, and the EF Core mapping. See also the changelog.
License
MIT — see LICENSE.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. 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. |
-
net10.0
- No dependencies.
NuGet packages (2)
Showing the top 2 NuGet packages that depend on CodoMetis.ValueRanges:
| Package | Downloads |
|---|---|
|
CodoMetis.ValueRanges.EFCore.PostgreSQL
Entity Framework Core (Npgsql) plugin for CodoMetis.ValueRanges: maps the range types to the PostgreSQL range columns (int4range, int8range, numrange, daterange, tsrange, tstzrange, and the custom timerange) and RangeSet<TRange, T> to the corresponding multirange columns — no manual value converters required. Translates the full range algebra from LINQ to SQL: Contains, Overlaps, IsContainedBy, IsStrictlyLeftOf/RightOf, DoesNotExtendLeftOf/RightOf, IsAdjacentTo (@>, <@, &&, <<, >>, &<, &>, -|-) on ranges and multiranges, bound accessors (lower, upper, lower_inc, upper_inc), Merge (range_merge), the RangeAgg/RangeIntersectAgg aggregates (range_agg, range_intersect_agg), Intersect (*), Union and Except (multirange + and -), RangeSet operations and operators including == equality, and the CreateFinite/CreateUnboundedStart/CreateUnboundedEnd factories as range constructor calls. v6 also maps the value set types to native PostgreSQL arrays (StringSet to text[], GuidSet to uuid[], …) by convention — wrapper instantiations like StringSet<YourKey> recognized automatically, nothing to configure — and translates the set algebra to the array operators: Contains as GIN-servable containment (@> ARRAY[value]), Overlaps (&&), IsSubsetOf (<@), IsSupersetOf (@>), Union (array_cat), Count/IsEmpty (cardinality). Enable with one line: options.UseNpgsql(..., npgsql => npgsql.UseValueRanges()). |
|
|
CodoMetis.ValueRanges.NodaTime
NodaTime range types for CodoMetis.ValueRanges: LocalDateRange (daterange), LocalDateTimeRange (tsrange), InstantRange (tstzrange) and YearMonthRange — a month-granularity range for billing and reporting periods, stored as a month-aligned daterange by the EF Core companion. Each range type is a discriminated union of five sealed variants (Finite, UnboundedStart, UnboundedEnd, EmptyRange, Infinity) with the full interval algebra of the core package: Contains, Overlaps, IsAdjacentTo, Intersect, Union, Except, Merge, bound accessors, RangeAgg/RangeIntersectAgg aggregates, RangeSet<TRange, T> multiranges, PostgreSQL literal parsing/formatting and System.Text.Json support. Includes conversions to and from NodaTime's own Interval and DateInterval. LocalDateTime is wall-clock time by construction and Instant is an instant by construction, so the DateTimeKind and offset-normalization caveats of the BCL-based types do not arise. v6 adds the NodaTime value sets — LocalDateSet (date[]), LocalDateTimeSet (timestamp[]), InstantSet (timestamptz[]), LocalTimeSet (time[]) and the month-granularity YearMonthSet (month-aligned date[]): immutable, canonical (deduplicated, sorted, null-free) sets with the full membership algebra, ISO-calendar normalization at construction, PostgreSQL array literals and JSON support. v6.4 adds a validated-wrapper arity for each of them (LocalDateSet<T>, LocalDateTimeSet<T>, InstantSet<T>, LocalTimeSet<T>, YearMonthSet<T>), constrained only on BCL interfaces, so generator-produced domain values never reference this package. |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 8.0.0 | 173 | 8/17/2026 |
| 7.0.0 | 168 | 8/17/2026 |
| 6.3.0 | 132 | 8/16/2026 |
| 6.2.1 | 150 | 8/16/2026 |
| 6.2.0 | 215 | 8/15/2026 |
| 6.1.0 | 123 | 8/14/2026 |
| 6.0.0 | 122 | 8/14/2026 |
| 5.0.0 | 118 | 8/13/2026 |
| 4.1.0 | 119 | 8/12/2026 |
| 4.0.0 | 104 | 8/10/2026 |
| 3.1.0 | 156 | 6/17/2026 |
| 3.0.0 | 134 | 6/11/2026 |
| 2.0.0 | 127 | 6/11/2026 |
| 1.0.0 | 124 | 6/10/2026 |