如何使用Reflection为对象上的所有DateTime属性设置DateTime.Kind

时间:2011-03-09 22:27:22

标签: c# datetime reflection

在我的应用程序中,我通过Web服务检索域对象。在Web服务数据中,我知道所有日期值都是UTC,但Web服务不会将其xs:dateTime值格式化为UTC日期。 (换句话说,字母Z未附加到每个日期的末尾以表示UTC。)

我目前无法改变Web服务的行为方式,但作为一种解决方法,我创建了一个方法,在Web服务中的对象被反序列化后立即调用该方法。

    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);
            }
        }
    }

该方法接受一个对象并遍历其属性,找到DateTimeNullable<DateTime>的属性,然后(应该)为每个属性显式设置DateTime.Kind属性属性值为DateTimeKind.Utc

代码不会抛出任何异常,但obj永远不会更改其DateTime属性。调试器p.SetValue(obj, date, null);被调用,但obj永远不会被修改。

为什么不将更改应用于obj

4 个答案:

答案 0 :(得分:31)

我尝试时工作正常。请注意,你只是在改变种类,而不是时间。并且您没有正确处理空日期,如果date.HasValue为false,则不能使用date.Value。确保不会以静默方式捕获异常并绕过其余的属性分配。修正:

            // 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);
                }
            }

答案 1 :(得分:1)

有关博客文章,请参阅http://derreckdean.wordpress.com/2013/04/24/converting-all-datetime-properties-of-an-object-graph-to-local-time-from-utc/。我使用此代码将WCF响应对象图转换为具有所有本地时间:

/// <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;
    }

}

(部分代码来自其他SO帖子。)

使用:从任何对象调用.AllDatesAreUTC()。它将走图表并进行本地时间转换。

    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);
            }
        }
    }

您可以通过修改SpecifyUtcKind方法更改行为以将时间标记为UTC而不更改时间本身。

编辑:根据评论中的对话,我建议不要在带有循环引用的对象图上使用它。

答案 2 :(得分:0)

我知道这事后很好,但希望它可以帮助某人。我试图在原始帖子中做与RickRunner完全相同的事情,并提出了非常相似的代码。我遇到了类似的问题,虽然对我来说obj.Kind被设置得很好,如果属性是常规的DateTime类型;但是对于可以为空的DateTime属性,无论我做什么,Kind都没有被修改。最后,我发现如果我将属性设置为null然后再返回DateTime,它会正确地重置Kind:

// 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, null, null);
        p.SetValue(obj, newDate, null);
    }
}

这很难看,我没有深入挖掘,试图弄清楚为什么SetValue没有正确地设置好。我花了相当多的时间在这上面,并且很高兴能够找到解决方案,无论多么生气。

答案 3 :(得分:0)

OP的代码非常有限,因为它不会遍历子列表和对象来查找DateTime属性,而只会查看顶层对象。 Derreck Dean的代码可以工作,但是就其服务目的而言非常冗长。这是一个更为简洁/有效的DateTime扩展,可用于处理目标对象(包括其子列表和对象)中的任何DateTime或可为null的DateTime属性的转换。

public static void ConvertDatesToUtc(this object obj) {
            foreach (var prop in obj.GetType().GetProperties().Where(p => p.CanWrite)) {
                var t = prop.PropertyType;
                if (t == typeof(DateTime)) {
                    //found datetime, specify its kind as utc.
                    var oldValue = (DateTime)prop.GetValue(obj, null);
                    var newValue = DateTime.SpecifyKind(oldValue, DateTimeKind.Utc);
                    prop.SetValue(obj, newValue, null);
                } else if (t == typeof(DateTime?)) {
                    //found nullable datetime, if populated specify its kind as utc.
                    var oldValue = (DateTime?)prop.GetValue(obj, null);
                    if (oldValue.HasValue) {
                        var newValue = (DateTime)DateTime.SpecifyKind(oldValue.Value, DateTimeKind.Utc);
                        prop.SetValue(obj, newValue, null);
                    }
                } else if (typeof(IEnumerable).IsAssignableFrom(t)) {
                    //traverse child lists recursively.
                    var vals = prop.GetValue(obj, null);
                    if (vals != null) {
                        foreach (object o in (IEnumerable)vals) {
                            ConvertDatesToUtc(o);
                        }
                    }
                } else {
                    //traverse child objects recursively.
                    var val = prop.GetValue(obj, null);
                    if (val != null)
                        ConvertDatesToUtc(val);
                }
            }
        }