diff --git a/src/Npgsql/Internal/Converters/Temporal/DateConverters.cs b/src/Npgsql/Internal/Converters/Temporal/DateConverters.cs index 807e2528d2..41e2cb83da 100644 --- a/src/Npgsql/Internal/Converters/Temporal/DateConverters.cs +++ b/src/Npgsql/Internal/Converters/Temporal/DateConverters.cs @@ -4,9 +4,9 @@ // ReSharper disable once CheckNamespace namespace Npgsql.Internal.Converters; -sealed class DateTimeDateConverter(bool dateTimeInfinityConversions) : PgBufferedConverter +sealed class DateOnlyDateConverter(bool dateTimeInfinityConversions) : PgBufferedConverter { - static readonly DateTime BaseValue = new(2000, 1, 1, 0, 0, 0); + static readonly DateOnly BaseValue = new(2000, 1, 1); public override bool CanConvert(DataFormat format, out BufferRequirements bufferRequirements) { @@ -14,42 +14,42 @@ public override bool CanConvert(DataFormat format, out BufferRequirements buffer return format is DataFormat.Binary; } - protected override DateTime ReadCore(PgReader reader) + protected override DateOnly ReadCore(PgReader reader) => reader.ReadInt32() switch { int.MaxValue => dateTimeInfinityConversions - ? DateTime.MaxValue + ? DateOnly.MaxValue : throw new InvalidCastException(NpgsqlStrings.CannotReadInfinityValue), int.MinValue => dateTimeInfinityConversions - ? DateTime.MinValue + ? DateOnly.MinValue : throw new InvalidCastException(NpgsqlStrings.CannotReadInfinityValue), - var value => BaseValue + TimeSpan.FromDays(value) + var value => BaseValue.AddDays(value) }; - protected override void WriteCore(PgWriter writer, DateTime value) + protected override void WriteCore(PgWriter writer, DateOnly value) { if (dateTimeInfinityConversions) { - if (value == DateTime.MaxValue) + if (value == DateOnly.MaxValue) { writer.WriteInt32(int.MaxValue); return; } - if (value == DateTime.MinValue) + if (value == DateOnly.MinValue) { writer.WriteInt32(int.MinValue); return; } } - writer.WriteInt32((value.Date - BaseValue).Days); + writer.WriteInt32(value.DayNumber - BaseValue.DayNumber); } } -sealed class DateOnlyDateConverter(bool dateTimeInfinityConversions) : PgBufferedConverter +sealed class DateTimeDateConverter(bool dateTimeInfinityConversions) : PgBufferedConverter { - static readonly DateOnly BaseValue = new(2000, 1, 1); + static readonly DateTime BaseValue = new(2000, 1, 1, 0, 0, 0); public override bool CanConvert(DataFormat format, out BufferRequirements bufferRequirements) { @@ -57,35 +57,35 @@ public override bool CanConvert(DataFormat format, out BufferRequirements buffer return format is DataFormat.Binary; } - protected override DateOnly ReadCore(PgReader reader) + protected override DateTime ReadCore(PgReader reader) => reader.ReadInt32() switch { int.MaxValue => dateTimeInfinityConversions - ? DateOnly.MaxValue + ? DateTime.MaxValue : throw new InvalidCastException(NpgsqlStrings.CannotReadInfinityValue), int.MinValue => dateTimeInfinityConversions - ? DateOnly.MinValue + ? DateTime.MinValue : throw new InvalidCastException(NpgsqlStrings.CannotReadInfinityValue), - var value => BaseValue.AddDays(value) + var value => BaseValue + TimeSpan.FromDays(value) }; - protected override void WriteCore(PgWriter writer, DateOnly value) + protected override void WriteCore(PgWriter writer, DateTime value) { if (dateTimeInfinityConversions) { - if (value == DateOnly.MaxValue) + if (value == DateTime.MaxValue) { writer.WriteInt32(int.MaxValue); return; } - if (value == DateOnly.MinValue) + if (value == DateTime.MinValue) { writer.WriteInt32(int.MinValue); return; } } - writer.WriteInt32(value.DayNumber - BaseValue.DayNumber); + writer.WriteInt32((value.Date - BaseValue).Days); } } diff --git a/src/Npgsql/Internal/Converters/Temporal/TimeConverters.cs b/src/Npgsql/Internal/Converters/Temporal/TimeConverters.cs index b93a878032..09385712bf 100644 --- a/src/Npgsql/Internal/Converters/Temporal/TimeConverters.cs +++ b/src/Npgsql/Internal/Converters/Temporal/TimeConverters.cs @@ -3,26 +3,26 @@ // ReSharper disable once CheckNamespace namespace Npgsql.Internal.Converters; -sealed class TimeSpanTimeConverter : PgBufferedConverter +sealed class TimeOnlyTimeConverter : PgBufferedConverter { public override bool CanConvert(DataFormat format, out BufferRequirements bufferRequirements) { bufferRequirements = BufferRequirements.CreateFixedSize(sizeof(long)); return format is DataFormat.Binary; } - protected override TimeSpan ReadCore(PgReader reader) => new(reader.ReadInt64() * 10); - protected override void WriteCore(PgWriter writer, TimeSpan value) => writer.WriteInt64(value.Ticks / 10); + protected override TimeOnly ReadCore(PgReader reader) => new(reader.ReadInt64() * 10); + protected override void WriteCore(PgWriter writer, TimeOnly value) => writer.WriteInt64(value.Ticks / 10); } -sealed class TimeOnlyTimeConverter : PgBufferedConverter +sealed class TimeSpanTimeConverter : PgBufferedConverter { public override bool CanConvert(DataFormat format, out BufferRequirements bufferRequirements) { bufferRequirements = BufferRequirements.CreateFixedSize(sizeof(long)); return format is DataFormat.Binary; } - protected override TimeOnly ReadCore(PgReader reader) => new(reader.ReadInt64() * 10); - protected override void WriteCore(PgWriter writer, TimeOnly value) => writer.WriteInt64(value.Ticks / 10); + protected override TimeSpan ReadCore(PgReader reader) => new(reader.ReadInt64() * 10); + protected override void WriteCore(PgWriter writer, TimeSpan value) => writer.WriteInt64(value.Ticks / 10); } sealed class DateTimeOffsetTimeTzConverter : PgBufferedConverter diff --git a/src/Npgsql/Internal/ResolverFactories/AdoTypeInfoResolverFactory.Multirange.cs b/src/Npgsql/Internal/ResolverFactories/AdoTypeInfoResolverFactory.Multirange.cs index f76ed3a457..3d82ab03f1 100644 --- a/src/Npgsql/Internal/ResolverFactories/AdoTypeInfoResolverFactory.Multirange.cs +++ b/src/Npgsql/Internal/ResolverFactories/AdoTypeInfoResolverFactory.Multirange.cs @@ -159,24 +159,23 @@ static TypeInfoMappingCollection AddMappings(TypeInfoMappingCollection mappings) CreateListMultirangeConverter(CreateRangeConverter(new Int8Converter(), options), options))); // datemultirange - mappings.AddType[]>(DataTypeNames.DateMultirange, - static (options, mapping, _) => - mapping.CreateInfo(options, CreateArrayMultirangeConverter( - CreateRangeConverter(new DateTimeDateConverter(options.EnableDateTimeInfinityConversions), options), options)), - isDefault: true); - mappings.AddType>>(DataTypeNames.DateMultirange, - static (options, mapping, _) => - mapping.CreateInfo(options, CreateListMultirangeConverter( - CreateRangeConverter(new DateTimeDateConverter(options.EnableDateTimeInfinityConversions), options), options))); mappings.AddType[]>(DataTypeNames.DateMultirange, static (options, mapping, _) => mapping.CreateInfo(options, CreateArrayMultirangeConverter( CreateRangeConverter(new DateOnlyDateConverter(options.EnableDateTimeInfinityConversions), options), options)), isDefault: true); + mappings.AddType[]>(DataTypeNames.DateMultirange, + static (options, mapping, _) => + mapping.CreateInfo(options, CreateArrayMultirangeConverter( + CreateRangeConverter(new DateTimeDateConverter(options.EnableDateTimeInfinityConversions), options), options))); mappings.AddType>>(DataTypeNames.DateMultirange, static (options, mapping, _) => mapping.CreateInfo(options, CreateListMultirangeConverter( CreateRangeConverter(new DateOnlyDateConverter(options.EnableDateTimeInfinityConversions), options), options))); + mappings.AddType>>(DataTypeNames.DateMultirange, + static (options, mapping, _) => + mapping.CreateInfo(options, CreateListMultirangeConverter( + CreateRangeConverter(new DateTimeDateConverter(options.EnableDateTimeInfinityConversions), options), options))); return mappings; } diff --git a/src/Npgsql/Internal/ResolverFactories/AdoTypeInfoResolverFactory.Range.cs b/src/Npgsql/Internal/ResolverFactories/AdoTypeInfoResolverFactory.Range.cs index 74a9028423..17ba8c3c33 100644 --- a/src/Npgsql/Internal/ResolverFactories/AdoTypeInfoResolverFactory.Range.cs +++ b/src/Npgsql/Internal/ResolverFactories/AdoTypeInfoResolverFactory.Range.cs @@ -87,15 +87,16 @@ static TypeInfoMappingCollection AddMappings(TypeInfoMappingCollection mappings) static (options, mapping, _) => mapping.CreateInfo(options, CreateRangeConverter(new Int8Converter(), options))); // daterange + mappings.AddStructType>(DataTypeNames.DateRange, + static (options, mapping, _) => + mapping.CreateInfo(options, + CreateRangeConverter(new DateOnlyDateConverter(options.EnableDateTimeInfinityConversions), options)), + isDefault: true); mappings.AddStructType>(DataTypeNames.DateRange, static (options, mapping, _) => mapping.CreateInfo(options, - CreateRangeConverter(new DateTimeDateConverter(options.EnableDateTimeInfinityConversions), options)), - isDefault: true); + CreateRangeConverter(new DateTimeDateConverter(options.EnableDateTimeInfinityConversions), options))); mappings.AddStructType>(DataTypeNames.DateRange, static (options, mapping, _) => mapping.CreateInfo(options, CreateRangeConverter(new Int4Converter(), options))); - mappings.AddStructType>(DataTypeNames.DateRange, - static (options, mapping, _) => - mapping.CreateInfo(options, CreateRangeConverter(new DateOnlyDateConverter(options.EnableDateTimeInfinityConversions), options))); return mappings; } diff --git a/src/Npgsql/Internal/ResolverFactories/AdoTypeInfoResolverFactory.cs b/src/Npgsql/Internal/ResolverFactories/AdoTypeInfoResolverFactory.cs index b2a39db34b..db350e2fc9 100644 --- a/src/Npgsql/Internal/ResolverFactories/AdoTypeInfoResolverFactory.cs +++ b/src/Npgsql/Internal/ResolverFactories/AdoTypeInfoResolverFactory.cs @@ -230,14 +230,15 @@ static TypeInfoMappingCollection AddMappings(TypeInfoMappingCollection mappings) static (options, mapping, _) => mapping.CreateInfo(options, new Int8Converter())); // Date + mappings.AddStructType(DataTypeNames.Date, + static (options, mapping, _) => + mapping.CreateInfo(options, new DateOnlyDateConverter(options.EnableDateTimeInfinityConversions)), isDefault: true); mappings.AddStructType(DataTypeNames.Date, static (options, mapping, _) => mapping.CreateInfo(options, new DateTimeDateConverter(options.EnableDateTimeInfinityConversions)), MatchRequirement.DataTypeName); mappings.AddStructType(DataTypeNames.Date, static (options, mapping, _) => mapping.CreateInfo(options, new Int4Converter())); - mappings.AddStructType(DataTypeNames.Date, - static (options, mapping, _) => mapping.CreateInfo(options, new DateOnlyDateConverter(options.EnableDateTimeInfinityConversions))); // Interval mappings.AddStructType(DataTypeNames.Interval, @@ -246,12 +247,12 @@ static TypeInfoMappingCollection AddMappings(TypeInfoMappingCollection mappings) static (options, mapping, _) => mapping.CreateInfo(options, new NpgsqlIntervalConverter())); // Time + mappings.AddStructType(DataTypeNames.Time, + static (options, mapping, _) => mapping.CreateInfo(options, new TimeOnlyTimeConverter()), isDefault: true); mappings.AddStructType(DataTypeNames.Time, - static (options, mapping, _) => mapping.CreateInfo(options, new TimeSpanTimeConverter()), isDefault: true); + static (options, mapping, _) => mapping.CreateInfo(options, new TimeSpanTimeConverter())); mappings.AddStructType(DataTypeNames.Time, static (options, mapping, _) => mapping.CreateInfo(options, new Int8Converter())); - mappings.AddStructType(DataTypeNames.Time, - static (options, mapping, _) => mapping.CreateInfo(options, new TimeOnlyTimeConverter())); // TimeTz mappings.AddStructType(DataTypeNames.TimeTz, @@ -446,9 +447,9 @@ static TypeInfoMappingCollection AddMappings(TypeInfoMappingCollection mappings) mappings.AddStructArrayType(DataTypeNames.TimestampTz); // Date + mappings.AddStructArrayType(DataTypeNames.Date); mappings.AddStructArrayType(DataTypeNames.Date); mappings.AddStructArrayType(DataTypeNames.Date); - mappings.AddStructArrayType(DataTypeNames.Date); // Interval mappings.AddStructArrayType(DataTypeNames.Interval); diff --git a/test/Npgsql.Tests/CommandBuilderTests.cs b/test/Npgsql.Tests/CommandBuilderTests.cs index e917b7f6b3..a9fe980c5b 100644 --- a/test/Npgsql.Tests/CommandBuilderTests.cs +++ b/test/Npgsql.Tests/CommandBuilderTests.cs @@ -341,7 +341,7 @@ PRIMARY KEY (Cod) Assert.That(row[0], Is.EqualTo("key1")); Assert.That(row[1], Is.EqualTo("description")); - Assert.That(row[2], Is.EqualTo(new DateTime(2018, 7, 3))); + Assert.That(row[2], Is.EqualTo(new DateOnly(2018, 7, 3))); Assert.That(row[3], Is.EqualTo(new DateTime(2018, 7, 3, 7, 2, 0))); Assert.That(row[4], Is.EqualTo(123)); Assert.That(row[5], Is.EqualTo(123.4)); diff --git a/test/Npgsql.Tests/ExceptionTests.cs b/test/Npgsql.Tests/ExceptionTests.cs index ac87ef2b0e..21d83ff9fb 100644 --- a/test/Npgsql.Tests/ExceptionTests.cs +++ b/test/Npgsql.Tests/ExceptionTests.cs @@ -250,6 +250,8 @@ PostgresException CreateWithSqlState(string sqlState) #pragma warning disable SYSLIB0011 #pragma warning disable SYSLIB0050 + +#if !NET9_0_OR_GREATER // BinaryFormatter serialization and deserialization have been removed. See https://aka.ms/binaryformatter for more information. [Test] public void Serialization() { @@ -283,6 +285,7 @@ public void Serialization() Assert.That(expected.Line, Is.EqualTo(actual.Line)); Assert.That(expected.Routine, Is.EqualTo(actual.Routine)); } +#endif SerializationInfo CreateSerializationInfo() => new(typeof(PostgresException), new FormatterConverter()); #pragma warning restore SYSLIB0011 diff --git a/test/Npgsql.Tests/ReaderTests.cs b/test/Npgsql.Tests/ReaderTests.cs index 7ee1aa6e11..b46342153f 100644 --- a/test/Npgsql.Tests/ReaderTests.cs +++ b/test/Npgsql.Tests/ReaderTests.cs @@ -475,7 +475,7 @@ public async Task GetValues() dr.Read(); var values = new object[4]; Assert.That(dr.GetValues(values), Is.EqualTo(3)); - Assert.That(values, Is.EqualTo(new object?[] { "hello", 1, new DateTime(2014, 1, 1), null })); + Assert.That(values, Is.EqualTo(new object?[] { "hello", 1, new DateOnly(2014, 1, 1), null })); } using (var dr = await command.ExecuteReaderAsync(Behavior)) { diff --git a/test/Npgsql.Tests/Types/DateTimeTests.cs b/test/Npgsql.Tests/Types/DateTimeTests.cs index 815514031a..078693bf96 100644 --- a/test/Npgsql.Tests/Types/DateTimeTests.cs +++ b/test/Npgsql.Tests/Types/DateTimeTests.cs @@ -13,9 +13,13 @@ public class DateTimeTests : TestBase { #region Date + [Test] + public Task Date_as_DateOnly() + => AssertType(new DateOnly(2020, 10, 1), "2020-10-01", "date", NpgsqlDbType.Date, DbType.Date); + [Test] public Task Date_as_DateTime() - => AssertType(new DateTime(2020, 10, 1), "2020-10-01", "date", NpgsqlDbType.Date, DbType.Date, isDefaultForWriting: false); + => AssertType(new DateTime(2020, 10, 1), "2020-10-01", "date", NpgsqlDbType.Date, DbType.Date, isDefault: false); [Test] public Task Date_as_DateTime_with_date_and_time_before_2000() @@ -26,37 +30,6 @@ public Task Date_as_DateTime_with_date_and_time_before_2000() public Task Date_as_int() => AssertType(7579, "2020-10-01", "date", NpgsqlDbType.Date, DbType.Date, isDefault: false); - [Test] - public Task Daterange_as_NpgsqlRange_of_DateTime() - => AssertType( - new NpgsqlRange(new(2002, 3, 4), true, new(2002, 3, 6), false), - "[2002-03-04,2002-03-06)", - "daterange", - NpgsqlDbType.DateRange, - isDefaultForWriting: false); - - [Test] - public async Task Datemultirange_as_array_of_NpgsqlRange_of_DateTime() - { - await using var conn = await OpenConnectionAsync(); - MinimumPgVersion(conn, "14.0", "Multirange types were introduced in PostgreSQL 14"); - - await AssertType( - new[] - { - new NpgsqlRange(new(2002, 3, 4), true, new(2002, 3, 6), false), - new NpgsqlRange(new(2002, 3, 8), true, new(2002, 3, 11), false) - }, - "{[2002-03-04,2002-03-06),[2002-03-08,2002-03-11)}", - "datemultirange", - NpgsqlDbType.DateMultirange, - isDefaultForWriting: false); - } - - [Test] - public Task Date_as_DateOnly() - => AssertType(new DateOnly(2020, 10, 1), "2020-10-01", "date", NpgsqlDbType.Date, DbType.Date, isDefaultForReading: false); - [Test] public Task Daterange_as_NpgsqlRange_of_DateOnly() => AssertType( @@ -64,7 +37,6 @@ public Task Daterange_as_NpgsqlRange_of_DateOnly() "[2002-03-04,2002-03-06)", "daterange", NpgsqlDbType.DateRange, - isDefaultForReading: false, skipArrayCheck: true); // NpgsqlRange[] is mapped to multirange by default, not array; test separately [Test] @@ -78,6 +50,15 @@ public Task Daterange_array_as_NpgsqlRange_of_DateOnly_array() """{"[2002-03-04,2002-03-06)","[2002-03-08,2002-03-09)"}""", "daterange[]", NpgsqlDbType.DateRange | NpgsqlDbType.Array, + isDefaultForWriting: false); + + [Test] + public Task Daterange_as_NpgsqlRange_of_DateTime() + => AssertType( + new NpgsqlRange(new(2002, 3, 4), true, new(2002, 3, 6), false), + "[2002-03-04,2002-03-06)", + "daterange", + NpgsqlDbType.DateRange, isDefault: false); [Test] @@ -94,8 +75,25 @@ await AssertType( }, "{[2002-03-04,2002-03-06),[2002-03-08,2002-03-11)}", "datemultirange", + NpgsqlDbType.DateMultirange); + } + + [Test] + public async Task Datemultirange_as_array_of_NpgsqlRange_of_DateTime() + { + await using var conn = await OpenConnectionAsync(); + MinimumPgVersion(conn, "14.0", "Multirange types were introduced in PostgreSQL 14"); + + await AssertType( + new[] + { + new NpgsqlRange(new(2002, 3, 4), true, new(2002, 3, 6), false), + new NpgsqlRange(new(2002, 3, 8), true, new(2002, 3, 11), false) + }, + "{[2002-03-04,2002-03-06),[2002-03-08,2002-03-11)}", + "datemultirange", NpgsqlDbType.DateMultirange, - isDefaultForReading: false); + isDefault: false); } #endregion @@ -103,24 +101,23 @@ await AssertType( #region Time [Test] - public Task Time_as_TimeSpan() + public Task Time_as_TimeOnly() => AssertType( - new TimeSpan(0, 10, 45, 34, 500), + new TimeOnly(10, 45, 34, 500), "10:45:34.5", "time without time zone", NpgsqlDbType.Time, - DbType.Time, - isDefaultForWriting: false); + DbType.Time); [Test] - public Task Time_as_TimeOnly() + public Task Time_as_TimeSpan() => AssertType( - new TimeOnly(10, 45, 34, 500), + new TimeSpan(0, 10, 45, 34, 500), "10:45:34.5", "time without time zone", NpgsqlDbType.Time, DbType.Time, - isDefaultForReading: false); + isDefault: false); #endregion diff --git a/test/Npgsql.Tests/Types/MultirangeTests.cs b/test/Npgsql.Tests/Types/MultirangeTests.cs index cf9cdcd8a9..86bebb1b67 100644 --- a/test/Npgsql.Tests/Types/MultirangeTests.cs +++ b/test/Npgsql.Tests/Types/MultirangeTests.cs @@ -47,12 +47,12 @@ public class MultirangeTests : TestBase // daterange new TestCaseData( - new NpgsqlRange[] + new NpgsqlRange[] { new(new(2020, 1, 1), true, false, new(2020, 1, 5), false, false), new(new(2020, 1, 10), true, false, default, false, true) }, - "{[2020-01-01,2020-01-05),[2020-01-10,)}", "datemultirange", NpgsqlDbType.DateMultirange, true, false, default(NpgsqlRange)) + "{[2020-01-01,2020-01-05),[2020-01-10,)}", "datemultirange", NpgsqlDbType.DateMultirange, true, false, default(NpgsqlRange)) .SetName("DateTime DateMultirange"), // tsmultirange