2009-08-19 15 views
12

OK, ¿qué me falta aquí? MSDN dice lo siguiente con respecto a DateTimeSerializationMode:Roundtrip XML Serialización de DateTime y xsd: date?

En las versiones 2.0 y posteriores del marco .NET , con esta propiedad establecida en objetos RoundtripDateTime son examinados para determinar si están en el local o UTC un tiempo no especificado zona, y se serializan de tal manera que esta información se conserva. Este es el comportamiento predeterminado y es recomendado para todas las aplicaciones nuevas que no se comuniquen con las versiones anteriores del marco.

Sin embargo:

namespace ConsoleApplication1 { 
    public class DateSerTest { 
     [XmlElement(DataType = "date")] 
     public DateTime Date { get; set; } 
    } 

    class Program { 
     static void Main(string[] args) { 
      DateSerTest d = new DateSerTest { 
       Date = DateTime.SpecifyKind(new DateTime(2009,8,18), DateTimeKind.Utc), 
      }; 
      XmlSerializer ser = new XmlSerializer(typeof(DateSerTest)); 
      using (FileStream fs = new FileStream("out.xml", FileMode.Create)) { 
       ser.Serialize(fs, d); 
      } 

      // out.xml will contain: 
      // <Date>2009-08-18</Date> 

      using (FileStream fs = new FileStream("out.xml", FileMode.Open)) { 
       DateSerTest d1 = (DateSerTest) ser.Deserialize(fs); 
       Console.WriteLine(d1.Date); // yields: 8/18/2009 12:00:00 AM 
       Console.WriteLine(d1.Date.Kind); // yields: Unspecified 
      } 

      // in.xml: 
      // <DateSerTest> 
      //  <Date>2009-08-18Z</Date> 
      // </DateSerTest> 

      using (FileStream fs = new FileStream("in.xml", FileMode.Open)) { 
       DateSerTest d1 = (DateSerTest) ser.Deserialize(fs); 
       Console.WriteLine(d1.Date); // yields: 8/17/2009 8:00:00 PM 
       Console.WriteLine(d1.Date.Kind); // yields: Local 
       using (FileStream fs1 = new FileStream("out2.xml", FileMode.Create)) { 
        ser.Serialize(fs1, d1); 

        // out2.xml will contain: 
        // <Date>2009-08-17</Date> 
       } 
      } 
      Console.ReadKey(); 
     } 
    } 
} 

Así que para los elementos XSD definidos como "fecha" en lugar de "fecha y hora", la fecha no se serializa como UTC. Esto es un problema, porque si deserializo este XML, la fecha resultante será de Tipo no especificado y cualquier conversión a UTC (que de hecho debería ser no operativa porque el UTC-ness de la fecha debería haberse conservado durante el viaje de ida y vuelta), cambiará al menos la hora del día, con un 50% de posibilidades de que la fecha sea ayer, dependiendo de si está al este o al oeste de Greenwich.

En caso de no conseguir la fecha escrita como:

<Date>2009-08-18Z</Date> 

?

De hecho, si deserializo un documento que contiene lo anterior, obtengo un DateTime que ya se ha convertido a la hora local (estoy en Nueva York, así que es el 17 de agosto a las 20:00) y si serializo ese objeto inmediatamente de nuevo a XML, me sale:

<Date>2009-08-17</Date> 

Así, UTC se convirtió en local en el camino, y la parte de hora de ese local se redujo a la salida, lo que hará que sea especificado en el camino de regreso de nuevo . Perdimos todo conocimiento de la especificación de fecha UTC original del 18 de agosto.

Aquí es lo que dice el W3C sobre xsd: Fecha:

[Definición:] El · espacio de valores · de fecha consiste en intervalos superiores a abrir de exactamente un día en la longitud de los líneas de tiempo de dateTime, comenzando en el momento inicial de cada día (en por zona horaria), es decir, '00: 00: 00 ', hasta pero sin incluir '24: 00: 00' (que es idéntico a '00: 00 : 00 'del próximo día ). Para los valores que no están en hora, los intervalos de apertura superior de cubren de manera disjunta la línea de tiempo no sincronizada, una por día. Para los valores timezoned, los intervalos comienzan en cada minuto y por lo tanto, se superponen.

El problema fundamental es que si hago lo siguiente:

  1. Construct (o no recibir) un valor DateTime UTC.
  2. Serializar a XML con un esquema que define ese campo como xsd: date
  3. Deserializar ese XML a un DateTime.
  4. Convierta el DateTime a UTC (que no debería tener ningún efecto ya que el "viaje de ida y vuelta" debería haber preservado esto).

