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(); }
}
Ответы
Ответ 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>