From 53aaea88c0dc2022356d4711d9f48efc5b660c3b Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Sun, 25 Oct 2026 11:03:00 +0200 Subject: [PATCH 1/3] Translate NodaTime LocalDate subtraction using age() Return calendar years, months and days instead of an elapsed-day interval. Cast operands to timestamp to avoid time zone dependence and cover calendar components, negative periods and month-end differences. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../NpgsqlSqlTranslatingExpressionVisitor.cs | 26 ++++----- .../NodaTime/LocalDateTranslationsTest.cs | 56 ++++++++++++++++++- 2 files changed, 63 insertions(+), 19 deletions(-) diff --git a/src/EFCore.PG/Query/Internal/NpgsqlSqlTranslatingExpressionVisitor.cs b/src/EFCore.PG/Query/Internal/NpgsqlSqlTranslatingExpressionVisitor.cs index 080eb9b75f..18c6cc2201 100644 --- a/src/EFCore.PG/Query/Internal/NpgsqlSqlTranslatingExpressionVisitor.cs +++ b/src/EFCore.PG/Query/Internal/NpgsqlSqlTranslatingExpressionVisitor.cs @@ -33,8 +33,6 @@ private readonly NpgsqlJsonPocoTranslator _jsonPocoTranslator private readonly RelationalTypeMapping _timestampMapping = dependencies.TypeMappingSource.FindMapping("timestamp without time zone")!; private readonly RelationalTypeMapping _timestampTzMapping = dependencies.TypeMappingSource.FindMapping("timestamp with time zone")!; - private static Type? _nodaTimePeriodType; - private static readonly MethodInfo EscapeLikePatternParameterMethod = typeof(NpgsqlSqlTranslatingExpressionVisitor).GetTypeInfo().GetDeclaredMethod(nameof(ConstructLikePatternParameter))!; @@ -228,9 +226,8 @@ protected override Expression VisitBinary(BinaryExpression binaryExpression) binaryExpression.NodeType != ExpressionType.ArrayIndex, "During preprocessing, ArrayIndex and List[] get normalized to ElementAt; see NpgsqlArrayTranslator. Should never see ArrayIndex."); - // We pattern match date subtraction before calling base.VisitBinary() as that would produce the default a - b translation, but that - // yields the number of days as an integer; this is incompatible with the .NET side, where LocalDate - LocalDate = Period, and Period - // is mapped to PG interval. So we override subtraction to add make_interval. + // PostgreSQL date subtraction returns an integer number of days; age() returns a calendar period instead. + // Its month-end handling differs from NodaTime's. Cast to timestamp to avoid time zone-dependent arithmetic. if (binaryExpression.NodeType is ExpressionType.Subtract && binaryExpression.Left.Type.UnwrapNullableType().FullName == "NodaTime.LocalDate" && binaryExpression.Right.Type.UnwrapNullableType().FullName == "NodaTime.LocalDate") @@ -241,18 +238,15 @@ protected override Expression VisitBinary(BinaryExpression binaryExpression) return QueryCompilationContext.NotTranslatedExpression; } - var subtraction = _sqlExpressionFactory.MakeBinary( - ExpressionType.Subtract, sqlLeft!, sqlRight!, _typeMappingSource.FindMapping(typeof(int)))!; - - return PgFunctionExpression.CreateWithNamedArguments( - "make_interval", - [subtraction], - ["days"], + return _sqlExpressionFactory.Function( + "age", + [ + _sqlExpressionFactory.Convert(sqlLeft!, typeof(DateTime), _timestampMapping), + _sqlExpressionFactory.Convert(sqlRight!, typeof(DateTime), _timestampMapping) + ], nullable: true, - argumentsPropagateNullability: TrueArrays[1], - builtIn: true, - _nodaTimePeriodType ??= binaryExpression.Left.Type.Assembly.GetType("NodaTime.Period")!, - typeMapping: null); + argumentsPropagateNullability: TrueArrays[2], + binaryExpression.Type); // Note: many other date/time arithmetic operators are fully supported as-is by PostgreSQL - see NpgsqlSqlExpressionFactory } diff --git a/test/EFCore.PG.FunctionalTests/Query/Translations/NodaTime/LocalDateTranslationsTest.cs b/test/EFCore.PG.FunctionalTests/Query/Translations/NodaTime/LocalDateTranslationsTest.cs index b0b7055839..77efb87df2 100644 --- a/test/EFCore.PG.FunctionalTests/Query/Translations/NodaTime/LocalDateTranslationsTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/Translations/NodaTime/LocalDateTranslationsTest.cs @@ -41,7 +41,7 @@ await AssertQuery( """ SELECT n."Id", n."DateInterval", n."Duration", n."Instant", n."InstantRange", n."Interval", n."LocalDate", n."LocalDate2", n."LocalDateRange", n."LocalDateTime", n."LocalTime", n."Long", n."OffsetTime", n."Period", n."TimeZoneId", n."ZonedDateTime" FROM "NodaTimeTypes" AS n -WHERE make_interval(days => n."LocalDate2" - n."LocalDate") = INTERVAL 'P1D' +WHERE age(n."LocalDate2"::timestamp, n."LocalDate"::timestamp) = INTERVAL 'P1D' """); } @@ -60,7 +60,7 @@ await AssertQuery( SELECT n."Id", n."DateInterval", n."Duration", n."Instant", n."InstantRange", n."Interval", n."LocalDate", n."LocalDate2", n."LocalDateRange", n."LocalDateTime", n."LocalTime", n."Long", n."OffsetTime", n."Period", n."TimeZoneId", n."ZonedDateTime" FROM "NodaTimeTypes" AS n -WHERE make_interval(days => n."LocalDate2" - @date) = INTERVAL 'P1D' +WHERE age(n."LocalDate2"::timestamp, @date::timestamp) = INTERVAL 'P1D' """); } @@ -92,7 +92,57 @@ await AssertQuery( """ SELECT n."Id", n."DateInterval", n."Duration", n."Instant", n."InstantRange", n."Interval", n."LocalDate", n."LocalDate2", n."LocalDateRange", n."LocalDateTime", n."LocalTime", n."Long", n."OffsetTime", n."Period", n."TimeZoneId", n."ZonedDateTime" FROM "NodaTimeTypes" AS n -WHERE make_interval(days => n."LocalDate2" - DATE '2018-04-20') = INTERVAL 'P1D' +WHERE age(n."LocalDate2"::timestamp, DATE '2018-04-20'::timestamp) = INTERVAL 'P1D' +"""); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public async Task Subtract_LocalDate_years_months_days(bool async) + { + await AssertQuery( + async, + ss => ss.Set() + .Select(t => t.LocalDate - new LocalDate(2017, 3, 19)) + .Where(p => p.Years == 1 && p.Months == 1 && p.Days == 1)); + + AssertSql( + """ +SELECT age(n."LocalDate"::timestamp, DATE '2017-03-19'::timestamp) +FROM "NodaTimeTypes" AS n +WHERE date_part('year', age(n."LocalDate"::timestamp, DATE '2017-03-19'::timestamp))::int = 1 AND date_part('month', age(n."LocalDate"::timestamp, DATE '2017-03-19'::timestamp))::int = 1 AND date_part('day', age(n."LocalDate"::timestamp, DATE '2017-03-19'::timestamp))::int = 1 +"""); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public async Task Subtract_LocalDate_negative(bool async) + { + await AssertQuery( + async, + ss => ss.Set().Select(t => new LocalDate(2017, 3, 19) - t.LocalDate)); + + AssertSql( + """ +SELECT age(DATE '2017-03-19'::timestamp, n."LocalDate"::timestamp) +FROM "NodaTimeTypes" AS n +"""); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public async Task Subtract_LocalDate_month_end(bool async) + { + // PostgreSQL age() returns 30 days for March 31 to April 30, whereas NodaTime returns one month. + await AssertQuery( + async, + ss => ss.Set().Select(t => t.LocalDate + Period.FromDays(10) - new LocalDate(2018, 3, 31)), + expectedQuery: ss => ss.Set().Select(t => Period.FromDays(30))); + + AssertSql( + """ +SELECT age(CAST(n."LocalDate" + INTERVAL 'P10D' AS timestamp without time zone), DATE '2018-03-31'::timestamp) +FROM "NodaTimeTypes" AS n """); } From 237ade61a5ce3bf14dfbc7a6a99596502183f440 Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Sun, 25 Oct 2026 11:09:01 +0200 Subject: [PATCH 2/3] Fix NodaTime BCE year extraction Correct negative PostgreSQL date/time years to NodaTime astronomical numbering without changing Period.Years or LocalDate subtraction. Partially addresses #4089. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../NpgsqlNodaTimeMemberTranslatorPlugin.cs | 11 +++- .../NodaTime/LocalDateTimeTranslationsTest.cs | 40 ++++++++++++++- .../NodaTime/LocalDateTranslationsTest.cs | 40 ++++++++++++++- .../NodaTime/PeriodTranslationsTest.cs | 15 ++++++ .../NodaTime/ZonedDateTimeTranslationsTest.cs | 50 ++++++++++++++++++- 5 files changed, 152 insertions(+), 4 deletions(-) diff --git a/src/EFCore.PG.NodaTime/Query/Internal/NpgsqlNodaTimeMemberTranslatorPlugin.cs b/src/EFCore.PG.NodaTime/Query/Internal/NpgsqlNodaTimeMemberTranslatorPlugin.cs index 833c4ffc1c..bd3116f842 100644 --- a/src/EFCore.PG.NodaTime/Query/Internal/NpgsqlNodaTimeMemberTranslatorPlugin.cs +++ b/src/EFCore.PG.NodaTime/Query/Internal/NpgsqlNodaTimeMemberTranslatorPlugin.cs @@ -225,7 +225,16 @@ SqlExpression Upper() private SqlExpression? TranslateDateTime(SqlExpression instance, MemberInfo member) => member.Name switch { - "Year" or "Years" => GetDatePartExpression(instance, "year"), + // PostgreSQL has no year zero, whereas NodaTime uses astronomical year numbering. + "Year" when GetDatePartExpression(instance, "year") is var year + => _sqlExpressionFactory.Case( + [ + new CaseWhenClause( + _sqlExpressionFactory.LessThan(year, _sqlExpressionFactory.Constant(0)), + _sqlExpressionFactory.Add(year, _sqlExpressionFactory.Constant(1))) + ], + year), + "Years" => GetDatePartExpression(instance, "year"), "Month" or "Months" => GetDatePartExpression(instance, "month"), "DayOfYear" => GetDatePartExpression(instance, "doy"), "Day" or "Days" => GetDatePartExpression(instance, "day"), diff --git a/test/EFCore.PG.FunctionalTests/Query/Translations/NodaTime/LocalDateTimeTranslationsTest.cs b/test/EFCore.PG.FunctionalTests/Query/Translations/NodaTime/LocalDateTimeTranslationsTest.cs index f9e75948af..99434990cd 100644 --- a/test/EFCore.PG.FunctionalTests/Query/Translations/NodaTime/LocalDateTimeTranslationsTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/Translations/NodaTime/LocalDateTimeTranslationsTest.cs @@ -40,10 +40,48 @@ await AssertQuery( """ SELECT n."Id", n."DateInterval", n."Duration", n."Instant", n."InstantRange", n."Interval", n."LocalDate", n."LocalDate2", n."LocalDateRange", n."LocalDateTime", n."LocalTime", n."Long", n."OffsetTime", n."Period", n."TimeZoneId", n."ZonedDateTime" FROM "NodaTimeTypes" AS n -WHERE date_part('year', n."LocalDateTime")::int = 2018 +WHERE CASE + WHEN date_part('year', n."LocalDateTime")::int < 0 THEN date_part('year', n."LocalDateTime")::int + 1 + ELSE date_part('year', n."LocalDateTime")::int +END = 2018 """); } + [ConditionalTheory] + [InlineData(false, -100)] + [InlineData(true, -100)] + [InlineData(false, 0)] + [InlineData(true, 0)] + [InlineData(false, 1)] + [InlineData(true, 1)] + public async Task Year_astronomical(bool async, int year) + { + await using var context = CreateContext(); + await context.Database.CreateExecutionStrategy().ExecuteAsync( + async () => + { + await using var transaction = await context.Database.BeginTransactionAsync(); + + var entity = await context.NodaTimeTypes.SingleAsync(); + entity.LocalDateTime = new LocalDateTime(year, 12, 31, 12, 0); + await context.SaveChangesAsync(); + Fixture.TestSqlLoggerFactory.Clear(); + + var query = context.NodaTimeTypes.Select(t => t.LocalDateTime.Year); + Assert.Equal(year, async ? await query.SingleAsync() : query.Single()); + + AssertSql( + """ +SELECT CASE + WHEN date_part('year', n."LocalDateTime")::int < 0 THEN date_part('year', n."LocalDateTime")::int + 1 + ELSE date_part('year', n."LocalDateTime")::int +END +FROM "NodaTimeTypes" AS n +LIMIT 2 +"""); + }); + } + [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public async Task Month(bool async) diff --git a/test/EFCore.PG.FunctionalTests/Query/Translations/NodaTime/LocalDateTranslationsTest.cs b/test/EFCore.PG.FunctionalTests/Query/Translations/NodaTime/LocalDateTranslationsTest.cs index 77efb87df2..3207c534b4 100644 --- a/test/EFCore.PG.FunctionalTests/Query/Translations/NodaTime/LocalDateTranslationsTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/Translations/NodaTime/LocalDateTranslationsTest.cs @@ -158,10 +158,48 @@ await AssertQuery( """ SELECT n."Id", n."DateInterval", n."Duration", n."Instant", n."InstantRange", n."Interval", n."LocalDate", n."LocalDate2", n."LocalDateRange", n."LocalDateTime", n."LocalTime", n."Long", n."OffsetTime", n."Period", n."TimeZoneId", n."ZonedDateTime" FROM "NodaTimeTypes" AS n -WHERE date_part('year', n."LocalDate")::int = 2018 +WHERE CASE + WHEN date_part('year', n."LocalDate")::int < 0 THEN date_part('year', n."LocalDate")::int + 1 + ELSE date_part('year', n."LocalDate")::int +END = 2018 """); } + [ConditionalTheory] + [InlineData(false, -100)] + [InlineData(true, -100)] + [InlineData(false, 0)] + [InlineData(true, 0)] + [InlineData(false, 1)] + [InlineData(true, 1)] + public async Task Year_astronomical(bool async, int year) + { + await using var context = CreateContext(); + await context.Database.CreateExecutionStrategy().ExecuteAsync( + async () => + { + await using var transaction = await context.Database.BeginTransactionAsync(); + + var entity = await context.NodaTimeTypes.SingleAsync(); + entity.LocalDate = new LocalDate(year, 12, 31); + await context.SaveChangesAsync(); + Fixture.TestSqlLoggerFactory.Clear(); + + var query = context.NodaTimeTypes.Select(t => t.LocalDate.Year); + Assert.Equal(year, async ? await query.SingleAsync() : query.Single()); + + AssertSql( + """ +SELECT CASE + WHEN date_part('year', n."LocalDate")::int < 0 THEN date_part('year', n."LocalDate")::int + 1 + ELSE date_part('year', n."LocalDate")::int +END +FROM "NodaTimeTypes" AS n +LIMIT 2 +"""); + }); + } + [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public async Task Month(bool async) diff --git a/test/EFCore.PG.FunctionalTests/Query/Translations/NodaTime/PeriodTranslationsTest.cs b/test/EFCore.PG.FunctionalTests/Query/Translations/NodaTime/PeriodTranslationsTest.cs index 01a9abc945..2407ed3f4d 100644 --- a/test/EFCore.PG.FunctionalTests/Query/Translations/NodaTime/PeriodTranslationsTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/Translations/NodaTime/PeriodTranslationsTest.cs @@ -28,6 +28,21 @@ WHERE date_part('year', n."Period")::int = 2018 """); } + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public async Task Years_negative(bool async) + { + await AssertQuery( + async, + ss => ss.Set().Select(t => Period.FromYears(-t.Id).Years)); + + AssertSql( + """ +SELECT date_part('year', make_interval(years => -n."Id"))::int +FROM "NodaTimeTypes" AS n +"""); + } + [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public async Task Months(bool async) diff --git a/test/EFCore.PG.FunctionalTests/Query/Translations/NodaTime/ZonedDateTimeTranslationsTest.cs b/test/EFCore.PG.FunctionalTests/Query/Translations/NodaTime/ZonedDateTimeTranslationsTest.cs index e222aa5232..f45930d20c 100644 --- a/test/EFCore.PG.FunctionalTests/Query/Translations/NodaTime/ZonedDateTimeTranslationsTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/Translations/NodaTime/ZonedDateTimeTranslationsTest.cs @@ -40,10 +40,58 @@ await AssertQuery( """ SELECT n."Id", n."DateInterval", n."Duration", n."Instant", n."InstantRange", n."Interval", n."LocalDate", n."LocalDate2", n."LocalDateRange", n."LocalDateTime", n."LocalTime", n."Long", n."OffsetTime", n."Period", n."TimeZoneId", n."ZonedDateTime" FROM "NodaTimeTypes" AS n -WHERE date_part('year', n."ZonedDateTime" AT TIME ZONE 'UTC')::int = 2018 +WHERE CASE + WHEN date_part('year', n."ZonedDateTime" AT TIME ZONE 'UTC')::int < 0 THEN date_part('year', n."ZonedDateTime" AT TIME ZONE 'UTC')::int + 1 + ELSE date_part('year', n."ZonedDateTime" AT TIME ZONE 'UTC')::int +END = 2018 """); } + [ConditionalTheory] + [InlineData(false, -100)] + [InlineData(true, -100)] + [InlineData(false, 0)] + [InlineData(true, 0)] + [InlineData(false, 1)] + [InlineData(true, 1)] + public async Task Year_astronomical(bool async, int year) + { + await using var context = CreateContext(); + await context.Database.CreateExecutionStrategy().ExecuteAsync( + async () => + { + await using var transaction = await context.Database.BeginTransactionAsync(); + + var entity = await context.NodaTimeTypes.SingleAsync(); + entity.ZonedDateTime = new LocalDateTime(year, 12, 31, 12, 0).InUtc(); + await context.SaveChangesAsync(); + Fixture.TestSqlLoggerFactory.Clear(); + + var query = context.NodaTimeTypes.Select( + t => new + { + t.ZonedDateTime.Year, + YearInZone = t.ZonedDateTime.ToInstant().InZone(DateTimeZoneProviders.Tzdb["Europe/Berlin"]).Year + }); + var result = async ? await query.SingleAsync() : query.Single(); + Assert.Equal(year, result.Year); + Assert.Equal(entity.ZonedDateTime.WithZone(DateTimeZoneProviders.Tzdb["Europe/Berlin"]).Year, result.YearInZone); + + AssertSql( + """ +SELECT CASE + WHEN date_part('year', n."ZonedDateTime" AT TIME ZONE 'UTC')::int < 0 THEN date_part('year', n."ZonedDateTime" AT TIME ZONE 'UTC')::int + 1 + ELSE date_part('year', n."ZonedDateTime" AT TIME ZONE 'UTC')::int +END AS "Year", CASE + WHEN date_part('year', n."ZonedDateTime" AT TIME ZONE 'Europe/Berlin')::int < 0 THEN date_part('year', n."ZonedDateTime" AT TIME ZONE 'Europe/Berlin')::int + 1 + ELSE date_part('year', n."ZonedDateTime" AT TIME ZONE 'Europe/Berlin')::int +END AS "YearInZone" +FROM "NodaTimeTypes" AS n +LIMIT 2 +"""); + }); + } + [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public async Task Month(bool async) From 747edba22093b8b204b13924089c22f9f4c8fa56 Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Sun, 25 Oct 2026 13:59:26 +0200 Subject: [PATCH 3/3] Avoid repeated date evaluation in NodaTime year extraction Use trunc(year - 0.5) + 1 to adjust BCE years with a single date_part call, and update SQL baselines. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../NpgsqlNodaTimeMemberTranslatorPlugin.cs | 21 +++++++++++-------- .../NodaTime/LocalDateTimeTranslationsTest.cs | 10 ++------- .../NodaTime/LocalDateTranslationsTest.cs | 10 ++------- .../NodaTime/ZonedDateTimeTranslationsTest.cs | 13 ++---------- 4 files changed, 18 insertions(+), 36 deletions(-) diff --git a/src/EFCore.PG.NodaTime/Query/Internal/NpgsqlNodaTimeMemberTranslatorPlugin.cs b/src/EFCore.PG.NodaTime/Query/Internal/NpgsqlNodaTimeMemberTranslatorPlugin.cs index bd3116f842..f1c15a1b6f 100644 --- a/src/EFCore.PG.NodaTime/Query/Internal/NpgsqlNodaTimeMemberTranslatorPlugin.cs +++ b/src/EFCore.PG.NodaTime/Query/Internal/NpgsqlNodaTimeMemberTranslatorPlugin.cs @@ -225,15 +225,18 @@ SqlExpression Upper() private SqlExpression? TranslateDateTime(SqlExpression instance, MemberInfo member) => member.Name switch { - // PostgreSQL has no year zero, whereas NodaTime uses astronomical year numbering. - "Year" when GetDatePartExpression(instance, "year") is var year - => _sqlExpressionFactory.Case( - [ - new CaseWhenClause( - _sqlExpressionFactory.LessThan(year, _sqlExpressionFactory.Constant(0)), - _sqlExpressionFactory.Add(year, _sqlExpressionFactory.Constant(1))) - ], - year), + // PostgreSQL has no year zero. trunc(year - 0.5) + 1 preserves positive years and increments negative years, + // matching NodaTime's astronomical numbering without evaluating the date expression more than once. + "Year" => _sqlExpressionFactory.Add( + _sqlExpressionFactory.Convert( + _sqlExpressionFactory.Function( + "trunc", + [_sqlExpressionFactory.Subtract(GetDatePartExpressionDouble(instance, "year"), _sqlExpressionFactory.Constant(0.5))], + nullable: true, + argumentsPropagateNullability: TrueArrays[1], + typeof(double)), + typeof(int)), + _sqlExpressionFactory.Constant(1)), "Years" => GetDatePartExpression(instance, "year"), "Month" or "Months" => GetDatePartExpression(instance, "month"), "DayOfYear" => GetDatePartExpression(instance, "doy"), diff --git a/test/EFCore.PG.FunctionalTests/Query/Translations/NodaTime/LocalDateTimeTranslationsTest.cs b/test/EFCore.PG.FunctionalTests/Query/Translations/NodaTime/LocalDateTimeTranslationsTest.cs index 99434990cd..476a984fda 100644 --- a/test/EFCore.PG.FunctionalTests/Query/Translations/NodaTime/LocalDateTimeTranslationsTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/Translations/NodaTime/LocalDateTimeTranslationsTest.cs @@ -40,10 +40,7 @@ await AssertQuery( """ SELECT n."Id", n."DateInterval", n."Duration", n."Instant", n."InstantRange", n."Interval", n."LocalDate", n."LocalDate2", n."LocalDateRange", n."LocalDateTime", n."LocalTime", n."Long", n."OffsetTime", n."Period", n."TimeZoneId", n."ZonedDateTime" FROM "NodaTimeTypes" AS n -WHERE CASE - WHEN date_part('year', n."LocalDateTime")::int < 0 THEN date_part('year', n."LocalDateTime")::int + 1 - ELSE date_part('year', n."LocalDateTime")::int -END = 2018 +WHERE trunc(date_part('year', n."LocalDateTime") - 0.5)::int + 1 = 2018 """); } @@ -72,10 +69,7 @@ await context.Database.CreateExecutionStrategy().ExecuteAsync( AssertSql( """ -SELECT CASE - WHEN date_part('year', n."LocalDateTime")::int < 0 THEN date_part('year', n."LocalDateTime")::int + 1 - ELSE date_part('year', n."LocalDateTime")::int -END +SELECT trunc(date_part('year', n."LocalDateTime") - 0.5)::int + 1 FROM "NodaTimeTypes" AS n LIMIT 2 """); diff --git a/test/EFCore.PG.FunctionalTests/Query/Translations/NodaTime/LocalDateTranslationsTest.cs b/test/EFCore.PG.FunctionalTests/Query/Translations/NodaTime/LocalDateTranslationsTest.cs index 3207c534b4..1e855cbac6 100644 --- a/test/EFCore.PG.FunctionalTests/Query/Translations/NodaTime/LocalDateTranslationsTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/Translations/NodaTime/LocalDateTranslationsTest.cs @@ -158,10 +158,7 @@ await AssertQuery( """ SELECT n."Id", n."DateInterval", n."Duration", n."Instant", n."InstantRange", n."Interval", n."LocalDate", n."LocalDate2", n."LocalDateRange", n."LocalDateTime", n."LocalTime", n."Long", n."OffsetTime", n."Period", n."TimeZoneId", n."ZonedDateTime" FROM "NodaTimeTypes" AS n -WHERE CASE - WHEN date_part('year', n."LocalDate")::int < 0 THEN date_part('year', n."LocalDate")::int + 1 - ELSE date_part('year', n."LocalDate")::int -END = 2018 +WHERE trunc(date_part('year', n."LocalDate") - 0.5)::int + 1 = 2018 """); } @@ -190,10 +187,7 @@ await context.Database.CreateExecutionStrategy().ExecuteAsync( AssertSql( """ -SELECT CASE - WHEN date_part('year', n."LocalDate")::int < 0 THEN date_part('year', n."LocalDate")::int + 1 - ELSE date_part('year', n."LocalDate")::int -END +SELECT trunc(date_part('year', n."LocalDate") - 0.5)::int + 1 FROM "NodaTimeTypes" AS n LIMIT 2 """); diff --git a/test/EFCore.PG.FunctionalTests/Query/Translations/NodaTime/ZonedDateTimeTranslationsTest.cs b/test/EFCore.PG.FunctionalTests/Query/Translations/NodaTime/ZonedDateTimeTranslationsTest.cs index f45930d20c..5d96330f25 100644 --- a/test/EFCore.PG.FunctionalTests/Query/Translations/NodaTime/ZonedDateTimeTranslationsTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/Translations/NodaTime/ZonedDateTimeTranslationsTest.cs @@ -40,10 +40,7 @@ await AssertQuery( """ SELECT n."Id", n."DateInterval", n."Duration", n."Instant", n."InstantRange", n."Interval", n."LocalDate", n."LocalDate2", n."LocalDateRange", n."LocalDateTime", n."LocalTime", n."Long", n."OffsetTime", n."Period", n."TimeZoneId", n."ZonedDateTime" FROM "NodaTimeTypes" AS n -WHERE CASE - WHEN date_part('year', n."ZonedDateTime" AT TIME ZONE 'UTC')::int < 0 THEN date_part('year', n."ZonedDateTime" AT TIME ZONE 'UTC')::int + 1 - ELSE date_part('year', n."ZonedDateTime" AT TIME ZONE 'UTC')::int -END = 2018 +WHERE trunc(date_part('year', n."ZonedDateTime" AT TIME ZONE 'UTC') - 0.5)::int + 1 = 2018 """); } @@ -79,13 +76,7 @@ await context.Database.CreateExecutionStrategy().ExecuteAsync( AssertSql( """ -SELECT CASE - WHEN date_part('year', n."ZonedDateTime" AT TIME ZONE 'UTC')::int < 0 THEN date_part('year', n."ZonedDateTime" AT TIME ZONE 'UTC')::int + 1 - ELSE date_part('year', n."ZonedDateTime" AT TIME ZONE 'UTC')::int -END AS "Year", CASE - WHEN date_part('year', n."ZonedDateTime" AT TIME ZONE 'Europe/Berlin')::int < 0 THEN date_part('year', n."ZonedDateTime" AT TIME ZONE 'Europe/Berlin')::int + 1 - ELSE date_part('year', n."ZonedDateTime" AT TIME ZONE 'Europe/Berlin')::int -END AS "YearInZone" +SELECT trunc(date_part('year', n."ZonedDateTime" AT TIME ZONE 'UTC') - 0.5)::int + 1 AS "Year", trunc(date_part('year', n."ZonedDateTime" AT TIME ZONE 'Europe/Berlin') - 0.5)::int + 1 AS "YearInZone" FROM "NodaTimeTypes" AS n LIMIT 2 """);