diff --git a/src/Npgsql.NodaTime/Internal/IntervalConverter.cs b/src/Npgsql.NodaTime/Internal/IntervalConverter.cs index 7da4aa401c..f062079a4a 100644 --- a/src/Npgsql.NodaTime/Internal/IntervalConverter.cs +++ b/src/Npgsql.NodaTime/Internal/IntervalConverter.cs @@ -6,7 +6,7 @@ namespace Npgsql.NodaTime.Internal; -public class IntervalConverter(PgConverter> rangeConverter) : PgStreamingConverter +sealed class IntervalConverter(PgConverter> rangeConverter, bool dateTimeInfinityConversions) : PgStreamingConverter { public override Interval Read(PgReader reader) => Read(async: false, reader, CancellationToken.None).GetAwaiter().GetResult(); @@ -27,7 +27,12 @@ async ValueTask Read(bool async, PgReader reader, CancellationToken ca : range.LowerBoundIsInclusive ? range.LowerBound : range.LowerBound + Duration.Epsilon; - Instant? end = range.UpperBoundInfinite + // For ranges with element types with infinity values (datetime, date etc.) an + // inclusive lower/upper bound causes their -/+ infinity (respectively) to fall within the range. + // If those values are returned for such a range postgres will not mark the affected bound as infinite accordingly. + // This is documented in https://www.postgresql.org/docs/current/rangetypes.html#RANGETYPES-INFINITE + // As NodaTime uses an exclusive upper bound we must consider this case as being another form of infinity (null). + Instant? end = range.UpperBoundInfinite || (dateTimeInfinityConversions && range.UpperBoundIsInclusive && range.UpperBound == Instant.MaxValue) ? null : range.UpperBoundIsInclusive ? range.UpperBound + Duration.Epsilon diff --git a/src/Npgsql.NodaTime/Internal/NodaTimeTypeInfoResolverFactory.Multirange.cs b/src/Npgsql.NodaTime/Internal/NodaTimeTypeInfoResolverFactory.Multirange.cs index 42c6360dad..fdd8d4c78f 100644 --- a/src/Npgsql.NodaTime/Internal/NodaTimeTypeInfoResolverFactory.Multirange.cs +++ b/src/Npgsql.NodaTime/Internal/NodaTimeTypeInfoResolverFactory.Multirange.cs @@ -31,12 +31,12 @@ static TypeInfoMappingCollection AddMappings(TypeInfoMappingCollection mappings) mappings.AddType(TimestampTzMultirangeDataTypeName, static (options, mapping, _) => mapping.CreateInfo(options, CreateArrayMultirangeConverter(new IntervalConverter( - CreateRangeConverter(new InstantConverter(options.EnableDateTimeInfinityConversions), options)), options)), + CreateRangeConverter(new InstantConverter(options.EnableDateTimeInfinityConversions), options), options.EnableDateTimeInfinityConversions), options)), isDefault: true); mappings.AddType>(TimestampTzMultirangeDataTypeName, static (options, mapping, _) => mapping.CreateInfo(options, CreateListMultirangeConverter(new IntervalConverter( - CreateRangeConverter(new InstantConverter(options.EnableDateTimeInfinityConversions), options)), options))); + CreateRangeConverter(new InstantConverter(options.EnableDateTimeInfinityConversions), options), options.EnableDateTimeInfinityConversions), options))); mappings.AddType[]>(TimestampTzMultirangeDataTypeName, static (options, mapping, _) => mapping.CreateInfo(options, diff --git a/src/Npgsql.NodaTime/Internal/NodaTimeTypeInfoResolverFactory.Range.cs b/src/Npgsql.NodaTime/Internal/NodaTimeTypeInfoResolverFactory.Range.cs index f62669333c..8958f88846 100644 --- a/src/Npgsql.NodaTime/Internal/NodaTimeTypeInfoResolverFactory.Range.cs +++ b/src/Npgsql.NodaTime/Internal/NodaTimeTypeInfoResolverFactory.Range.cs @@ -31,7 +31,7 @@ static TypeInfoMappingCollection AddMappings(TypeInfoMappingCollection mappings) static (options, mapping, _) => mapping.CreateInfo(options, new IntervalConverter( - CreateRangeConverter(new InstantConverter(options.EnableDateTimeInfinityConversions), options))), + CreateRangeConverter(new InstantConverter(options.EnableDateTimeInfinityConversions), options), options.EnableDateTimeInfinityConversions)), isDefault: true); mappings.AddStructType>(TimestampTzRangeDataTypeName, static (options, mapping, _) => mapping.CreateInfo(options, diff --git a/test/Npgsql.PluginTests/NodaTimeInfinityTests.cs b/test/Npgsql.PluginTests/NodaTimeInfinityTests.cs index 75559169f0..e8f8036ada 100644 --- a/test/Npgsql.PluginTests/NodaTimeInfinityTests.cs +++ b/test/Npgsql.PluginTests/NodaTimeInfinityTests.cs @@ -361,6 +361,26 @@ public async Task Interval_convert_infinity() } } + [Test] + public async Task Inclusive_End_Range_Infinity_read() + { + await using var conn = await OpenConnectionAsync(); + await using var cmd = new NpgsqlCommand( + "SELECT tstzrange('-infinity', 'infinity','[]') as val", conn); + + await using var reader = await cmd.ExecuteReaderAsync(); + await reader.ReadAsync(); + + if (Statics.DisableDateTimeInfinityConversions) + { + Assert.That(() => reader[0], Throws.Exception.TypeOf()); + } + else + { + Assert.That(reader[0], Is.EqualTo(new Interval(Instant.MinValue, null))); + } + } + protected override NpgsqlDataSource DataSource { get; } public NodaTimeInfinityTests(bool disableDateTimeInfinityConversions)