DateTime和xsd:date的往返XML序列化?

时间:2021-02-03 21:32:25

OK, what am I missing here? MSDN says the following with regard to DateTimeSerializationMode:

好的,我在这里错过了什么? MSDN对DateTimeSerializationMode说以下内容:

In versions 2.0 and later of the .Net Framework, with this property set to RoundtripDateTime objects are examined to determine whether they are in the local, UTC or an unspecified time zone, and are serialized in such a way that this information is preserved. This is the default behavior and is recommended for all new applications that do not communicate with older versions of the framework.

在.Net Framework的2.0版及更高版本中,将此属性设置为RoundtripDateTime,将检查对象以确定它们是在本地,UTC还是未指定的时区,并以这样的方式序列化以保留此信息。这是默认行为,建议用于所有不与旧版本框架通信的新应用程序。

However:

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

So for XSD elements defined as "date" rather than "dateTime", the date is not serialized as UTC. This is a problem, because if I deserialize this XML the resulting date will be of Kind Unspecified, and any conversion to UTC (which should in fact be a no-op because the UTC-ness of the date should have been preserved during the roundtrip), will change at least the time of day, with a 50% chance of making the date yesterday, depending on whether you're east or west of Greenwich.

因此,对于定义为“date”而不是“dateTime”的XSD元素,日期不会序列化为UTC。这是一个问题,因为如果我反序列化这个XML,结果日期将是Kind Unspecified,并且任何转换为​​UTC(实际上应该是无操作,因为在往返期间应该保留日期的UTC-ness ),至少会改变一天中的时间,有可能在昨天做出约会,这取决于你是在格林威治的东边还是西边。

Shouldn't the date get written as:

不应该将日期写成:

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

?

Indeed, if I deserialize a document that contains the above, I get a DateTime that's already been converted to Local time (I'm in New York so that's Aug 17th 20:00), and if I immediately serialize that object back to XML, I get:

实际上,如果我反序列化包含上述内容的文档,我会得到一个已经转换为本地时间的DateTime(我在纽约,所以那是8月17日20:00),如果我立即将该对象序列化回XML,我明白了:

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

So, UTC was converted to Local on the way in, and the time part of that Local dropped on the way out, which will make it Unspecified on the way back in again. We've lost all knowledge of the original UTC date specification of August 18th.

因此,UTC在进入的过程中被转换为Local,并且该Local的时间部分在出路时掉线,这将使其在返回途中未指定。我们已经失去了对8月18日原始UTC日期规范的所有了解。

Here's what the W3C says about xsd:date:

以下是W3C对xsd:date的说法:

[Definition:] The ·value space· of date consists of top-open intervals of exactly one day in length on the timelines of dateTime, beginning on the beginning moment of each day (in each timezone), i.e. '00:00:00', up to but not including '24:00:00' (which is identical with '00:00:00' of the next day). For nontimezoned values, the top-open intervals disjointly cover the nontimezoned timeline, one per day. For timezoned values, the intervals begin at every minute and therefore overlap.

[定义:]日期的值空间·包含在dateTime时间轴上长度恰好为一天的开顶间隔,从每天的开始时刻开始(在每个时区),即'00:00:00 ',但不包括'24:00:00'(与第二天的'00:00:00'相同)。对于非时间化的值,顶部开放的间隔不相交地覆盖非时间化的时间线,每天一个。对于时间值,间隔从每分钟开始,因此重叠。

The fundamental problem is that if I do the following:

根本问题是如果我做以下事情:

  1. Construct (or otherwise receive) a UTC DateTime value.
  2. 构造(或以其他方式接收)UTC DateTime值。

  3. Serialize to XML with a schema defining that field as xsd:date
  4. 使用将该字段定义为xsd:date的模式序列化为XML

  5. Deserialize that XML back to a DateTime.
  6. 将该XML反序列化为DateTime。

  7. Convert the DateTime to UTC (which should have no effect since the "roundtrip" should have preserved this).
  8. 将DateTime转换为UTC(由于“往返”应保留此值,因此应该没有效果)。

