Entity Framework DateTime and UTC
Asked Answered
F

19

144

Is it possible to have Entity Framework (I am using the Code First Approach with CTP5 currently) store all DateTime values as UTC in the database?

Or is there maybe a way to specify it in the mapping, for example in this one for the last_login column:

modelBuilder.Entity<User>().Property(x => x.Id).HasColumnName("id");
modelBuilder.Entity<User>().Property(x => x.IsAdmin).HasColumnName("admin");
modelBuilder.Entity<User>().Property(x => x.IsEnabled).HasColumnName("enabled");
modelBuilder.Entity<User>().Property(x => x.PasswordHash).HasColumnName("password_hash");
modelBuilder.Entity<User>().Property(x => x.LastLogin).HasColumnName("last_login");
Freakish answered 10/1, 2011 at 15:44 Comment(2)
efcore 6 added a better way - see https://mcmap.net/q/131209/-entity-framework-datetime-and-utcThies
Just use the data type DateTimeOffset when defining your model. Unlike DateTime data type, DateTimeOddset stores also the time zone section. So your datebase will store date, time and the timezone. Upvote below Moutono's answer. That is the simplest way!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!Byblow
G
159

Here is one approach you might consider:

First, define this following attribute:

[AttributeUsage(AttributeTargets.Property)]
public class DateTimeKindAttribute : Attribute
{
    private readonly DateTimeKind _kind;

    public DateTimeKindAttribute(DateTimeKind kind)
    {
        _kind = kind;
    }

    public DateTimeKind Kind
    {
        get { return _kind; }
    }

    public static void Apply(object entity)
    {
        if (entity == null)
            return;

        var properties = entity.GetType().GetProperties()
            .Where(x => x.PropertyType == typeof(DateTime) || x.PropertyType == typeof(DateTime?));

        foreach (var property in properties)
        {
            var attr = property.GetCustomAttribute<DateTimeKindAttribute>();
            if (attr == null)
                continue;

            var dt = property.PropertyType == typeof(DateTime?)
                ? (DateTime?) property.GetValue(entity)
                : (DateTime) property.GetValue(entity);

            if (dt == null)
                continue;

            property.SetValue(entity, DateTime.SpecifyKind(dt.Value, attr.Kind));
        }
    }
}

Now hook that attribute up to your EF context:

public class MyContext : DbContext
{
    public DbSet<Foo> Foos { get; set; }

    public MyContext()
    {
        ((IObjectContextAdapter)this).ObjectContext.ObjectMaterialized +=
            (sender, e) => DateTimeKindAttribute.Apply(e.Entity);
    }
}

Now on any DateTime or DateTime? properties, you can apply this attribute:

public class Foo
{
    public int Id { get; set; }

    [DateTimeKind(DateTimeKind.Utc)]
    public DateTime Bar { get; set; }
}

With this in place, whenever Entity Framework loads an entity from the database, it will set the DateTimeKind that you specify, such as UTC.

Note that this doesn't do anything when saving. You'll still have to have the value properly converted to UTC before you try to save it. But it does allow you to set the kind when retrieving, which allows it to be serialized as UTC, or converted to other time zones with TimeZoneInfo.

Globate answered 10/10, 2013 at 16:47 Comment(23)
If you can't get this working, you're probably missing one of these usings: using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations.Schema; using System.Linq; using System.Reflection;Schroer
@Schroer - You'll find most examples on S.O. will omit usings for brevity, unless they are directly relevant to the question. But thanks.Globate
Thanks for this approach. Since I want to assume that all dates going into our db are UTC, I actually simplified this by removing the custom attribute completely and hardcoding UTC in the SetValue call above. Btw, code above is for .NET 4.5, so I had to do some PropertyInfo extensions to make the new calls work.Carner
Is there a way to do this with fluent api?Moulder
Be aware that accessing IObjectContextAdapter.ObjectContext triggers extremely expensive code. I was profiling an application using this solution, and 400 calls to create a DbContext took well over 6 seconds of active thread time: not exactly light-weight.Disseminate
@MattJohnson without @Saustrup's using statements, you get some unhelpful compile errors such as 'System.Array' does not contain a definition for 'Where'Polyzoic
As @Carner said, this only works with .NET 4.5. I've created some extensions to make it compatible with .NET 4.0 at gist.github.com/munr/3544bd7fab6615290561. Another thing to note is that this won't work with projections, only fully loaded entities.Haematoma
Didn't work for me sadly. Though When I use record.CreatedTS = DateTime.SpecifyKind(record.CreatedTS.Value, DateTimeKind.Utc); it didn't show the local timezone, moreover it didn't show any timezone. Is it supposed to work that way?Culbertson
@Culbertson - DateTime doesn't contain a time zone. You are probably wanting DateTimeOffset. This code is specifically about how to set DateTimeKind.Utc when retrieving a value from the database. It doesn't do anything on the saving side.Globate
Any suggestions on getting this going with projections?Trinh
That looks extra work to set those DateTimeKind attributes. Why not just make all DateTime and DateTime? kinds as at https://mcmap.net/q/131446/-is-it-possible-to-prevent-entityframework-4-from-overwriting-customized-properties ?Mockup
@Artyom, because there are plenty of legitimate uses for not storing as UTC. For example, birthdates.Globate
@MattJohnson Sounds strange. How would you know time zone then? Birthdate can be better stored with DateTimeOffset. DateTime from DB returns with unspecified kind. So I don't see reason not to make all of them in UTC. What other uses?Mockup
GetValue and SetValue by reflection are very slow compared to native access. lotsacode.wordpress.com/2010/04/12/…Chemosphere
@RobKent - sure, but in this case, native access isn't available.Globate
@MattJohnson - I know. Just hit the same problem. It is sooo slow to set the Kind property with reflection. Can't afford that hit to be honest. Might be easier to just to do it manually on the getters.Chemosphere
Alternate and more thorough implementation - #40206393Babirusa
Anyone that can convert this code into something that will work in Entity Framework Core?Honorine
This is great work! I'm thinking on expanding it with something like this: msdn.microsoft.com/en-us/library/… to validate that the given DateTime is of the right kind and throw an exception if it isn'tRachelrachele
How does this work with EFdatabase first? I tried the same code with db first approach and it didnt work.Handsaw
"Might be easier to just to do it manually on the getters." -Rob Kent. A+. That's probably the best-performing solution. That makes it very clear in a single location how each property is meant to be interpreted (as UTC, local, or unspecified), and incurs almost no overhead when returning the DateTime struct, and no overhead when materializing the objects.Martamartaban
This works for some of my entities, but most of them never trigger the ObjectMaterialized eventGudrunguelderrose
This works well for the main object being queried, but if you have included related objects in your query they don't get included because the ObjectMaterialized event is only triggered for the top object, not the children as far as I can tell.Ringdove
K
88

