2009-08-19 11 views
12

OK, cosa mi manca qui? MSDN dice quanto segue per quanto riguarda DateTimeSerializationMode:Serializzazione XML roundtrip di DateTime e xsd: date?

Nelle versioni 2.0 e successive di .NET framework , con questa proprietà impostata su oggetti RoundtripDateTime vengono esaminate per determinare se sono in locali, UTC o una zona non specificata , e sono serializzate in modo tale che questa informazione è conservata. Questo è il comportamento predefinito ed è consigliato per tutte le nuove applicazioni che non comunicano con le precedenti versioni del framework.

Tuttavia:

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

Così, per gli elementi XSD definiti come "data", piuttosto che "datetime", la data non viene serializzato come UTC. Questo è un problema, perché se deserializzo questo XML la data risultante sarà di tipo Non specificato, e qualsiasi conversione in UTC (che dovrebbe in effetti essere un no-op perché l'UTC-ness della data avrebbe dovuto essere conservato durante il roundtrip), cambierà almeno l'ora del giorno, con una probabilità del 50% di rendere la data di ieri, a seconda che tu sia a est oa ovest di Greenwich.

non dovrebbe la data di ottenere scritto come:

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

?

Infatti, se deserializzi un documento che contiene quanto sopra, ricevo un DateTime che è già stato convertito in Ora locale (sono a New York quindi è il 17 agosto 20:00) e se eseguo immediatamente la serializzazione di quell'oggetto torna a XML, ottengo:

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

Così, UTC è stato convertito in locale sul modo in, e la parte di tempo di quella locale è sceso sulla via d'uscita, che lo renderà non specificato sulla via del ritorno di nuovo . Abbiamo perso tutta la conoscenza delle specifiche originali della data UTC del 18 agosto.

Ecco cosa dice il W3C su xsd: Data:

[Definizione:] Il · spazio valore · di data è costituito da intervalli di top-open di esattamente un giorno di lunghezza sulle tempistiche di dateTime, a partire dal momento all'inizio di ogni giornata (in ogni fuso orario), vale a dire '00: 00: 00' , fino a esclusi '24: 00: 00' (che è identica a '00: 00 : 00 'del prossimo giorno ). Per i valori non temporizzati, gli intervalli di apertura massima coprono in modo disgiunto la linea temporale non temporizzata, una per il giorno . Per i valori timezoned, gli intervalli iniziano ogni minuto e quindi si sovrappongono.

Il problema fondamentale è che se faccio la seguente:

  1. Construct (o in altro modo ricevere) un valore UTC DateTime.
  2. Serializzare in XML con uno schema che definisce il campo come xsd: date
  3. Deserializzare tale XML su un DateTime.
  4. Converti il ​​DateTime in UTC (che non dovrebbe avere alcun effetto poiché il "roundtrip" avrebbe dovuto conservare questo).

o il seguente:

  1. deserializzare un documento XML contenente un xsd UTC: (es. 2009-08-18Z) data di oggetto.
  2. Serializzare di nuovo su un nuovo documento XML senza toccarlo.

Ciascuna di queste procedure me dovrebbe ottenere la stessa data ho messo in.

Soluzione

L'unico modo che posso vedere così lontano per ottenere il andata e ritorno comportamento mi aspetto è quello di implementare la proprietà data come segue, partendo dal presupposto che tutti xsd: gli elementi data rappresentano UTC:

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

Piccola cosa: il serializzatore XML non usa '[Serializable]'. –

+0

Ok, bene con la tua modifica, non vedo più la domanda. Quale è la domanda? – Cheeso

+1

Sto ancora cercando di capirlo, ma suppongo che la mia domanda a questo punto sia: Ho ragione di concludere che la serializzazione XML roundtrip tra DateTime e xsd: data è rotta? – lesscode

risposta

8

ho aperto un problema di collegamento e ottenuto questo ritorno da Microsoft, confermando le mie paure:

Abbiamo comportamenti diversi per maneggevolezza data, ora e DateTime valori. Per i valori DateTime, se XmlDateTimeSerializationMode non è locale le informazioni circa il tipo (UTC, locale o non specificato) è conservato. Questo è vero anche durante la deserializzazione di . Tuttavia, per Data e Ora , vengono sempre serializzati fuori con lo stesso formato: (aaaa-MM-gg per Data e HH: mm: ss.fffffff.zzzzzz per il tempo ). Quindi le informazioni sul tipo si perdono durante la serializzazione e la deserializzazione . Stiamo aprendo un bug documentazione dalla nostra parte, al fine di migliorare la documentazione relativa questo.

+1

qualcuno sa se questo è stato mai risolto? –

+2

Dipende cosa intendi per 'risolto' ... La dichiarazione di Microsoft mi indica che l'API non fa la cosa giusta, ma la risolveranno documentando l'anomalia. – lesscode

+0

Quindi puoi semplicemente specificare la proprietà come serializzata come DateTime per risolvere il problema? – xr280xr

1

Non vedo il problema che hai descritto.

Il codice di esempio non deserializza. Ho aggiunto un po 'di codice per deserializzare, ed è andata e ritorno come mi aspetterei. Non ho visto la data tornare indietro di un giorno o inoltrare un giorno.

Ho notato che la parte relativa al tempo del campo d.Date è stata rimossa per la serializzazione, indipendentemente dal DateTimeKind. Questo mi sembra corretto. Non ha senso per me, in modo intuitivo, o serializzare un fuso orario con una "Data", o per convertire in UTC. Sarebbe sorprendente per me che se avessi un valore di data di 8-18-2009, e se serializzato, si presentava come 8-19-2009Z. Quindi penso che il modo in cui funziona ora sembra corretto.

  • DateTime serializzate come xsd: dateTime include informazioni sulla zona.
  • DateTimes serializzato come xsd: date, do not. Mi aspetto inoltre che con [XmlElement(DateType="time")] (xsd: time), il fuso orario non venga incluso. Non ho provato questo.

Quindi il problema, a mio avviso, questo comportamento, che per me ha senso, non è documentato chiaramente, specialmente con le modifiche introdotte per il roundtripping. Il fatto che DataType = "date" e DataType = "time" non vengano convertiti in UTC per la serializzazione, dovrebbe essere chiaramente indicato.

hai scritto:

e tutta la conversione a UTC cambierà almeno l'ora del giorno,

Ma non ho visto questo a tutti. Quando converto un orario che è DateTimeKind.Unspecified, in Utc, non cambia l'ora del giorno. Cambia solo il 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); 

     } 
    } 
} 

I risultati:

 

    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

Hai ragione che il problema è relativo alla serializzazione delle date UTC - ecco perché ho ridotto il mio campione a questo - ho aggiunto il resto. – lesscode

+0

DateTimes non specificati effettivamente vengono convertiti come se fossero locali, secondo MSDN: DateTime.ToUniversalTime Metodo ... specificato L'oggetto DateTime corrente si presume essere un ora locale, e la conversione avviene come se il tipo fosse locale – lesscode

+0

SpecifyKind, ovviamente, non modifica l'ora o la data. – lesscode

Problemi correlati