Or the following:

或者以下内容:

  1. Deserialize an XML document containing a UTC xsd:date object (eg. 2009-08-18Z).
  2. 反序列化包含UTC xsd:date对象的XML文档(例如,2009-08-18Z)。

  3. Serialize it back to a new XML document without touching it.
  4. 将其序列化回新的XML文档而不触及它。

Either of these procedures should get me the same date I put in.

这些程序中的任何一个都应该让我与我投入的日期相同。

Workaround

The only way I can see so far to get the roundtrip behaviour I expect is to implement the Date property as follows, on the assumption that all xsd:date elements represent UTC:

到目前为止我能看到的唯一方法是获得我期望的往返行为,假设所有xsd:date元素都代表UTC,实现Date属性如下:

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

2 个解决方案

#1


8  

I opened a Connect issue and got this back from Microsoft, confirming my fears:

我打开了一个Connect问题并从微软那里得到了回复,证实了我的担忧:

We have different behaviors for handling Date, Time and DateTime values. For DateTime values, if XmlDateTimeSerializationMode is not Local the information about the kind (UTC, Local or Unspecified) is preserved. This is also true while deserializing. However, for Date and Time, they are always serialized out with the same format: (yyyy-MM-dd for Date and HH:mm:ss.fffffff.zzzzzz for Time). So the information about kind is lost on serializing and deserializing. We are opening a documentation bug on our side in order to improve the documentation about this.

我们在处理Date,Time和DateTime值时有不同的行为。对于DateTime值,如果XmlDateTimeSerializationMode不是Local,则保留有关类型(UTC,Local或Unspecified)的信息。反序列化时也是如此。但是,对于日期和时间,它们总是以相同的格式序列化:(对于日期为yyyy-MM-dd,对于时间为HH:mm:ss.fffffff.zzzzzz)。因此,序列化和反序列化会丢失有关种类的信息。我们正在打开文档错误,以便改进有关此文档的文档。

#2


1  

I don't see the problem you described.

我没有看到你描述的问题。

Your sample code doesn't deserialize. I added some code to deserialize, and it roundtrips as I would expect. I did not see the date move back a day, or forward a day.

您的示例代码不反序列化。我添加了一些反序列化的代码,并按照我的预期进行了往返。我没有看到日期向后移动一天,或向前移动一天。

I did notice that the time portion of the d.Date field is stripped for serialization, regardless of the DateTimeKind. This seems correct to me. It doesn't make sense to me, intuitively, to either serialize a timezone with a "Date", or to convert to UTC. It would be surprising to me that if I had a Date value of 8-18-2009, and when serialized, it showed up as 8-19-2009Z. So I think the way it works now seems correct.

我注意到d.Date字段的时间部分被剥离以进行序列化,而不管DateTimeKind。这对我来说似乎是正确的。直观地说,使用“Date”序列化时区或转换为UTC对我没有意义。令我惊讶的是,如果我的日期值为8-18-2009,并且在序列化时,它显示为8-19-2009Z。所以我认为它现在的工作方式似乎是正确的。

  • DateTime's that are serialized as xsd:dateTime include zone info.
  • 被序列化为xsd的DateTime:dateTime包括区域信息。

  • DateTimes serialized as xsd:date, do not. I would also expect that with [XmlElement(DateType="time")] (xsd:time), the timezone would not be included. I didn't test this.
  • DateTimes序列化为xsd:date,不是。我也希望在[XmlElement(DateType =“time”)](xsd:time)时,不会包含时区。我没试过这个。

So the problem as I see it is, this behavior, which makes sense to me, isn't documented clearly, especially with the changes introduced for roundtripping. The fact that DataType="date" and DataType="time" do not convert to UTC for serialization, should be clearly stated.

所以我看到的问题是,这种对我来说有意义的行为没有明确记录,特别是对于往返的引入的变化。应清楚地说明DataType =“date”和DataType =“time”不能转换为UTC进行序列化的事实。