O lo siguiente:

  1. deserializar un documento XML que contiene un xsd UTC: (. Ej 2009-08-18Z) Fecha de objeto.
  2. Serializarlo de nuevo en un nuevo documento XML sin tocarlo.

Cualquiera de estos procedimientos me debe recibir la misma fecha que puse en.

Solución

La única manera de que pueda ver lo que va a conseguir el ida y vuelta comportamiento espero es aplicar la propiedad Fecha de la siguiente manera, en el supuesto de que todos los xsd: representan elementos de fecha UTC:

[XmlElement(DataType = "date")] 
public DateTime Date { 
    get { return _dt; } 
    set { _dt = value.Kind == DateTimeKind.Unspecified ? 
        DateTime.SpecifyKind(value, DateTimeKind.Utc) : 
        value.ToUniversalTime(); } 
} 
+0

Pequeña cosa: El Serializador XML no usa '[Serializable]'. –

+0

Ok, bueno con su modificación, ya no veo la pregunta. ¿Cuál es la pregunta? – Cheeso

+1

Todavía estoy tratando de entenderlo, pero supongo que mi pregunta en este momento es: ¿Tengo razón al concluir que la serialización de ida y vuelta XML entre DateTime y xsd: date está rota? – lesscode

Respuesta

8

yo abrimos un tema Connect y consiguieron esta detrás de Microsoft, lo que confirma mis temores:

Tenemos diferentes comportamientos para Fecha manejo, tiempo y DateTime valores. Para los valores de DateTime, si XmlDateTimeSerializationMode no es local, la información sobre el tipo (UTC, local o no especificado) es preservado. Esto también es cierto mientras se deserializa . Sin embargo, para Fecha y Hora, siempre se serializan con el mismo formato: (aaaa-MM-dd para Fecha y HH: mm: ss.fffffff.zzzzzz para Hora). Por lo tanto, la información sobre el tipo se pierde al serializar y deserializar . Estamos abriendo un error de documentación de nuestro lado en orden para mejorar la documentación acerca de esto.

+1

¿Alguien sabe si esto alguna vez se resolvió? –

+2

Depende de lo que quiere decir con "resuelto" ... La declaración de Microsoft me indica que la API no hace lo correcto, pero la "resolverá" al documentar la anomalía. – lesscode

+0

Entonces, ¿puede especificar que la propiedad se serialice como DateTime para resolver el problema, entonces? – xr280xr

1

No veo el problema que describes.

Su código de ejemplo no se deserializa. Agregué un código para deserializar, y lo hago de ida y vuelta como era de esperar. No vi la fecha retroceder un día o adelantar un día.

Me di cuenta que la porción de tiempo del campo d.Date se quita para la serialización, independientemente de DateTimeKind. Esto me parece correcto. No tiene sentido para mí, de forma intuitiva, serializar una zona horaria con una "Fecha", o convertir a UTC. Sería sorprendente para mí que si tuviera un valor de Fecha del 8-18-2009, y cuando se serializara, apareciera como 8-19-2009Z. Así que creo que la forma en que funciona ahora parece correcta.

  • DateTime que se serializan como xsd: dateTime include zone info.
  • DateTimes serializado como xsd: date, do not. También esperaría que con [XmlElement(DateType="time")] (xsd: time), la zona horaria no se incluiría. No probé esto.

Así que el problema es que este comportamiento, que tiene sentido para mí, no está documentado claramente, especialmente con los cambios introducidos para el viaje de ida y vuelta. El hecho de que DataType = "date" y DataType = "time" no se conviertan a UTC para la serialización, debe indicarse claramente.

que escribió:

y cualquier conversión a UTC va a cambiar, al menos, la hora del día,

Pero no vi esto en absoluto. Cuando convierto una hora que es DateTimeKind.Unspecified, a Utc, no cambia la hora del día. Simplemente cambia el tipo.

class Program 
{ 
    static System.IO.MemoryStream StringToMemoryStream(string s) 
    { 
     byte[] a = System.Text.Encoding.ASCII.GetBytes(s); 
     return new System.IO.MemoryStream(a); 
    } 


