Skip to content

Commit

Permalink
Code literal support for NodaTime Instant and ZonedDateTime
Browse files Browse the repository at this point in the history
Closes #854
  • Loading branch information
roji committed Mar 7, 2020
1 parent cf792d5 commit 6f34754
Show file tree
Hide file tree
Showing 2 changed files with 50 additions and 8 deletions.
44 changes: 36 additions & 8 deletions src/EFCore.PG.NodaTime/Storage/Internal/NodaTimeMappings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using NpgsqlTypes;
using System.Linq.Expressions;
using System.Reflection;
using NodaTime.TimeZones;
using static Npgsql.EntityFrameworkCore.PostgreSQL.NodaTime.Utilties.Util;

// ReSharper disable once CheckNamespace
Expand All @@ -32,9 +33,14 @@ public override CoreTypeMapping Clone(ValueConverter converter)
protected override string GenerateNonNullSqlLiteral(object value)
=> $"TIMESTAMP '{InstantPattern.ExtendedIso.Format((Instant)value)}'";

// GenerateCodeLiteral isn't implemented because round-tripping Instant would require rendering an expression such as
// NodaConstants.UnixEpoch + Duration.FromNanoseconds(nanoseconds), which isn't currently supported by EF Core's code
// generator
public override Expression GenerateCodeLiteral(object value)
=> GenerateCodeLiteral((Instant)value);

internal static Expression GenerateCodeLiteral(Instant instant)
=> Expression.Call(FromUnixTimeTicks, Expression.Constant(instant.ToUnixTimeTicks()));

static readonly MethodInfo FromUnixTimeTicks
= typeof(Instant).GetRuntimeMethod(nameof(Instant.FromUnixTimeTicks), new[] { typeof(long) });
}

public class TimestampLocalDateTimeMapping : NpgsqlTypeMapping
Expand Down Expand Up @@ -103,9 +109,8 @@ public override CoreTypeMapping Clone(ValueConverter converter)
protected override string GenerateNonNullSqlLiteral(object value)
=> $"TIMESTAMPTZ '{InstantPattern.ExtendedIso.Format((Instant)value)}'";

// GenerateCodeLiteral isn't implemented because round-tripping Instant would require rendering an expression such as
// NodaConstants.UnixEpoch + Duration.FromNanoseconds(nanoseconds), which isn't currently supported by EF Core's code
// generator
public override Expression GenerateCodeLiteral(object value)
=> TimestampInstantMapping.GenerateCodeLiteral((Instant)value);
}

public class TimestampTzOffsetDateTimeMapping : NpgsqlTypeMapping
Expand Down Expand Up @@ -172,8 +177,31 @@ public override CoreTypeMapping Clone(ValueConverter converter)
protected override string GenerateNonNullSqlLiteral(object value)
=> $"TIMESTAMPTZ '{Pattern.Format((ZonedDateTime)value)}'";

// GenerateCodeLiteral isn't implemented because round-tripping DateTimeZone would require a property access into
// DateTimeZoneProviders, which isn't currently supported by EF Core's code generator
public override Expression GenerateCodeLiteral(object value)
{
var zonedDateTime = (ZonedDateTime)value;

return Expression.New(
Constructor,
TimestampInstantMapping.GenerateCodeLiteral(zonedDateTime.ToInstant()),
Expression.Call(
Expression.MakeMemberAccess(
null,
TzdbDateTimeZoneSourceDefaultMember),
ForIdMethod,
Expression.Constant(zonedDateTime.Zone.Id)));
}

static readonly ConstructorInfo Constructor =
typeof(ZonedDateTime).GetConstructor(new[] { typeof(Instant), typeof(DateTimeZone) });

static readonly MemberInfo TzdbDateTimeZoneSourceDefaultMember =
typeof(TzdbDateTimeZoneSource).GetMember(nameof(TzdbDateTimeZoneSource.Default))[0];

static readonly MethodInfo ForIdMethod =
typeof(TzdbDateTimeZoneSource).GetRuntimeMethod(
nameof(TzdbDateTimeZoneSource.ForId),
new[] { typeof(string) });
}

#endregion timestamptz
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,15 @@ public void GenerateSqlLiteral_returns_zoned_date_time_literal()
Assert.Equal("TIMESTAMPTZ '2018-04-20T10:31:33.666666+02'", mapping.GenerateSqlLiteral(zonedDateTime));
}

[Fact]
public void GenerateCodeLiteral_returns_zoned_date_time_literal()
{
var zonedDateTime = (new LocalDateTime(2018, 4, 20, 10, 31, 33, 666) + Period.FromTicks(6660))
.InZone(DateTimeZone.ForOffset(Offset.FromHours(2)), Resolvers.LenientResolver);
Assert.Equal(@"new NodaTime.ZonedDateTime(NodaTime.Instant.FromUnixTimeTicks(15242130936666660L), NodaTime.TimeZones.TzdbDateTimeZoneSource.Default.ForId(""UTC+02""))",
CodeLiteral(zonedDateTime));
}

[Fact]
public void GenerateSqlLiteral_returns_offset_date_time_literal()
{
Expand All @@ -77,6 +86,11 @@ public void GenerateSqlLiteral_returns_offset_date_time_literal()
Assert.Equal("TIMESTAMPTZ '2018-04-20T10:31:33.666666+02'", mapping.GenerateSqlLiteral(offsetDateTime));
}

[Fact]
public void GenerateCodeLiteral_returns_instant_literal()
=> Assert.Equal("NodaTime.Instant.FromUnixTimeTicks(15832607590000000L)",
CodeLiteral(Instant.FromUtc(2020, 3, 3, 18, 39, 19)));

[Fact]
public void GenerateCodeLiteral_returns_offset_date_time_literal()
{
Expand Down

0 comments on commit 6f34754

Please sign in to comment.