Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How To Set DateTime.Kind for all DateTime Properties on an Object using Reflection

In my application I retrieve domain objects via a web service. In the web service data, I know all the date values are UTC, but the web service does not format its xs:dateTime values as UTC dates. (In other words the letter Z is not appended to the end of each date to indicate UTC.)

I cannot change the way the web service behaves at this time, but as a workaround I have created a method which I call immediately after the objects from the web service have been deserialized.

    private void ExplicitlyMarkDateTimesAsUtc<T>(T obj) where T : class
    {
        Type t = obj.GetType();

        // Loop through the properties.
        PropertyInfo[] props = t.GetProperties();
        for (int i = 0; i < props.Length; i++)
        {
            PropertyInfo p = props[i];
            // If a property is DateTime or DateTime?, set DateTimeKind to DateTimeKind.Utc.
            if (p.PropertyType == typeof(DateTime))
            {
                DateTime date = (DateTime)p.GetValue(obj, null);
                date = DateTime.SpecifyKind(date, DateTimeKind.Utc);
                p.SetValue(obj, date, null);
            }
            // Same check for nullable DateTime.
            else if (p.PropertyType == typeof(Nullable<DateTime>))
            {
                DateTime? date = (DateTime?)p.GetValue(obj, null);
                DateTime? newDate = DateTime.SpecifyKind(date.Value, DateTimeKind.Utc);
                p.SetValue(obj, newDate, null);
            }
        }
    }

The method takes an object and loops through its properties, finding the properties that are either DateTime or Nullable<DateTime>, and then (is supposed to) explicitly sets the DateTime.Kind property for each of the property values to DateTimeKind.Utc.

The code does not throw any exceptions, but obj never gets its DateTime properties changed. In the debugger p.SetValue(obj, date, null); is called, but obj never gets modified.

Why aren't changes getting applied to obj?

like image 577
RunnerRick Avatar asked Mar 09 '11 22:03

RunnerRick


People also ask

How do you specify the kind in DateTime?

Syntax: public static DateTime SpecifyKind (DateTime value, DateTimeKind kind); Parameters: value: It is the date and time. kind: It is one of the enumeration values which indicates whether the new object represents local time, UTC, or neither.

What is DateTime kind?

DateTimeKind. One of the enumeration values that indicates what the current time represents. The default is Unspecified.

What is the use of DateTime properties?

DateTime contains other properties as well, like Hour, Minute, Second, Millisecond, Year, Month, and Day. The other properties of DateTime are: We can get the name of the day from the week with the help of the DayOfWeek property. To get the day of the year, we will use DayOfYear property.


2 Answers

Works fine when I try it. Beware that you are only changing the Kind, not the time. And you don't handle null dates properly, you cannot use date.Value if date.HasValue is false. Make sure that the exception isn't caught silently and bypassing the rest of the property assignments. Fix:

            // Same check for nullable DateTime.
            else if (p.PropertyType == typeof(Nullable<DateTime>)) {
                DateTime? date = (DateTime?)p.GetValue(obj, null);
                if (date.HasValue) {
                    DateTime? newDate = DateTime.SpecifyKind(date.Value, DateTimeKind.Utc);
                    p.SetValue(obj, newDate, null);
                }
            }
like image 200
Hans Passant Avatar answered Oct 15 '22 20:10

Hans Passant


See http://derreckdean.wordpress.com/2013/04/24/converting-all-datetime-properties-of-an-object-graph-to-local-time-from-utc/ for blog post. I use this code to convert a WCF response object graph to have all local times:

/// <summary>
/// Since all dates in the DB are stored as UTC, this converts dates to the local time using the Windows time zone settings.
/// </summary>
public static class AllDateTimesAsUTC
{

    /// <summary>
    /// Specifies that an object's dates are coming in as UTC.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="obj"></param>
    /// <returns></returns>
    public static T AllDatesAreUTC<T>(this T obj)
    {
        if (obj == null)
        {
            return default(T);
        }
        IterateDateTimeProperties(obj);
        return obj;
    }

