Skip to content

Entity Framework DateTime Time Zone Issues and Solutions

TLDR

  • If the Kind property of a DateTime is Unspecified, unexpected offsets occur during time zone conversions.
  • Database columns (such as datetime2) do not store time zone information, causing Entity Framework to default the Kind to Unspecified when retrieving data, which leads to incorrect time display on the frontend.
  • The solution is to use a ValueConverter to ensure the time is UTC before writing to the database and to force the Kind to Utc upon retrieval.
  • It is recommended to use DbContext.ConfigureConventions() for unified configuration to avoid manually defining converters for each property.

DateTime Time Zone Format Issues

In .NET, the Kind property of a DateTime determines how time is handled. When Kind is Unspecified, calling ToLocalTime() or ToUniversalTime() causes the system to perform incorrect time zone offset calculations based on the current environment.

When this problem occurs: When developers use DateTime objects directly without explicitly specifying or converting their Kind property, causing the system to misjudge the time zone during time operations.

The impact of Kind on conversion behavior:

  • Local: Calling ToLocalTime() does not change the time.
  • Utc: Calling ToUniversalTime() does not change the time.
  • Unspecified: Calling ToLocalTime() assumes the original time is UTC and adds the time zone offset; calling ToUniversalTime() assumes the original time is local time and subtracts the offset.

TIP

To avoid such issues, you can refer to the IClock implementation in the ABP.IO framework, which performs normalization by comparing the Kind.

Entity Framework DateTime Time Zone Issues

When using database types that do not contain time zone information (such as datetime or datetime2), the Kind of data retrieved by Entity Framework is always Unspecified. This causes the time string to lack the Z suffix representing UTC when serialized to the frontend, resulting in an 8-hour discrepancy between the displayed time and the expected time.

When this problem occurs: When a project uses Code First or reverse engineering, and the database columns do not store time zone information, causing the Kind property of the object to fail to correctly reflect the UTC status after EF Core reads the data.

Solution: Using ValueConverter

Through ValueConverter, you can force a conversion to UTC when writing data and force the Kind to be marked as Utc when reading.

1. Define the Converter

csharp
public class UtcDateTimeValueConverter : ValueConverter<DateTime, DateTime> {
    public UtcDateTimeValueConverter()
        : base(v => ToDb(v), v => FromDb(v)) {
    }

    private static DateTime ToDb(DateTime dateTime) {
        return dateTime.Kind == DateTimeKind.Utc ? dateTime : dateTime.ToUniversalTime();
    }

    private static DateTime FromDb(DateTime dateTime) {
        return DateTime.SpecifyKind(dateTime, DateTimeKind.Utc);
    }
}

In .NET 6 and later versions, it is recommended to use ConfigureConventions to handle all DateTime columns uniformly, without needing to set them one by one:

csharp
public partial class MyDbContext : DbContext {
    protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder) {
        ArgumentNullException.ThrowIfNull(configurationBuilder);

        configurationBuilder.Properties<DateTime>().HaveConversion<UtcDateTimeValueConverter>();
    }
}

If using a DbContext generated by reverse engineering, you can use the OnModelCreatingPartial method to extend it:

csharp
partial void OnModelCreatingPartial(ModelBuilder modelBuilder) {
    foreach (IMutableEntityType entityType in modelBuilder.Model.GetEntityTypes()) {
        foreach (IMutableProperty property in entityType.GetProperties()) {
            if (property.ClrType == typeof(DateTime) || property.ClrType == typeof(DateTime?)) {
                property.SetValueConverter(typeof(UtcDateTimeValueConverter));
            }
        }
    }
}

TIP

A complete executable example for this article: CloudyWing/EfCoreBehaviorSample.

Change Log

  • 2024-08-15 Initial documentation created.
  • 2026-05-29 Added link to the corresponding GitHub example project.