Как установить DateTime.Kind для всех свойств DateTime на объекте с помощью отражения - PullRequest
22 голосов
/ 10 марта 2011

В моем приложении я получаю доменные объекты через веб-сервис. В данных веб-службы я знаю, что все значения даты указаны в формате 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?

Ответы [ 3 ]

31 голосов
/ 10 марта 2011

Работает нормально, когда я пытаюсь.Остерегайтесь того, что вы меняете только Вид, а не время.И вы не обрабатываете нулевые даты должным образом, вы не можете использовать date.Value, если date.HasValue имеет значение false.Удостоверьтесь, что исключение не перехватывается молча и обходя остальные назначения свойств.Исправить:

            // 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 голос
/ 25 апреля 2013

См. 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.)

Для использования: call .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.

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

0 голосов
/ 23 октября 2013

Я знаю, что это хорошо после факта, но надеюсь, что это может кому-то помочь. Я пытался сделать то же самое, что и RickRunner в оригинальном посте, и придумал очень похожий код. Я столкнулся с подобной проблемой, хотя для меня obj.Kind устанавливался нормально, если свойство было обычного типа DateTime; однако для свойств DateTime, допускающих значение NULL, тип не изменялся, что бы я ни делал. В конце концов, я обнаружил, что если я установил для свойства значение null, а затем вернулся к DateTime, он правильно сбросил тип:

// 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 неправильно устанавливает Kind. Я потратил немало времени на это и был просто рад, что нашел решение, каким бы громким оно ни было.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...