44

I have C# program where all DateTime objects are DateTimeKind.UTC. When saving the objects to the database it stores UTC as expected. However, when retrieving them, they are DateTimeKind.Unspecified. Is there a way to tell Entity Framework (Code First) when creating DateTime objects in C# to always use DateTimeKind.UTC?

Leniel Maccaferri
  • 100,159
  • 46
  • 371
  • 480
William
  • 1,354
  • 3
  • 13
  • 18

4 Answers4

22

No, there's not. And it's actually DateTimeKind.Unspecified.

However, if you are concerned about supporting multiple timezones, you should consider using DateTimeOffset. It's like a regular DateTime, except that it does not represent a "perspective" of time, it represents an absolute view, in which 3PM (UTC - 3) equals 4PM (UTC - 2). DateTimeOffset contains both the DateTime and the time zone and it's supported by both EntityFramework and SQL Server.

Andre Pena
  • 56,650
  • 48
  • 196
  • 243
  • but what about this...https://social.msdn.microsoft.com/Forums/en-US/1a86afb2-e5c7-46b6-9759-d815fad8da5e/entity-framework-convert-datetime-between-kinds?forum=adodotnetentityframework ? – Rufus L Apr 07 '15 at 16:53
  • They are creating another non persistent property for converting the DateTime to UTC or Local. Not exactly what OP wants. – Andre Pena Apr 07 '15 at 17:21
  • 24
    `DateTimeOffset` *does not* contain the time zone: it contains a *UTC offset* for the instant in time where the value was created. It has a little more information about the actual value, but it's hardly more useful than a bare UTC `DateTime` value. If you want a value relative to a time zone, you have to store that time zone somewhere and convert the value, just as you would for a `DateTime`. – Suncat2000 Sep 09 '16 at 13:53
  • I cannot seem to even get at the datetime component of datetimeoffset in entity framework. There's no way to cast it to datetime to get the "local" part of the value. In SQL, it would simply be "cast(value as datetime)", but there seems to be no such equivalent in EF. – Triynko Dec 14 '16 at 04:16
  • SQLLite in memory doesn't support `DateTimeOffset` queries :-( https://learn.microsoft.com/en-us/ef/core/providers/sqlite/limitations#query-limitations – Worthy7 May 29 '21 at 07:19
8

You can have your datacontext fix up all the relevant values as it goes. The following does so with a cache of properties for entity types, so as to avoid having to examine the type each time:

public class YourContext : DbContext
{
  private static readonly List<PropertyInfo> EmptyPropsList = new List<PropertyInfo>();
  private static readonly Hashtable PropsCache = new Hashtable(); // Spec promises safe for single-reader, multiple writer.
                                                                  // Spec for Dictionary makes no such promise, and while
                                                                  // it should be okay in this case, play it safe.
  private static List<PropertyInfo> GetDateProperties(Type type)
  {
    List<PropertyInfo> list = new List<PropertyInfo>();
    foreach(PropertyInfo prop in type.GetProperties())
    {
      Type valType = prop.PropertyType;
      if(valType == typeof(DateTime) || valType == typeof(DateTime?))
        list.Add(prop);
    }
    if(list.Count == 0)
      return EmptyPropsList; // Don't waste memory on lots of empty lists.
    list.TrimExcess();
    return list;
  }
  private static void FixDates(object sender, ObjectMaterializedEventArgs evArg)
  {
    object entity = evArg.Entity;
    if(entity != null)
    {
      Type eType = entity.GetType();
      List<PropertyInfo> rules = (List<PropertyInfo>)PropsCache[eType];
      if(rules == null)
        lock(PropsCache)
          PropsCache[eType] = rules = GetPropertyRules(eType); // Don't bother double-checking. Over-write is safe.
      foreach(var rule in rules)
      {
        var info = rule.PropertyInfo;
        object curVal = info.GetValue(entity);
        if(curVal != null)
          info.SetValue(entity, DateTime.SpecifyKind((DateTime)curVal, rule.Kind));
      }
    }
  }
  public YourContext()
  {
    ((IObjectContextAdapter)this).ObjectContext.ObjectMaterialized += FixDates;
    /* rest of constructor logic here */
  }
  /* rest of context class here */
}

This can also be combined with attributes so as to allow one to set the DateTimeKind each property should have, by storing a set of rules about each property, rather than just the PropertyInfo, and looking for the attribute in GetDateProperties.

