2011-03-09 13 views
21

En mi aplicación, recupero objetos de dominio a través de un servicio web. En los datos del servicio web, sé que todos los valores de fecha son UTC, pero el servicio web no formatea sus valores xs:dateTime como fechas UTC. (En otras palabras, la letra Z no se adjunta al final de cada fecha para indicar UTC.)Cómo configurar DateTime.Kind para todas las propiedades de DateTime en un objeto usando Reflection

No puedo cambiar la forma en que se comporta el servicio web en este momento, pero como solución alternativa he creado un método que llamo inmediatamente después de que los objetos del servicio web hayan sido deserializados.

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

El método toma un objeto y bucles a través de sus propiedades, la búsqueda de las propiedades que son o bien (se supone que) DateTime o Nullable<DateTime>, y luego establece explícitamente la propiedad DateTime.Kind para cada uno de los valores de propiedad para DateTimeKind.Utc.

El código no arroja ninguna excepción, pero obj nunca obtiene sus propiedades DateTime modificadas. En el depurador se llama p.SetValue(obj, date, null);, pero obj nunca se modifica.

¿Por qué no se aplican los cambios a obj?

+1

¿Cómo se determina que obj no está actualizado? –

+0

@Stefan Lo puedo ver en el depurador. Sus valores de propiedad no son modificados. – RunnerRick

+0

Intente cambiar el valor no solo el tipo, quizás agregando para ver si cambia. Cambiar el tipo no debería cambiar el valor. – Aliostad

Respuesta

28

Funciona bien cuando lo intento. Ten en cuenta que solo estás cambiando el tipo, no el tiempo. Y no maneja las fechas nulas correctamente, no puede usar date.Value if date.HasValue is false. Asegúrese de que la excepción no se capture silenciosamente y evite el resto de las asignaciones de propiedades. REVISIÓN:

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

Es un buen control para agregar. Sin embargo, no resuelve mi problema. (Cuando depuré mi código, estaba dentro del primer bloque 'if'. – RunnerRick

+0

Estoy 99% seguro de que el código es bueno. Desconfíe del depurador si es necesario. –

+0

A mí también me parece bien. Sin embargo, cuando mis objetos se usan en la interfaz de usuario, el desplazamiento de la zona horaria es incorrecto. Cuando preprocesé los objetos antes de usarlos en la interfaz de usuario, se solucionó el problema de la zona horaria. Así que pude preprocesar los objetos antes de usarlos en el UI, pero estaba intentando evitar repetir el mismo código en todas partes. – RunnerRick

1

Ver http://derreckdean.wordpress.com/2013/04/24/converting-all-datetime-properties-of-an-object-graph-to-local-time-from-utc/ para la entrada en el blog. Puedo utilizar este código para convertir un gráfico WCF objeto respuesta para tener todos los tiempos locales:

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

} 

(. Parte del código procedían de otros mensajes SO)

Para usar: llamar .AllDatesAreUTC() desde cualquier objeto. Recorrerá el gráfico y realizará las conversiones de tiempo local.

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

Puede cambiar el comportamiento para marcar los tiempos como UTC sin cambiar el tiempo mismo modificando el método SpecifyUtcKind.

EDIT: No recomiendo usar esto en un gráfico de objetos con referencias circulares, según la conversación en los comentarios.

+0

Esto arroja una excepción de stackoverflow. Probablemente debido a su recurrencia a través del gráfico de objetos, pasando por todas las propiedades, incluidas las cadenas y primitivas. –

+0

¿Qué tan grande es su gráfico de objetos, y tiene objetos que hacen referencia a otros objetos que están en el mismo gráfico? Este código no está hecho para manejar eso; Solo lo necesitaba para manejar gráficos simples que no se vinculan entre sí, lo que definitivamente causaría la 'StackOverflowException'. –

+0

Tal vez sea ese el caso ... Mi gráfico tiene una propiedad que es una lista paginada y cada elemento de esa colección es un objeto EF con propiedades de navegación. No puedo entender por mi vida cómo atravesar el gráfico, ignorar las primitivas (entre otras cosas) solo para encontrar las fechas. –

0

Sé que esto está bien después del hecho, pero con la esperanza de que pueda ayudar a alguien. Intenté hacer lo mismo que RickRunner en la publicación original y obtuve un código muy similar. Me encontré con un problema similar, aunque para mí obj.Kind se estaba estableciendo bien si la propiedad era del tipo regular de DateTime; sin embargo, para las propiedades de DateTime que se pueden anular, el tipo no se modificó sin importar lo que hice. Al final, descubrí que si establece la propiedad en null y luego de vuelta a un DateTime, no restablece la clase correctamente:

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

Es feo y yo no cavar demasiado profundo para tratar de la figura por qué SetValue no establece el tipo correctamente en primer lugar. Pasé bastante tiempo con esto y me alegré de haber llegado a una solución, por muy desagradable que fuera.

Cuestiones relacionadas