Подтвердить что ты не робот

XML-сериализация DateTime и xsd: дата?

Хорошо, что мне здесь не хватает? MSDN сообщает следующее относительно DateTimeSerializationMode:

В версиях 2.0 и более поздних версий .Net Framework, с этим свойством Объекты RoundtripDateTime проверяются определить, находятся ли они в локальный, UTC или неопределенное время зоны и сериализуются таким образом что эта информация сохраняется. Это поведение по умолчанию и рекомендуется для всех новых приложений которые не общаются со старыми версии фреймворка.

Однако:

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

Итак, для элементов XSD, определенных как "дата", а не "dateTime", дата не сериализуется как UTC. Это проблема, потому что, если я десериализую этот XML, результирующая дата будет иметь вид Unspecified и любое преобразование в UTC (что на самом деле должно быть no-op, поскольку UTC-дата даты должна была быть сохранена во время кругового путешествия), изменится, по крайней мере, на время дня, с 50% шансом сделать вчерашнюю дату, в зависимости от того, находитесь вы на востоке или западе от Гринвича.

Не следует ли писать дату:

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

?

В самом деле, если я десериализую документ, который содержит выше, я получаю DateTime, который уже был преобразован в локальное время (я в Нью-Йорке так, что 17 августа 20:00), и если я немедленно сериализую этот объект обратно к XML, я получаю:

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

Итак, UTC был преобразован в Local по пути внутрь, а временная часть этого Local сброшена на выходе, что сделает его Unspecified на обратном пути снова. Мы потеряли все знания о первоначальной спецификации даты UTC от 18 августа.

Вот что говорит W3C о xsd: date:

[Определение:] · · значение · пространства дата состоит из верхних открытых интервалов ровно один день в длину на временные рамки dateTime, начиная с начальный момент каждого дня (в каждый часовой пояс), то есть '00: 00: 00 ', до но не включая "24: 00: 00" (который идентичный "00: 00: 00" следующего день). Для неточечных значений верхние открытые интервалы непересекаются несрочная временная шкала, по одному в день. Для измеренных значений времени интервалы начинаются каждую минуту и поэтому перекрываются.

Основная проблема заключается в том, что если я делаю следующее:

  • Построить (или получить другое) значение UTC DateTime.
  • Сериализовать XML с помощью схемы, определяющей это поле как xsd: date
  • Отключить этот XML обратно к DateTime.
  • Преобразуйте DateTime в UTC (который не должен иметь эффекта, так как "roundtrip" должен был сохранить это).

Или следующее:

  • Дезертициализировать XML-документ, содержащий объект UTC xsd: date (например, 2009-08-18Z).
  • Сериализовать его обратно в новый XML-документ, не касаясь его.

Любая из этих процедур должна получить мне ту же дату, которую я вставлял.

Обход

Единственный способ, который я вижу до сих пор, чтобы получить поведение roundtrip, которое я ожидаю, заключается в том, чтобы реализовать свойство Date следующим образом, исходя из предположения, что все элементы xsd: date представляют UTC:

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

Ответ 1

Я открыл проблему с Connect и получил это от Microsoft, подтвердив свои страхи:

У нас разное поведение для обработка даты, времени и даты значения. Для значений DateTime, если XmlDateTimeSerializationMode не Локальная информация о типе (UTC, Local или Unspecified) является сохранились. Это также верно, десериализации. Однако для даты и Время, они всегда сериализуются в том же формате: (yyyy-MM-dd для Дата и ЧЧ: мм: ss.fffffff.zzzzzz для Время). Таким образом, информация о виде теряется при сериализации и десериализации. Мы открываем ошибка документации на нашей стороне для упорядочения улучшить документацию о это.

Ответ 2

Я не вижу проблемы, которую вы описали.

Ваш образец кода не десериализуется. Я добавил код для десериализации, и он округляется, как и следовало ожидать. Я не видел, чтобы дата двигалась назад в день или пересылала день.

Я заметил, что временная часть поля d.Date удаляется для сериализации независимо от DateTimeKind. Это кажется мне правильным. Мне неинтересно, чтобы либо сериализовать часовой пояс с помощью "Даты", либо конвертировать в UTC. Мне было бы удивительно, что если бы у меня было значение даты 8-18-2009, и когда оно было сериализовано, оно появилось как 8-19-2009Z. Поэтому я думаю, что теперь это работает правильно.

  • DateTime, которые сериализованы как xsd: dateTime, включают информацию о зоне.
  • DateTimes сериализуется как xsd: date, do not. Я также ожидал бы, что с [XmlElement(DateType="time")] (xsd: time) часовой пояс не будет включен. Я не проверял это.

Итак, проблема, которую я вижу в этом, это поведение, которое имеет для меня смысл, не задокументировано четко, особенно с изменениями, внесенными для округления. Тот факт, что DataType = "date" и DataType = "time" не преобразуются в UTC для сериализации, должны быть четко указаны.

Вы писали:

и любое преобразование в UTC изменит по крайней мере время суток,

Но я этого не видел. Когда я конвертирую время, указанное DateTimeKind.Unspecified, в Utc, оно не меняет время суток. Он просто меняет вид.

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

        }
    }
}

Результаты:

    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>