    private static void IterateDateTimeProperties(object obj)
    {
        if (obj == null)
        {
            return;
        }
        var properties = obj.GetType().GetProperties();
        //Set all DaetTimeKinds to Utc
        foreach (var prop in properties)
        {
            var t = prop.PropertyType;
            if (t == typeof(DateTime) || t == typeof(DateTime?))
            {
                SpecifyUtcKind(prop, obj);
            }
            else if (t.IsEnumerable())
            {
                var vals = prop.GetValue(obj, null);
                if (vals != null)
                {
                    foreach (object o in (IEnumerable)vals)
                    {
                        IterateDateTimeProperties(o);
                    }
                }
            }
            else
            {
                var val = prop.GetValue(obj, null);
                if (val != null)
                {
                    IterateDateTimeProperties(val);
                }
            }
        }
        //properties.ForEach(property => SpecifyUtcKind(property, obj));
        return; // obj;
    }

    private static void SpecifyUtcKind(PropertyInfo property, object value)
    {
        //Get the datetime value
        var datetime = property.GetValue(value, null);
        DateTime output;

        //set DateTimeKind to Utc
        if (property.PropertyType == typeof(DateTime))
        {
            output = DateTime.SpecifyKind((DateTime)datetime, DateTimeKind.Utc);
        }

        else if (property.PropertyType == typeof(DateTime?))
        {
            var nullable = (DateTime?)datetime;
            if (!nullable.HasValue) return;
            output = (DateTime)DateTime.SpecifyKind(nullable.Value, DateTimeKind.Utc);
        }
        else
        {
            return;
        }

        Debug.WriteLine("     ***** Converted date from {0} to {1}.", datetime, output);
        datetime = output.ToLocalTime();

        //And set the Utc DateTime value
        property.SetValue(value, datetime, null);
    }
    internal static Type[] ConvertibleTypes = {typeof(bool), typeof(byte), typeof(char),
typeof(DateTime), typeof(decimal), typeof(double), typeof(float), typeof(int), 
typeof(long), typeof(sbyte), typeof(short), typeof(string), typeof(uint), 
typeof(ulong), typeof(ushort)};

    /// <summary>
    /// Returns true if this Type matches any of a set of Types.
    /// </summary>
    /// <param name="types">The Types to compare this Type to.</param>
    public static bool In(this Type type, params Type[] types)
    {
        foreach (Type t in types) if (t.IsAssignableFrom(type)) return true; return false;
    }

    /// <summary>
    /// Returns true if this Type is one of the types accepted by Convert.ToString() 
    /// (other than object).
    /// </summary>
    public static bool IsConvertible(this Type t) { return t.In(ConvertibleTypes); }

    /// <summary>
    /// Gets whether this type is enumerable.
    /// </summary>
    public static bool IsEnumerable(this Type t)
    {
        return typeof(IEnumerable).IsAssignableFrom(t);
    }

    /// <summary>
    /// Returns true if this property's getter is public, has no arguments, and has no 
    /// generic type parameters.
    /// </summary>
    public static bool SimpleGetter(this PropertyInfo info)
    {
        MethodInfo method = info.GetGetMethod(false);
        return method != null && method.GetParameters().Length == 0 &&
             method.GetGenericArguments().Length == 0;
    }

}

(Some of the code came from other SO posts.)

To use: call .AllDatesAreUTC() from any object. It will walk the graph and do the local time conversions.

    void svc_ZOut_GetZOutSummaryCompleted(object sender, ZOut_GetZOutSummaryCompletedEventArgs e)
    {
        svc.ZOut_GetZOutSummaryCompleted -= new EventHandler<ZOut_GetZOutSummaryCompletedEventArgs>(svc_ZOut_GetZOutSummaryCompleted);
        svc = null;
        var whenDone = (Action<bool, ZOutResult>)e.UserState;
        if (e.Error != null)
        {
            FireOnExceptionRaised(e.Error);
            whenDone(false, null);
        }
        else
        {
            var res = e.Result.AllDatesAreUTC();
            FireOnSessionReceived(res.IsError, res.Session);
            if (res.IsError == true)
            {
                whenDone(false, null);
            }
            else
            {
                whenDone(true, res.Result);
            }
        }
    }

You can change the behavior to mark times as UTC without changing the time itself by modifying the SpecifyUtcKind method.

EDIT: I don't recommend using this on an object graph with circular references, as per the conversation in the comments.

like image 1
Derreck Dean Avatar answered Oct 15 '22 19:10

Derreck Dean