you wrote:

and any conversion to UTC will change at least the time of day,

任何到UTC的转换至少会在一天中的某个时间发生变化,

But I didn't see this at all. When I convert a time that is DateTimeKind.Unspecified, to Utc, it doesn't change the time of day. It just changes the kind.

但我根本没有看到这一点。当我将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);

        }
    }
}

The results:


    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>

#1


8  

I opened a Connect issue and got this back from Microsoft, confirming my fears:

我打开了一个Connect问题并从微软那里得到了回复,证实了我的担忧:

We have different behaviors for handling Date, Time and DateTime values. For DateTime values, if XmlDateTimeSerializationMode is not Local the information about the kind (UTC, Local or Unspecified) is preserved. This is also true while deserializing. However, for Date and Time, they are always serialized out with the same format: (yyyy-MM-dd for Date and HH:mm:ss.fffffff.zzzzzz for Time). So the information about kind is lost on serializing and deserializing. We are opening a documentation bug on our side in order to improve the documentation about this.

我们在处理Date,Time和DateTime值时有不同的行为。对于DateTime值,如果XmlDateTimeSerializationMode不是Local,则保留有关类型(UTC,Local或Unspecified)的信息。反序列化时也是如此。但是,对于日期和时间,它们总是以相同的格式序列化:(对于日期为yyyy-MM-dd,对于时间为HH:mm:ss.fffffff.zzzzzz)。因此,序列化和反序列化会丢失有关种类的信息。我们正在打开文档错误,以便改进有关此文档的文档。

#2


1  

I don't see the problem you described.

我没有看到你描述的问题。

Your sample code doesn't deserialize. I added some code to deserialize, and it roundtrips as I would expect. I did not see the date move back a day, or forward a day.

您的示例代码不反序列化。我添加了一些反序列化的代码,并按照我的预期进行了往返。我没有看到日期向后移动一天,或向前移动一天。

I did notice that the time portion of the d.Date field is stripped for serialization, regardless of the DateTimeKind. This seems correct to me. It doesn't make sense to me, intuitively, to either serialize a timezone with a "Date", or to convert to UTC. It would be surprising to me that if I had a Date value of 8-18-2009, and when serialized, it showed up as 8-19-2009Z. So I think the way it works now seems correct.

我注意到d.Date字段的时间部分被剥离以进行序列化,而不管DateTimeKind。这对我来说似乎是正确的。直观地说,使用“Date”序列化时区或转换为UTC对我没有意义。令我惊讶的是,如果我的日期值为8-18-2009,并且在序列化时,它显示为8-19-2009Z。所以我认为它现在的工作方式似乎是正确的。

  • DateTime's that are serialized as xsd:dateTime include zone info.
  • 被序列化为xsd的DateTime:dateTime包括区域信息。

  • DateTimes serialized as xsd:date, do not. I would also expect that with [XmlElement(DateType="time")] (xsd:time), the timezone would not be included. I didn't test this.
  • DateTimes序列化为xsd:date,不是。我也希望在[XmlElement(DateType =“time”)](xsd:time)时,不会包含时区。我没试过这个。

So the problem as I see it is, this behavior, which makes sense to me, isn't documented clearly, especially with the changes introduced for roundtripping. The fact that DataType="date" and DataType="time" do not convert to UTC for serialization, should be clearly stated.

所以我看到的问题是,这种对我来说有意义的行为没有明确记录,特别是对于往返的引入的变化。应清楚地说明DataType =“date”和DataType =“time”不能转换为UTC进行序列化的事实。

you wrote:

and any conversion to UTC will change at least the time of day,

任何到UTC的转换至少会在一天中的某个时间发生变化,

But I didn't see this at all. When I convert a time that is DateTimeKind.Unspecified, to Utc, it doesn't change the time of day. It just changes the kind.

但我根本没有看到这一点。当我将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);

        }
    }
}

The results:


    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>