Jon Hanna
  • 110,372
  • 10
  • 146
  • 251
  • Looks nice! There is `GetPropertyRules` `GetDateProperties` mismatch though – Artyom Mar 11 '16 at 12:33
  • And wouldn't it be better to have `private static readonly List EmptyPropsList = new List(0);`? – Artyom Mar 11 '16 at 12:34
  • 1
    @Artyom ironically at the time I wrote this I'd have thought the explicit 0 was better and the above just missed it by mistake, but since then I've compared the code paths and learned that there's a slight advantage in using no capacity argument for a list you intend to keep empty. – Jon Hanna Mar 11 '16 at 12:52
  • 1
    is something like this available in entity framework 7? – Radu D Oct 02 '16 at 16:01
  • Does not work for projected or anonymous object. – user2397863 Dec 11 '17 at 09:06
  • @user2397863 does it not? I seem to recall that it does, though I've been using EF Core of late for which the above isn't applicable so I'm not quite fresh in EF6. – Jon Hanna Dec 11 '17 at 10:19
3

My solution, using code first: Declare the DateTime properties in this way:

private DateTime _DateTimeProperty;
public DateTime DateTimeProperty
{
    get
    {
        return _DateTimeProperty;
    }
    set
    {
        _DateTimeProperty = value.ToKindUtc();
    }
}

Also can create the property as:

private DateTime? _DateTimeProperty;
public DateTime? DateTimeProperty
{
    get
    {
        return _DateTimeProperty;
    }
    set
    {
        _DateTimeProperty = value.ToKindUtc();
    }
}

ToKindUtc() is a extension to change DateTimeKind.Unspecified to DateTimeKind.Utc or call ToUniversalTime() if kind is DateTimeKind.Local Here the code for the extensions:

public static class DateTimeExtensions
{
    public static DateTime ToKindUtc(this DateTime value)
    {
        return KindUtc(value);
    }
    public static DateTime? ToKindUtc(this DateTime? value)
    {
        return KindUtc(value);
    }
    public static DateTime ToKindLocal(this DateTime value)
    {
        return KindLocal(value);
    }
    public static DateTime? ToKindLocal(this DateTime? value)
    {
        return KindLocal(value);
    }
    public static DateTime SpecifyKind(this DateTime value, DateTimeKind kind)
    {
        if (value.Kind != kind)
        {
            return DateTime.SpecifyKind(value, kind);
        }
        return value;
    }
    public static DateTime? SpecifyKind(this DateTime? value, DateTimeKind kind)
    {
        if (value.HasValue)
        {
            return DateTime.SpecifyKind(value.Value, kind);
        }
        return value;
    }
    public static DateTime KindUtc(DateTime value)
    {
        if (value.Kind == DateTimeKind.Unspecified)
        {
            return DateTime.SpecifyKind(value, DateTimeKind.Utc);
        }
        else if (value.Kind == DateTimeKind.Local)
        {
            return value.ToUniversalTime();
        }
        return value;
    }
    public static DateTime? KindUtc(DateTime? value)
    {
        if (value.HasValue)
        {
            return KindUtc(value.Value);
        }
        return value;
    }
    public static DateTime KindLocal(DateTime value)
    {
        if (value.Kind == DateTimeKind.Unspecified)
        {
            return DateTime.SpecifyKind(value, DateTimeKind.Local);
        }
        else if (value.Kind == DateTimeKind.Utc)
        {
            return value.ToLocalTime();
        }
        return value;
    }
    public static DateTime? KindLocal(DateTime? value)
    {
        if (value.HasValue)
        {
            return KindLocal(value.Value);
        }
        return value;
    }
}

Remember to include in the model's file.

using TheNameSpaceWhereClassIsDeclared;

The set method of property is called when reading from datatabase with EF, or when assigned in a MVC controller's edit method.

Warning, if in web forms, if you edit dates in local timezone, you MUST convert the date to UTC before send to server.

FRL
  • 748
  • 7
  • 9
1

Have a look on the michael.aird answer here: https://stackoverflow.com/a/9386364/279590 It stamp the date UTC kind during loading, with an event on ObjectMaterialized.

Community
  • 1
  • 1
Olivier de Rivoyre
  • 1,579
  • 1
  • 18
  • 24