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

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

在.Net Framework的2.0及更高版本中,将此属性设置为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();
        }
    }
}

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

不应该把日期写成:

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

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

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

因此,UTC在途中被转换为Local,并且该Local的时间部分在出路时下降,这将使其在返回的路上处于未指定状态。 我们已经失去了8月18日原始UTC日期规范的所有知识。

以下是W3C关于xsd:date的说法:

[定义:]日期的值空间由dateTime的时间轴上的开始时间间隔(在每个时区的开始时刻开始)(即每个时区中)组成,即'00:00:00 ',但不包括'24:00:00'(与第二天的'00:00:00'相同)。 对于非时间分辨值,顶部开放时间间隔不包括非时间分布时间线,每天一个。 对于分时值,间隔从每分钟开始并因此重叠。

根本的问题是,如果我做到以下几点:

  • 构建(或以其他方式接收)UTC日期时间值。
  • 使用将该字段定义为xsd:date的模式序列化为XML
  • 将该XML反序列化回到DateTime。
  • 将DateTime转换为UTC(由于“往返”应该保留这一点,所以不应该有效)。
  • 或者以下:

  • 反序列化包含UTC xsd:date对象的XML文档(例如2009-08-18Z)。
  • 将其序列化回新的XML文档而不用触摸它。
  • 这些程序中的任何一个都应该让我和我放入的日期相同。

    解决方法

    我期望能够看到的往返行为的唯一方法是按如下方式实现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(); }
    }
    

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

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


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

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

    我注意到,无论DateTimeKind如何,d.Date字段的时间部分都被去除了序列化。 这对我来说似乎是正确的。 直观地说,对我来说,通过“Date”序列化时区或转换为UTC是毫无意义的。 如果我的日期值为8-18-2009,并且在序列化时显示为8-19-2009Z,那将是令人惊讶的。 所以我认为它现在的工作方式似乎是正确的。

  • 序列化为xsd:dateTime的DateTime包含区域信息。
  • DateTimes序列化为xsd:date,不。 我也希望用[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>
    
    链接地址: http://www.djcxy.com/p/8129.html

    上一篇: Roundtrip XML Serialization of DateTime and xsd:date?

    下一篇: How Do I Serialize DateTime Objects in .NET in a Standards Compliant Way