    static void Main(string[] args) 
    { 
     var settings = new System.Xml.XmlWriterSettings { OmitXmlDeclaration = true, Indent= true }; 
     XmlSerializerNamespaces _ns = new XmlSerializerNamespaces(); 
     _ns.Add("", ""); 

     Console.WriteLine("\nDate Serialization testing..."); 

     for (int m=0; m < 2; m++) 
     { 
      var builder = new System.Text.StringBuilder(); 

      DateTime t = DateTime.Parse("2009-08-18T22:31:24.0019-04:00"); 
      DateSerTest d = new DateSerTest 
       { 
        Date = t, 
        DateTime = t 
       }; 

      Console.WriteLine("\nRound {0}", m+1); 
      if (m==1) 
       d.Date = d.Date.ToUniversalTime(); 

      Console.WriteLine("d.Date {2,-11} = {0} Kind({1})", d.Date.ToString("u"), d.Date.Kind.ToString(), 
           (m==1) ? "(converted)" : "(original)"); 
      Console.WriteLine("d.DateTime   = {0} Kind({1})", d.DateTime.ToString("u"), d.DateTime.Kind.ToString()); 

      XmlSerializer ser = new XmlSerializer(typeof(DateSerTest)); 

      Console.WriteLine("\nSerialize d"); 
      using (var writer = System.Xml.XmlWriter.Create(builder, settings)) 
      { 
       ser.Serialize(writer, d, _ns); 
      } 
      string xml = builder.ToString(); 
      Console.WriteLine("{0}", xml); 

      Console.WriteLine("\nDeserialize into d2"); 
      System.IO.MemoryStream ms = StringToMemoryStream(xml); 
      DateSerTest d2= (DateSerTest) ser.Deserialize(ms); 

      Console.WriteLine("d2.Date = {0} Kind({1})", d2.Date.ToString("u"), d2.Date.Kind.ToString()); 
      Console.WriteLine("d2.DateTime= {0} Kind({1})", d2.DateTime.ToString("u"), d2.DateTime.Kind.ToString()); 

      Console.WriteLine("\nAfter SpecifyKind"); 
      d2.Date = DateTime.SpecifyKind(d2.Date, DateTimeKind.Utc); 
      Console.WriteLine("d2.Date = {0} Kind({1})", d2.Date.ToString("u"), d2.Date.Kind.ToString()); 

      Console.WriteLine("\nRe-Serialize d2"); 
      builder = new System.Text.StringBuilder(); 
      using (var writer = System.Xml.XmlWriter.Create(builder, settings)) 
      { 
       ser.Serialize(writer, d2, _ns); 
      } 
      xml = builder.ToString(); 
      Console.WriteLine("{0}", xml); 

     } 
    } 
} 

Los resultados:

 

    Date Serialization testing... 

    Round 1 
    d.Date (original) = 2009-08-18 22:31:24Z Kind(Local) 
    d.DateTime   = 2009-08-18 22:31:24Z Kind(Local) 

    Serialize d 
    <DateSerTest> 
     <Date>2009-08-18</Date> 
     <DateTime>2009-08-18T22:31:24.0019-04:00</DateTime> 
    </DateSerTest> 

    Deserialize into d2 
    d2.Date = 2009-08-18 00:00:00Z Kind(Unspecified) 
    d2.DateTime= 2009-08-18 22:31:24Z Kind(Local) 

    After SpecifyKind 
    d2.Date = 2009-08-18 00:00:00Z Kind(Utc) 

    Re-Serialize d2 
    <DateSerTest> 
     <Date>2009-08-18</Date> 
     <DateTime>2009-08-18T22:31:24.0019-04:00</DateTime> 
    </DateSerTest> 

    Round 2 
    d.Date (converted) = 2009-08-19 02:31:24Z Kind(Utc) 
    d.DateTime   = 2009-08-18 22:31:24Z Kind(Local) 

    Serialize d 
    <DateSerTest> 
     <Date>2009-08-19</Date> 
     <DateTime>2009-08-18T22:31:24.0019-04:00</DateTime> 
    </DateSerTest> 

    Deserialize into d2 
    d2.Date = 2009-08-19 00:00:00Z Kind(Unspecified) 
    d2.DateTime= 2009-08-18 22:31:24Z Kind(Local) 

    After SpecifyKind 
    d2.Date = 2009-08-19 00:00:00Z Kind(Utc) 

    Re-Serialize d2 
    <DateSerTest> 
     <Date>2009-08-19</Date> 
     <DateTime>2009-08-18T22:31:24.0019-04:00</DateTime> 
    </DateSerTest> 
+0

Tiene razón en que el problema radica en la serialización de las fechas UTC, es por eso que resumí mi muestra solo para eso. Ahora agregué el resto. – lesscode

+0

DateTime no especificados en efecto, se convierten como si fueran locales, de acuerdo con MSDN: DateTime.ToUniversalTime Método ... no especificado El objeto DateTime actual se supone que es una hora local, y la conversión se realiza como si Kind fuera Local. – lesscode

+0

SpecifyKind, por supuesto, no cambia la hora o la fecha. – lesscode