For EF Core, there is a great discussion on this topic on GitHub: https://github.com/dotnet/efcore/issues/4711

A solution (credit to Christopher Haws) that will result in treating all dates when storing them to/retrieving them from database as UTC is to add the following to the OnModelCreating method of your DbContext class:

var dateTimeConverter = new ValueConverter<DateTime, DateTime>(
    v => v.ToUniversalTime(),
    v => DateTime.SpecifyKind(v, DateTimeKind.Utc));

var nullableDateTimeConverter = new ValueConverter<DateTime?, DateTime?>(
    v => v.HasValue ? v.Value.ToUniversalTime() : v,
    v => v.HasValue ? DateTime.SpecifyKind(v.Value, DateTimeKind.Utc) : v);

foreach (var entityType in builder.Model.GetEntityTypes())
{
    if (entityType.IsKeyless)
    {
        continue;
    }

    foreach (var property in entityType.GetProperties())
    {
        if (property.ClrType == typeof(DateTime))
        {
            property.SetValueConverter(dateTimeConverter);
        }
        else if (property.ClrType == typeof(DateTime?))
        {
            property.SetValueConverter(nullableDateTimeConverter);
        }
    }
}

Also, check this link if you want to exclude some properties of some entities from being treated as UTC.

Kana answered 16/4, 2020 at 5:29 Comment(5)
Definitely the best solution for me! ThanksAmsterdam
@MarkRedman I don't think it makes sense, because if you have a legitimate use case for DateTimeOffset, you want to keep the information about the time zone as well. See learn.microsoft.com/en-us/dotnet/standard/datetime/… or https://mcmap.net/q/25756/-datetime-vs-datetimeoffset for when to choose between DateTime and DateTimeOffset.Kana
IsQueryType seems to have been replaced by IsKeyLess: github.com/dotnet/efcore/commit/…Geophagy
Why is IsQueryType (or IsKeyLess now) check needed?Kristie
It might not be needed, actually. Depending on a particular configuration of EF core, one might or might not run into issues if they decide not to omit the keyless entity types.Kana
I
44

I really like Matt Johnson's approach, but in my model ALL of my DateTime members are UTC and I don't want to have to decorate all of them with an attribute. So I generalized Matt's approach to allow the event handler to apply a default Kind value unless a member is explicitly decorated with the attribute.

The constructor for the ApplicationDbContext class includes this code:

/// <summary> Constructor: Initializes a new ApplicationDbContext instance. </summary>
public ApplicationDbContext()
        : base(MyApp.ConnectionString, throwIfV1Schema: false)
{
    // Set the Kind property on DateTime variables retrieved from the database
    ((IObjectContextAdapter)this).ObjectContext.ObjectMaterialized +=
      (sender, e) => DateTimeKindAttribute.Apply(e.Entity, DateTimeKind.Utc);
}

DateTimeKindAttribute looks like this:

/// <summary> Sets the DateTime.Kind value on DateTime and DateTime? members retrieved by Entity Framework. Sets Kind to DateTimeKind.Utc by default. </summary>
[AttributeUsage(AttributeTargets.Property)]
public class DateTimeKindAttribute : Attribute
{
    /// <summary> The DateTime.Kind value to set into the returned value. </summary>
    public readonly DateTimeKind Kind;

    /// <summary> Specifies the DateTime.Kind value to set on the returned DateTime value. </summary>
    /// <param name="kind"> The DateTime.Kind value to set on the returned DateTime value. </param>
    public DateTimeKindAttribute(DateTimeKind kind)
    {
        Kind = kind;
    }

    /// <summary> Event handler to connect to the ObjectContext.ObjectMaterialized event. </summary>
    /// <param name="entity"> The entity (POCO class) being materialized. </param>
    /// <param name="defaultKind"> [Optional] The Kind property to set on all DateTime objects by default. </param>
    public static void Apply(object entity, DateTimeKind? defaultKind = null)
    {
        if (entity == null) return;

        // Get the PropertyInfos for all of the DateTime and DateTime? properties on the entity
        var properties = entity.GetType().GetProperties()
            .Where(x => x.PropertyType == typeof(DateTime) || x.PropertyType == typeof(DateTime?));

        // For each DateTime or DateTime? property on the entity...
        foreach (var propInfo in properties) {
            // Initialization
            var kind = defaultKind;

            // Get the kind value from the [DateTimekind] attribute if it's present
            var kindAttr = propInfo.GetCustomAttribute<DateTimeKindAttribute>();
            if (kindAttr != null) kind = kindAttr.Kind;

            // Set the Kind property
            if (kind != null) {
                var dt = (propInfo.PropertyType == typeof(DateTime?))
                    ? (DateTime?)propInfo.GetValue(entity)
                    : (DateTime)propInfo.GetValue(entity);

                if (dt != null) propInfo.SetValue(entity, DateTime.SpecifyKind(dt.Value, kind.Value));
            }
        }
    }
}
Immoralist answered 23/6, 2016 at 19:40 Comment(5)
This is very useful extension to the accepted answer!Shavian
Perhaps I'm missing something, but how does this default to DateTimeKind.Utc as opposed to DateTimeKind.Unspecified?Wilke
@Rhonage Sorry about that. The default is set up in the ApplicationDbContext constructor. I updated the answer to include that.Immoralist
@Bob.at.AIPsychLab Thanks mate, much clearer now. Was trying to figure out if there was some weight Reflection going on - but nope, dead simple!Wilke
This fails if a model has a DateTIme attribute without a (public) setter method. Edit suggested. See also https://mcmap.net/q/131448/-how-to-check-if-property-setter-is-publicAssembler
T
29

