2009-08-19 14 views
12

OK, czego tu brakuje? MSDN mówi, co następuje w odniesieniu do DateTimeSerializationMode:Roundtrip XML Serialization of DateTime i xsd: date?

W wersjach 2.0 i późniejsze Framework z tej właściwości ustawiony na obiektów RoundtripDateTime są badane celu ustalenia, czy są one w lokalnym, lub UTC w nieokreślonym czasie strefa i są serializowane w taki sposób, aby ta informacja została zachowana. Jest to domyślne zachowanie i jest zalecane dla wszystkich nowych aplikacji , które nie komunikują się ze starszymi wersjami architektury .

Jednakże:

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

Więc dla elementów XSD definiuje się jako "dacie" zamiast "dateTime", data nie jest szeregowane jako UTC. To jest problem, ponieważ jeśli deserializuję ten XML, to wynikowy termin będzie Kind Unspecified i dowolna konwersja na UTC (która w rzeczywistości powinna być no-op, ponieważ data UTC daty powinna zostać zachowana podczas roundtrip), zmieni co najmniej porę dnia, z 50% szansą na wczorajszą randkę, w zależności od tego, czy jesteś na wschód czy na zachód od Greenwich.

Jeżeli nie dacie się zapisać jako:

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

?

Rzeczywiście, jeśli deserializuję dokument, który zawiera powyższe, otrzymuję DateTime, który został już skonwertowany na czas lokalny (jestem w Nowym Jorku, to jest 17 sierpnia 20:00), i jeśli natychmiast serializuje ten obiekt z powrotem do XML, otrzymuję:

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

Więc UTC przekształcono lokalny po drodze, a część czasu, że lokalne spadła na drodze, która pozwoli Nieokreślony w drodze powrotnej ponownie . Straciliśmy całą wiedzę na temat pierwotnej specyfikacji daty UTC z 18 sierpnia.

Oto co W3C mówi o xsd: Data:

[Definicja:] · przestrzeni wartość · od data składa się z najlepszych otwartych przedziałach dokładnie jeden dzień w długości na terminów z dateTime, rozpoczynając od początkowej chwili każdego dnia (w każdej strefie czasowej), tj. '00: 00: 00 ', do , ale bez "24: 00: 00" (która jest identyczna z '00: 00 : 00 'następnego dnia ). W przypadku wartości niedoświetlonych, przedziały od góry do góry rozłącznie pokrywają się z niezwiązaną linią czasu, jedną na dzień . W przypadku wartości ograniczonych czasowo interwały rozpoczynają się od każdej minuty i dlatego nakładają się na siebie.

Podstawowym problemem jest to, że jeśli I wykonaj następujące czynności:

  1. Construct (lub inaczej odbierać) wartość UTC DateTime.
  2. Serializować do XML z schematu definiującego to pole jako xsd: data
  3. deserializowania że XML z powrotem do DateTime.
  4. Konwersja DateTime na UTC (która nie powinna wywoływać żadnego efektu, ponieważ "roundtrip" powinien to zachować).

Albo co następuje:

  1. deserializowania dokument XML zawierający UTC xsd: (np. 2009-08-18Z) Data przedmiot.
  2. Serialize to z powrotem do nowego dokumentu XML bez dotykania go.

Każda z tych procedur powinno dać mi tę samą datę I umieścić w.

Obejście

jedyny sposób widzę tak daleko, aby uzyskać obie zachowanie Spodziewam jest implementować właściwość Date w następujący sposób, przy założeniu, że wszystkie elementy xsd: date reprezentują UTC:

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

Mała rzecz: Serializator XML nie używa "[Serializable]". –

+0

Dobrze, z twoją modyfikacją, nie widzę już pytania. Jakie jest pytanie? – Cheeso

+1

Nadal próbuję się o tym przekonać, ale domyślam się, że moje pytanie w tym miejscu brzmi: Czy mam rację, twierdząc, że serialowanie w obiegu XML między DateTime i xsd: date jest zepsute? – lesscode

Odpowiedz

8

otworzyłem Połącz problem i mam to z powrotem od firmy Microsoft, potwierdzający moje obawy:

Mamy różne zachowania dla obsługi daty, godziny i DateTime wartości. W przypadku wartości DateTime, jeśli XmlDateTimeSerializationMode nie jest Lokalna, informacja o rodzaju (UTC, lokalna lub nieokreślona) jest zachowana. Jest to również prawdą podczas przekształcania deserialnego na . Jednak w przypadku daty i czasu są zawsze serializowane na numer w tym samym formacie: (rrrr-MM-dd dla Data i HH: mm: ss.fffffff.zzzzzz dla Czas). Tak więc informacje o rodzaju są tracone podczas przekształcania do postaci szeregowej i odserializowania . Otwieramy błąd dokumentacji po naszej stronie w celu w celu udoskonalenia dokumentacji na temat tego.

+1

ktoś wie, czy to kiedykolwiek rozwiązano? –

+2

Zależy od tego, co rozumiesz przez "rozwiązany" ... Oświadczenie Microsoftu wskazuje mi, że API nie postępuje właściwie, ale "rozwiązuje" je, dokumentując anomalię. – lesscode

+0

Czy możesz więc określić właściwość serializowaną jako DateTime w celu rozwiązania problemu? – xr280xr

1

Nie widzę problemu, który opisałeś.

Twój przykładowy kod nie jest deserializowany. Dodałem kod do deserializacji, i to w obie strony, jak się spodziewałem. Nie widziałem, aby data cofnęła się o jeden dzień, lub o dzień.

Zauważyłem, że, że część czasu d.Date pole jest pozbawiony do serializacji, niezależnie od DateTimeKind. Wydaje mi się to poprawne. Nie ma dla mnie sensu, intuicyjnie, albo serializować strefy czasowej z "datą", albo konwertować na UTC. Byłoby dla mnie zaskakujące, że gdybym miał wartość daty 8-18-2009, a po serializacji pojawił się jako 8-19-2009Z. Myślę więc, że sposób, w jaki teraz działa, wydaje się poprawny.

  • Daty, które są serializowane jako xsd: dateTime zawierają informacje o strefie.
  • DateTimes serializowane jako xsd: date, do not. Spodziewam się, że z [XmlElement(DateType="time")] (xsd: time) strefa czasowa nie zostanie uwzględniona. Nie testowałem tego.

Problem, jaki widzę, jest taki, który ma dla mnie sens, nie jest jasno udokumentowany, szczególnie w przypadku zmian wprowadzonych w czasie obiegu. Fakt, że DataType = "date" i DataType = "time" nie konwertuje na UTC w celu serializacji, powinien być jasno określony.

napisałeś:

i każda konwersja do UTC zmieni przynajmniej godzinę,

Ale nie widzę tego w ogóle. Kiedy konwertuję czas określony jako DateTimeKind.Unspecified, na Utc, nie zmienia to pory dnia. To po prostu zmienia rodzaj.

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

     } 
    } 
} 

Wyniki:

 

    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

Masz rację, że problem polega na serializacji dat UTC - dlatego skróciłem moją próbkę do tego właśnie - teraz dodałem resztę. – lesscode

+0

Nieokreślona DateTimes rzeczywiście zamieniony tak jakby były one lokalne, zgodnie z MSDN: DateTime.ToUniversalTime Metoda ... Nieokreślony Obecny obiekt DateTime zakłada się czas lokalny, a konwersja jest wykonywana jako jeśli Kind był Local. – lesscode

+0

SpecifyKind, oczywiście, nie zmienia godziny ani daty. – lesscode

Powiązane problemy