Подтвердить что ты не робот

Как установить DateTime.Kind для всех свойств DateTime объекта, используя Reflection

В моем приложении я получаю объекты домена через веб-службу. В данных веб-службы я знаю, что все значения даты - это UTC, но веб-служба не форматирует значения xs:dateTime в качестве дат UTC. (Другими словами, буква Z не добавляется к концу каждой даты, чтобы указать UTC.)

Я не могу изменить способ ведения веб-службы в это время, но в качестве обходного пути я создал метод, который я вызываю сразу же после того, как объекты из веб-службы были десериализованы.

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

Метод берет объект и перебирает его свойства, обнаруживая свойства, которые являются либо DateTime, либо Nullable<DateTime>, а затем (предполагается) явно устанавливает свойство DateTime.Kind для каждого из значений свойств DateTimeKind.Utc.

Код не генерирует никаких исключений, но obj никогда не изменяет свойства DateTime. В отладчике p.SetValue(obj, date, null); вызывается, но obj никогда не изменяется.

Почему не применяются изменения к obj?

4b9b3361

Ответ 1

Хорошо работает, когда я пытаюсь. Остерегайтесь того, что вы меняете вид, а не время. И вы неправильно обрабатываете нулевые даты, вы не можете использовать date.Value, если date.HasValue является ложным. Убедитесь, что исключение не застигнуто молча и минуя остальные назначения свойств. 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);
                }
            }

Ответ 2

Смотрите 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 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 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);
            }
        }
    }

Вы можете изменить поведение, чтобы пометить время как UTC, не изменяя самого времени, изменив метод SpecifyUtcKind.

РЕДАКТИРОВАТЬ: Я не рекомендую использовать это на графе объектов с круговыми ссылками, как в разговоре в комментариях.

Ответ 3

Я знаю, что это хорошо после факта, но надеясь, что это может помочь кому-то. Я пытался сделать то же самое, что и RickRunner в оригинальном посте, и придумал очень похожий код. Я столкнулся с аналогичной проблемой, хотя для меня obj.Kind устанавливался нормально, если свойство имело обычный тип DateTime; однако для свойств свойства DateTime с нулевым значением вид не изменялся независимо от того, что я сделал. В конце концов, я обнаружил, что если я установил свойство в null, а затем обратно в DateTime, он будет reset 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 не задает правильный вид в первую очередь. Я потратил немало времени на это и был просто рад, что пришел к решению, хотя и грозный.

Ответ 4

Код OP очень ограничен в том смысле, что он не перебирает дочерние списки и объекты, ищущие свойства DateTime, он только смотрит на объект верхнего уровня. Код Дерека Дина работает, но очень многословен для цели, которой он служит. Вот более краткое/рабочее расширение DateTime, которое можно использовать для преобразования любого свойства DateTime или свойства 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);
                }
            }
        }