In EF Core 6.0 DbContext.ConfigureConventions() is added that can register a conversion for all properties of a specific type. (https://learn.microsoft.com/en-us/ef/core/modeling/bulk-configuration#pre-convention-configuration)

Here's the solution using ConfigureConventions:

public class NullableDateTimeAsUtcValueConverter() : ValueConverter<DateTime?, DateTime?>(
    v => !v.HasValue ? v : ToUtc(v.Value), v => v.HasValue ? DateTime.SpecifyKind(v.Value, DateTimeKind.Utc) : v)
{
    private static DateTime? ToUtc(DateTime v) => v.Kind == DateTimeKind.Utc ? v : v.ToUniversalTime();
}    

public class DateTimeAsUtcValueConverter() : ValueConverter<DateTime, DateTime>(
    v => v.Kind == DateTimeKind.Utc ? v : v.ToUniversalTime(), v => DateTime.SpecifyKind(v, DateTimeKind.Utc));


protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
{
    ArgumentNullException.ThrowIfNull(configurationBuilder);
        
    configurationBuilder.Properties<DateTime>().HaveConversion<DateTimeAsUtcValueConverter>();
    configurationBuilder.Properties<DateTime?>().HaveConversion<NullableDateTimeAsUtcValueConverter>();
}

provided by https://github.com/dotnet/efcore/issues/4711#issuecomment-1048572602

Tace answered 28/7, 2022 at 14:29 Comment(3)
works perfectly! Small and easy. Put that code in DBContext or create a partial class of DBContext in case you work with Scaffolding like me.Aweinspiring
For efcore 6+ this is it!Thies
This didn't work in EF 7. The ConvertToProviderExpression is first argument, convertFromProviderExpression is the second argument. So I had to switch arguments to base in order to get it to work. base(v => DateTime.SpecifyKind(v, DateTimeKind.Utc), v => v)Slighting
P
21

This answer works with Entity Framework 6

The accepted answer does not work for Projected or Anonymous object. Performance could be a problem too.

To achieve this, we need to use a DbCommandInterceptor, an object provided by EntityFramework.

Create Interceptor:

public class UtcInterceptor : DbCommandInterceptor
{
    public override void ReaderExecuted(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext)
    {
        base.ReaderExecuted(command, interceptionContext);

        if (interceptionContext?.Result != null && !(interceptionContext.Result is UtcDbDataReader))
        {
            interceptionContext.Result = new UtcDbDataReader(interceptionContext.Result);
        }
    }
}

interceptionContext.Result is DbDataReader, which we replace by ours

public class UtcDbDataReader : DbDataReader
{
    private readonly DbDataReader source;

    public UtcDbDataReader(DbDataReader source)
    {
        this.source = source;
    }

    public override DateTime GetDateTime(int ordinal)
    {
        return DateTime.SpecifyKind(source.GetDateTime(ordinal), DateTimeKind.Utc);
    }        

    // you need to fill all overrides. Just call the same method on source in all cases

    public new void Dispose()
    {
        source.Dispose();
    }

    public new IDataReader GetData(int ordinal)
    {
        return source.GetData(ordinal);
    }
}

Register the interceptor in your DbConfiguration

internal class MyDbConfiguration : DbConfiguration
{
    protected internal MyDbConfiguration ()
    {           
        AddInterceptor(new UtcInterceptor());
    }
}

Finally, register the configuration for on your DbContext

[DbConfigurationType(typeof(MyDbConfiguration ))]
internal class MyDbContext : DbContext
{
    // ...
}

That's it. Cheers.

For simplicity, here is the entire implementation of DbReader:

using System;
using System.Collections;
using System.Data;
using System.Data.Common;
using System.IO;
using System.Threading;
using System.Threading.Tasks;

namespace MyNameSpace
{
    /// <inheritdoc />
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1010:CollectionsShouldImplementGenericInterface")]
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix")]
    public class UtcDbDataReader : DbDataReader
    {
        private readonly DbDataReader source;

        public UtcDbDataReader(DbDataReader source)
        {
            this.source = source;
        }

        /// <inheritdoc />
        public override int VisibleFieldCount => source.VisibleFieldCount;

        /// <inheritdoc />
        public override int Depth => source.Depth;

        /// <inheritdoc />
        public override int FieldCount => source.FieldCount;

        /// <inheritdoc />
        public override bool HasRows => source.HasRows;

        /// <inheritdoc />
        public override bool IsClosed => source.IsClosed;

        /// <inheritdoc />
        public override int RecordsAffected => source.RecordsAffected;

        /// <inheritdoc />
        public override object this[string name] => source[name];

        /// <inheritdoc />
        public override object this[int ordinal] => source[ordinal];

        /// <inheritdoc />
        public override bool GetBoolean(int ordinal)
        {
            return source.GetBoolean(ordinal);
        }

        /// <inheritdoc />
        public override byte GetByte(int ordinal)
        {
            return source.GetByte(ordinal);
        }

        /// <inheritdoc />
        public override long GetBytes(int ordinal, long dataOffset, byte[] buffer, int bufferOffset, int length)
        {
            return source.GetBytes(ordinal, dataOffset, buffer, bufferOffset, length);
        }

        /// <inheritdoc />
        public override char GetChar(int ordinal)
        {
            return source.GetChar(ordinal);
        }

        /// <inheritdoc />
        public override long GetChars(int ordinal, long dataOffset, char[] buffer, int bufferOffset, int length)
        {
            return source.GetChars(ordinal, dataOffset, buffer, bufferOffset, length);
        }

        /// <inheritdoc />
        public override string GetDataTypeName(int ordinal)
        {
            return source.GetDataTypeName(ordinal);
        }

        /// <summary>
        /// Returns datetime with Utc kind
        /// </summary>
        public override DateTime GetDateTime(int ordinal)
        {
            return DateTime.SpecifyKind(source.GetDateTime(ordinal), DateTimeKind.Utc);
        }

        /// <inheritdoc />
        public override decimal GetDecimal(int ordinal)
        {
            return source.GetDecimal(ordinal);
        }

        /// <inheritdoc />
        public override double GetDouble(int ordinal)
        {
            return source.GetDouble(ordinal);
        }

        /// <inheritdoc />
        public override IEnumerator GetEnumerator()
        {
            return source.GetEnumerator();
        }

        /// <inheritdoc />
        public override Type GetFieldType(int ordinal)
        {
            return source.GetFieldType(ordinal);
        }

        /// <inheritdoc />
        public override float GetFloat(int ordinal)
        {
            return source.GetFloat(ordinal);
        }

        /// <inheritdoc />
        public override Guid GetGuid(int ordinal)
        {
            return source.GetGuid(ordinal);
        }

        /// <inheritdoc />
        public override short GetInt16(int ordinal)
        {
            return source.GetInt16(ordinal);
        }

        /// <inheritdoc />
        public override int GetInt32(int ordinal)
        {
            return source.GetInt32(ordinal);
        }

        /// <inheritdoc />
        public override long GetInt64(int ordinal)
        {
            return source.GetInt64(ordinal);
        }

        /// <inheritdoc />
        public override string GetName(int ordinal)
        {
            return source.GetName(ordinal);
        }

        /// <inheritdoc />
        public override int GetOrdinal(string name)
        {
            return source.GetOrdinal(name);
        }

        /// <inheritdoc />
        public override string GetString(int ordinal)
        {
            return source.GetString(ordinal);
        }

        /// <inheritdoc />
        public override object GetValue(int ordinal)
        {
            return source.GetValue(ordinal);
        }

        /// <inheritdoc />
        public override int GetValues(object[] values)
        {
            return source.GetValues(values);
        }

        /// <inheritdoc />
        public override bool IsDBNull(int ordinal)
        {
            return source.IsDBNull(ordinal);
        }

        /// <inheritdoc />
        public override bool NextResult()
        {
            return source.NextResult();
        }

        /// <inheritdoc />
        public override bool Read()
        {
            return source.Read();
        }

        /// <inheritdoc />
        public override void Close()
        {
            source.Close();
        }

        /// <inheritdoc />
        public override T GetFieldValue<T>(int ordinal)
        {
            return source.GetFieldValue<T>(ordinal);
        }

        /// <inheritdoc />
        public override Task<T> GetFieldValueAsync<T>(int ordinal, CancellationToken cancellationToken)
        {
            return source.GetFieldValueAsync<T>(ordinal, cancellationToken);
        }

        /// <inheritdoc />
        public override Type GetProviderSpecificFieldType(int ordinal)
        {
            return source.GetProviderSpecificFieldType(ordinal);
        }

        /// <inheritdoc />
        public override object GetProviderSpecificValue(int ordinal)
        {
            return source.GetProviderSpecificValue(ordinal);
        }

        /// <inheritdoc />
        public override int GetProviderSpecificValues(object[] values)
        {
            return source.GetProviderSpecificValues(values);
        }

        /// <inheritdoc />
        public override DataTable GetSchemaTable()
        {
            return source.GetSchemaTable();
        }

        /// <inheritdoc />
        public override Stream GetStream(int ordinal)
        {
            return source.GetStream(ordinal);
        }

        /// <inheritdoc />
        public override TextReader GetTextReader(int ordinal)
        {
            return source.GetTextReader(ordinal);
        }

        /// <inheritdoc />
        public override Task<bool> IsDBNullAsync(int ordinal, CancellationToken cancellationToken)
        {
            return source.IsDBNullAsync(ordinal, cancellationToken);
        }

        /// <inheritdoc />
        public override Task<bool> ReadAsync(CancellationToken cancellationToken)
        {
            return source.ReadAsync(cancellationToken);
        }

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1063:ImplementIDisposableCorrectly")]
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1816:CallGCSuppressFinalizeCorrectly")]
        public new void Dispose()
        {
            source.Dispose();
        }

        public new IDataReader GetData(int ordinal)
        {
            return source.GetData(ordinal);
        }
    }
}
Publus answered 12/12, 2017 at 10:10 Comment(6)
So far this seems like the best answer. I tried the attribute variation first as it seemed less far reaching but my unit tests would fail with mocking as the constructor event tie-in doesn't seem to know about table mappings that occur in the OnModelCreating event. This one gets my vote!Etz
Why are you shadowing Dispose and GetData?Clericalism
This code should probably credit @IvanStoev: https://mcmap.net/q/131447/-datetime-kind-set-to-unspecified-not-utc-upon-loading-from-databaseMakepeace
Unfortunately this fails if you are mapping Spatial dataParotid
@Clericalism yea shadowing Dispose is mistake, override Dispose(bool)Publus
For persisting Local as UTC and querying with Local, refer to https://mcmap.net/q/131209/-entity-framework-datetime-and-utcIndoeuropean
H
17

Another year, another solution! This is for EF Core.

I have a lot of DATETIME2(7) columns that map to DateTime, and always store UTC. I don't want to store an offset because if my code is correct then the offset will always be zero.

Meanwhile I have other colums that store basic date-time values of unknown offset (provided by users), so they are just stored/displayed "as is", and not compared with anything.

Therefore I need a solution that I can apply to specific columns.

Define an extension method UsesUtc:

private static DateTime FromCodeToData(DateTime fromCode, string name)
    => fromCode.Kind == DateTimeKind.Utc ? fromCode : throw new InvalidOperationException($"Column {name} only accepts UTC date-time values");

private static DateTime FromDataToCode(DateTime fromData) 
    => fromData.Kind == DateTimeKind.Unspecified ? DateTime.SpecifyKind(fromData, DateTimeKind.Utc) : fromData.ToUniversalTime();

public static PropertyBuilder<DateTime?> UsesUtc(this PropertyBuilder<DateTime?> property)
{
    var name = property.Metadata.Name;
    return property.HasConversion<DateTime?>(
        fromCode => fromCode != null ? FromCodeToData(fromCode.Value, name) : default,
        fromData => fromData != null ? FromDataToCode(fromData.Value) : default
    );
}

public static PropertyBuilder<DateTime> UsesUtc(this PropertyBuilder<DateTime> property)
{
    var name = property.Metadata.Name;
    return property.HasConversion(fromCode => FromCodeToData(fromCode, name), fromData => FromDataToCode(fromData));
}

This can then be used on properties in model setup:

modelBuilder.Entity<CustomerProcessingJob>().Property(x => x.Started).UsesUtc();

It has the minor advantage over attributes that you can only apply it to properties of the correct type.

Note that it assumes values from the DB are in UTC but just have the wrong Kind. Therefore it polices the values you try to store in the DB, throwing a descriptive exception if they are not UTC.

Hawthorn answered 2/4, 2020 at 16:25 Comment(4)
This is a great solution that should be higher up especially now that most new development will be using Core or .NET 5. Bonus imaginary points for the UTC enforcement policy - if more people kept their dates UTC all the way to the actual user display, we'd have hardly any date/time bugs.Delagarza
I like the cleanliness of the solution but am lost at the step "Define an extension method"...where/how exactly?Erinn
Awesome solution. But 1 Q: In your FromDataToCode, the value fromData will always have DateTimeKind.Unspecified, no? If no, in what situation will it have something else?Tiercel
@Sнаđошƒаӽ yes, it could alternatively throw if the kind isn't UnspecifiedHawthorn
M
15

I believe I've found a solution that doesn't require any custom UTC checking or DateTime manipulation.

Basically you need to change your EF entities to use DateTimeOffset (NOT DateTime) datatype. This will store the time zone with the date value in the database (SQL Server 2015 in my case).

When EF Core requests the data from the DB it will receive the timezone info as well. When you pass this data to a web application (Angular2 in my case) the date is automatically converted to the local timezone of the browser which is what I expect.

And when it is passed back to my server it is converted to UTC again automatically, also as expected.

Mummify answered 28/2, 2017 at 17:16 Comment(11)
DateTimeOffset does not store the time zone, contrary to common perception. It stores an offset from UTC that the value represents. The offset cannot be mapped in reverse to determine the actual time zone the offset was created from, thereby making the datatype nearly useless.Pneumatophore
No, but it can be used to store a DateTime correctly: medium.com/@ojb500/in-praise-of-datetimeoffset-e0711f991cbaMalikamalin
Only UTC doesn't need a location, because it is everywhere the same. If you use something else than UTC you also need the location, else the information of time is useless, also at using datetimeoffset.Cowell
@Pneumatophore It's by far the most sensible way to store a point in time. All other date/time types don't give you the time zone either.Sacellum
DATETIMEOFFSET will do what the original poster wanted: store date-time as UTC without having to perform any (explicit) conversion. @Malikamalin DATETIME, DATETIME2, and DATETIMEOFFSET all store date-time value correctly. Other than additionally storing an offset from UTC, DATETIMEOFFSET has almost no advantage at all. What you use in your database is your call. I just wanted to drive home the point that it does not store a time zone as many people mistakenly think.Pneumatophore
@Pneumatophore With the offset from UTC you can figure out the set of time zones that the date you are storing could possibly fall in to. For example you could Google "what time zones is UTC +2" . You will then get this answer: "CAT Central Africa Time, CEST Central European Summer Time, EET Eastern European Time, IST Israel Standard Time, SAST South African Standard Time, USZ1 Kaliningrad Time, WAST West Africa Summer Time". So for me that is an advantage in comparison to other Date data types.Mummify
@Mummify Now, tell me which one of those time zones was the source of the timestamp and you may understand why DATETIMEOFFSET has nearly zero advantage. Guessing is rarely a good business strategy. You still have to store the time zone separately.Pneumatophore
@Pneumatophore The advantage is that you can send this date, as is, from your api to your client browser. When the client browser opens this date it knows what the offset is from UCT and is therefore able to convert it to the default date on the system that the client is viewing it. So the conversion from your server time zone to the browser timezone happens without the developer having to write any code for it.Mummify
If you convert the DATETIMEOFFSET to JSON and send it to the browser, you can convert the date to the local time zone of the browser in typescript like this: let localTime = new Date(yourDateTimeOffset)Mummify
I will add that at the time of writing, if you are using Postgres with EntityFramework - DateTimeOffset will possibly be difficult if not impossible to use unless you can guarantee all server times are set to the same timezone (it automatically converts DateTimeOffset to UTC, then converts it to LocalTime when reading from Postgres). Using DateTime to store a UTC value allows you to read it as such, without any conversion into or out.Xl
This worked perfectly for a read-only API. EF was returning server local times, despite the database being postgres (utc only). Now it returns the correct utc time with Z as the offset. Excellent! I love coming back to microsoft and finding idiocy like this.Ornstead
E
9

I'm researching this right now, and most of these answers aren't exactly great. From what I can see, there's no way to tell EF6 that the dates coming out of the database are in UTC format. If that is the case, the simplest way to make sure your model's DateTime properties are in UTC would be to verify and convert in the setter.

Here's some c# like pseudocode which describes the algorithm

public DateTime MyUtcDateTime 
{    
    get 
    {        
        return _myUtcDateTime;        
    }
    set
    {   
        if(value.Kind == DateTimeKind.Utc)      
            _myUtcDateTime = value;            
        else if (value.Kind == DateTimeKind.Local)         
            _myUtcDateTime = value.ToUniversalTime();
        else 
            _myUtcDateTime = DateTime.SpecifyKind(value, DateTimeKind.Utc);        
    }    
}

The first two branches are obvious. The last holds the secret sauce.

When EF6 creates a model from data loaded from the database, DateTimes are DateTimeKind.Unspecified. If you know your dates are all UTC in the db, then the last branch will work great for you.

DateTime.Now is always DateTimeKind.Local, so the above algorithm works fine for dates generated in code. Most of the time.

You have to be cautious, however, as there are other ways DateTimeKind.Unspecified can sneak into your code. For example, you might deserialize your models from JSON data, and your deserializer flavor defaults to this kind. It's up to you to guard against localized dates marked DateTimeKind.Unspecified from getting to that setter from anybody but EF.

Evelynneven answered 27/4, 2016 at 13:15 Comment(4)
As I found out after several years of wrestling with this issue, if you are assigning or selecting DateTime fields into other structures, for example a data transfer object, EF ignores both getter and setter methods. In these cases, you still have to change Kind to DateTimeKind.Utc after your results are generated. Example: from o in myContext.Records select new DTO() { BrokenTimestamp = o.BbTimestamp }; sets all Kind to DateTimeKind.Unspecified.Pneumatophore
I've been using DateTimeOffset with Entity Framework for a while and if you specify your EF entities with a data type of DateTimeOffset, then all your EF queries will return the dates with the offset from UTC, exactly like it is saved in the DB. So if you changed your data type to DateTimeOffset instead of DateTime you wouldn't need the above workaround.Mummify
That's nice to know! Thanks @MummifyEvelynneven
As per @Pneumatophore comment, this simply doesn't work and should be removedAmsterdam
F
7

Credits to @ajcvickers. Starting in EF Core 2.1, this would be one way to deal with DateTime.Kind:

modelBuilder
    .Entity<Foo>()
    .Property(e => e.SomeDate)
    .HasConversion(v => v, v => DateTime.SpecifyKind(v, DateTimeKind.Utc));

This will ensure every time the date is read from the database it is specified as Utc automatically.

Source:

https://github.com/dotnet/efcore/issues/4711#issuecomment-358695190

Farkas answered 9/6, 2021 at 21:26 Comment(0)
P
6

There is no way to specify the DataTimeKind in the Entity Framework. You may decide to convert the date time values to utc before storing to db and always assume the data retrived from db as UTC. But the DateTime objects materalized during query will always be "Unspecified". You could also evalualte using DateTimeOffset object instead of DateTime.

Phelia answered 18/8, 2011 at 14:13 Comment(0)
C
4

If you are careful to properly pass in UTC dates when you set the values and all you care about is making sure the DateTimeKind is set properly when the entities are retrieved from the database, see my answer here: https://mcmap.net/q/131446/-is-it-possible-to-prevent-entityframework-4-from-overwriting-customized-properties

Cornflower answered 21/2, 2012 at 22:35 Comment(0)
S
2

Matt Johnson-Pint's solution works, but if all your DateTimes are supposed to be UTC, creating an attribute would be too circuitous. Here is how I simplified it:

public class MyContext : DbContext
{
    public DbSet<Foo> Foos { get; set; }

    public MyContext()
    {
        ((IObjectContextAdapter)this).ObjectContext.ObjectMaterialized +=
            (sender, e) => SetDateTimesToUtc(e.Entity);
    }

    private static void SetDateTimesToUtc(object entity)
    {
        if (entity == null)
        {
            return;
        }

        var properties = entity.GetType().GetProperties();
        foreach (var property in properties)
        {
            if (property.PropertyType == typeof(DateTime))
            {
                property.SetValue(entity, DateTime.SpecifyKind((DateTime)property.GetValue(entity), DateTimeKind.Utc));
            }
            else if (property.PropertyType == typeof(DateTime?))
            {
                var value = (DateTime?)property.GetValue(entity);
                if (value.HasValue)
                {
                    property.SetValue(entity, DateTime.SpecifyKind(value.Value, DateTimeKind.Utc));
                }
            }
        }
    }
}
Sensible answered 14/11, 2019 at 14:49 Comment(0)
D
1

For those who need to achieve @MattJohnson solution with .net framework 4 like me, with reflection syntax/method limitation , it require a little bit modification as listed below:

     foreach (var property in properties)
        {     

            DateTimeKindAttribute attr  = (DateTimeKindAttribute) Attribute.GetCustomAttribute(property, typeof(DateTimeKindAttribute));

            if (attr == null)
                continue;

            var dt = property.PropertyType == typeof(DateTime?)
                ? (DateTime?)property.GetValue(entity,null)
                : (DateTime)property.GetValue(entity, null);

            if (dt == null)
                continue;

            //If the value is not null set the appropriate DateTimeKind;
            property.SetValue(entity, DateTime.SpecifyKind(dt.Value, attr.Kind) ,null);
        }  
Droplight answered 10/12, 2015 at 5:36 Comment(0)
I
1

The solutions here are useful, but I expect many are coming to this with the problem that they want all of their datetimes available in local timezone, but they want it translated so that the persisted version is saved UTC.

There are 3 challenges to implement this:

  1. Reading the data as UTC and converting to Local
  2. Adjusting query parameters e.g. SELECT * From PRODUCT where SALEDATE< @1
  3. Storing data which is LocalTime as UTC

1. Reading the data as UTC and converting to Local

In this case, the above solution based on the work of Ivan Stoev DateTime.Kind set to unspecified, not UTC, upon loading from database will do what you need.

2. Adjusting query parameters

Similarly to Ivan's solution for the interceptor, you can utilise the ReaderExecuting interceptor. Bonus is that this is far easier to implement than ReaderExecuted.

    public override void ReaderExecuting(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext)
    {
        foreach (DbParameter dbParameter in command.Parameters)
        {
            if (dbParameter.Value is DateTime dtLocal)
            {
                if (dtLocal.Kind != DateTimeKind.Utc)
                {
                    dbParameter.Value = dtLocal.ToUniversalTime();
                }
            }
        }
        base.ReaderExecuting(command, interceptionContext);
    }

3. Storing data which is LocalTime as UTC

While there are query interceptors that appear like they would assist here, they are called multiple times and created unexpected results. The best solution I came up with was to override SaveChanges

    public override int SaveChanges()
    {
        UpdateCommonProperties();
        UpdateDatesToUtc();
        bool saveFailed;
        do
        {
            saveFailed = false;
            try
            {
                var result = base.SaveChanges();
                return result;
            }
            catch (DbUpdateConcurrencyException ex)
            {
                saveFailed = ConcurrencyExceptionHandler(ex);
            }

        } while (saveFailed);
        return 0;
    }

    private void UpdateDatesToUtc()
    {
        if (!ChangeTracker.HasChanges()) return;

        var modifiedEntries = ChangeTracker.Entries().Where(x => (x.State == EntityState.Added || x.State == EntityState.Modified));

        foreach (var entry in modifiedEntries)
        {
            entry.ModifyTypes<DateTime>(ConvertToUtc);
            entry.ModifyTypes<DateTime?>(ConvertToUtc);
        }
    }

    private static DateTime ConvertToUtc(DateTime dt)
    {
        if (dt.Kind == DateTimeKind.Utc) return dt;
        return dt.ToUniversalTime();
    }

    private static DateTime? ConvertToUtc(DateTime? dt)
    {
        if (dt?.Kind == DateTimeKind.Utc) return dt;
        return dt?.ToUniversalTime();
    }

And the extension is (based on response by Talon https://mcmap.net/q/131449/-entity-framework-always-save-datetimeoffset-as-utc

public static class TypeReflectionExtension
{
    static Dictionary<Type, PropertyInfo[]> PropertyInfoCache = new Dictionary<Type, PropertyInfo[]>();

    static void TypeReflectionHelper()
    {
        PropertyInfoCache = new Dictionary<Type, PropertyInfo[]>();
    }

    public static PropertyInfo[] GetTypeProperties(this Type type)
    {
        if (!PropertyInfoCache.ContainsKey(type))
        {
            PropertyInfoCache[type] = type.GetProperties();
        }
        return PropertyInfoCache[type];
    }

    public static void ModifyTypes<T>(this DbEntityEntry dbEntityEntry, Func<T, T> method)
    {
        foreach (var propertyInfo in dbEntityEntry.Entity.GetType().GetTypeProperties().Where(p => p.PropertyType == typeof(T) && p.CanWrite))
        {
            propertyInfo.SetValue(dbEntityEntry.Entity, method(dbEntityEntry.CurrentValues.GetValue<T>(propertyInfo.Name)));
        }
    }
}
Indoeuropean answered 8/10, 2020 at 4:6 Comment(0)
O
1

To define DateTimeKind for entity properties in the Entity Framework Core, I propose the following class:

/// <summary>
/// Класс для преобразования DateTimeKind у сущностей в EF
/// </summary>
public static class DateTimeKindAnnotation
{
    private const string DateTimeKindAnnotation = "DateTimeKind";

    private static readonly ValueConverter<DateTime, DateTime> UtcConverter =
        new ValueConverter<DateTime, DateTime>(v => v, v => DateTime.SpecifyKind(v, DateTimeKind.Utc));

    private static readonly ValueConverter<DateTime, DateTime> LocalConverter =
        new ValueConverter<DateTime, DateTime>(v => v, v => DateTime.SpecifyKind(v, DateTimeKind.Local));

    private static readonly ValueConverter<DateTime, DateTime> UnspecifiedConverter =
        new ValueConverter<DateTime, DateTime>(v => v, v => DateTime.SpecifyKind(v, DateTimeKind.Unspecified));

    /// <summary>
    /// Данное свойство будет иметь заданный DateTimeKind в EF
    /// </summary>
    /// <typeparam name="TProperty"></typeparam>
    /// <param name="builder"></param>
    /// <param name="kind"></param>
    public static PropertyBuilder<DateTime> HasDateTimeKind(this PropertyBuilder<DateTime> builder, DateTimeKind kind) =>
        builder.HasAnnotation(DateTimeKindAnnotation, kind);

    /// <summary>
    /// Данное свойство будет иметь заданный DateTimeKind в EF
    /// </summary>
    /// <typeparam name="TProperty"></typeparam>
    /// <param name="builder"></param>
    /// <param name="kind"></param>
    public static PropertyBuilder<DateTime?> HasDateTimeKind(this PropertyBuilder<DateTime?> builder, DateTimeKind kind) =>
        builder.HasAnnotation(DateTimeKindAnnotation, kind);

    public static DateTimeKind? FindDateTimeKind(this IMutableProperty property)
    {
        var attribute = property.PropertyInfo.GetCustomAttribute<DateTimeKindAttribute>();
        if (attribute is not null)
        {
            return attribute.Kind;
        }
        return (DateTimeKind?)property.FindAnnotation(DateTimeKindAnnotation)?.Value;
    }

    /// <summary>
    /// Преобразует DateTimeKind у всех сущностей в EF к значению по умолчанию, заданному через атрибут или анотацию.
    /// </summary>
    /// <remarks>Убедитесь, что это вызывается после настройки всех ваших сущностей.</remarks>
    /// <param name="builder"></param>
    /// <param name="defaultKind">DateTimeKind, который надо использовать по умолчанию.</param>
    public static void ApplyDateTimeKindConverter(this ModelBuilder builder, DateTimeKind defaultKind = DateTimeKind.Utc)
    {
        foreach (var entityType in builder.Model.GetEntityTypes())
        {
            foreach (var property in entityType.GetProperties())
            {
                if (property.ClrType == typeof(DateTime) || property.ClrType == typeof(DateTime?))
                {
                    DateTimeKind kind = property.FindDateTimeKind() ?? defaultKind;
                    switch (kind)
                    {
                        case DateTimeKind.Utc:
                            property.SetValueConverter(UtcConverter);
                            break;
                        case DateTimeKind.Local:
                            property.SetValueConverter(LocalConverter);
                            break;
                        case DateTimeKind.Unspecified:
                            property.SetValueConverter(UnspecifiedConverter);
                            break;
                        default:
                            throw new NotSupportedException($"Kind \"{kind}\" неподдерживается");
                    }
                }
            }
        }
    }
}

/// <summary>
/// Задает тот DateTimeKind, который будет применяться EF для поля сущности.
/// </summary>
public class DateTimeKindAttribute : Attribute
{
    public DateTimeKindAttribute(DateTimeKind kind) => Kind = kind;
    public DateTimeKind Kind { get; }
}

For its simplest application, you need to add a call to ApplyDateTimeKindConverter () at the end of OnModelCreating. By default, all Datetime fields are assigned a single kind Utc. Exceptions can be implemented via the DateTimeKindAttribute attribute or the fluentapi method HasDateTimeKind (DateTimeKind.)

Ovipositor answered 5/6, 2021 at 9:58 Comment(0)
C
0

Another approach would be to create an interface with the datetime properties, implement them on the partial entity classes. And then use the SavingChanges event to check if the object is of the interface type, set those datetime values to whatever you want. In fact, if these are createdon/modifiedon kind of dates, you can use that event to populate them.

Congressman answered 18/8, 2011 at 14:16 Comment(1)
not a bad idea but the classes won't be used in anonymous selects.Pratincole
G
0

In my case, I had only one table with UTC datetimes. Here's what I did:

public partial class MyEntity
{
    protected override void OnPropertyChanged(string property)
    {
        base.OnPropertyChanged(property);            

        // ensure that values coming from database are set as UTC
        // watch out for property name changes!
        switch (property)
        {
            case "TransferDeadlineUTC":
                if (TransferDeadlineUTC.Kind == DateTimeKind.Unspecified)
                    TransferDeadlineUTC = DateTime.SpecifyKind(TransferDeadlineUTC, DateTimeKind.Utc);
                break;
            case "ProcessingDeadlineUTC":
                if (ProcessingDeadlineUTC.Kind == DateTimeKind.Unspecified)
                    ProcessingDeadlineUTC = DateTime.SpecifyKind(ProcessingDeadlineUTC, DateTimeKind.Utc);
            default:
                break;
        }
    }
}
Golda answered 9/2, 2012 at 20:9 Comment(0)
M
0

Expanded on the previous solution for EF Dotnet Core put code that works for me.

 public partial class XxxxxxDataContext {
    partial void CustomizeMapping(ref ModelBuilder modelBuilder) {
      var dateTimeConverter = new ValueConverter<DateTime, DateTime>(
        v => v.ToUniversalTime(),
        (v) => v.Kind == DateTimeKind.Utc?DateTime.SpecifyKind(v, DateTimeKind.Utc):TimeZoneInfo.ConvertTimeToUtc(v, TimeZoneInfo.Local));

      var nullableDateTimeConverter = new ValueConverter<DateTime?, DateTime?>(
        v => v.HasValue ? v.Value.ToUniversalTime() : v,
        v => v.HasValue ? v.Value.Kind == DateTimeKind.Utc ? DateTime.SpecifyKind(v.Value, DateTimeKind.Utc) : TimeZoneInfo.ConvertTimeToUtc(v.Value, TimeZoneInfo.Local) : v);

      foreach (var entityType in modelBuilder.Model.GetEntityTypes()) {
        if (entityType.IsKeyless) {
          continue;
        }

        foreach (var property in entityType.GetProperties()) {
          if (property.ClrType == typeof(DateTime)) {
            property.SetValueConverter(dateTimeConverter);
          } else if (property.ClrType == typeof(DateTime?)) {
            property.SetValueConverter(nullableDateTimeConverter);
          }
        }
      }
    }
  }
Mode answered 5/4, 2021 at 14:10 Comment(0)
S
0

In case you cannot convert the DataBase time from Local time to UTC, there is a workaround using AutoMapper and instead of SpecifyKind():

            CreateMap<DateTime, Google.Protobuf.WellKnownTypes.Timestamp>()
            .ConvertUsing(x => 
            Google.Protobuf.WellKnownTypes.Timestamp
            .FromDateTime(DateTime.SpecifyKind(x, DateTimeKind.Utc)));

Use ToUniversalTime():

            CreateMap<DateTime, Google.Protobuf.WellKnownTypes.Timestamp>()
                .ConvertUsing(x => 
                Google.Protobuf.WellKnownTypes.Timestamp.FromDateTime(
                    (x).ToUniversalTime()));

Or vice-versa is case if one doesn't work for you

Sib answered 13/12, 2022 at 10:32 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.