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

时间:2009-08-19 02:14:55

标签: .net datetime xml-serialization

好的,我在这里错过了什么? MSDN对DateTimeSerializationMode说明如下:

  

在.Net的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,结果日期将是未指定的类型,以及任何到UTC的转换(实际上应该是无操作,因为在往返期间应该保留日期的UTC-ness ),至少会改变一天中的时间,有可能在昨天做出约会,这取决于你是在格林威治的东边还是西边。

日期不应写为:

  <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的时间表,从...开始   每天的开始时刻(in   每个时区),即'00:00:00',最多   但不包括'24:00:00'(即   与下一个的'00:00:00'相同   天)。对于非时间化的值,   顶部开放的间隔不相称地覆盖   非时间时间线,每个时间线一个   天。对于timezoned值,   间隔从每分钟开始   因此重叠。

根本问题在于,如果我执行以下操作:

  1. 构建(或以其他方式接收)UTC DateTime值。
  2. 使用将该字段定义为xsd:date
  3. 的模式序列化为XML
  4. 将该XML反序列化为DateTime。
  5. 将DateTime转换为UTC(由于“往返”应保留此值,因此无效)。
  6. 或以下内容:

    1. 反序列化包含UTC xsd:date对象的XML文档(例如,2009-08-18Z)。
    2. 将其序列化为新的XML文档而不触及它。
    3. 这些程序中的任何一个都应该让我与我投入的日期相同。

      解决方法

      到目前为止我能看到的唯一方法是获得往返行为,我希望实现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(); }
      }
      

2 个答案:

答案 0 :(得分:9)

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

  

我们有不同的行为   处理日期,时间和日期时间   值。对于DateTime值,如果   XmlDateTimeSerializationMode不是   本地有关该种类的信息   (UTC,Local或Unspecified)是   保存。这也是如此   反序列化。但是,对于日期和   时间,它们总是被序列化   格式相同:( yyyy-MM-dd for   日期和HH:mm:ss.fffffff.zzzzzz for   时间)。关于善良的信息   在序列化和丢失时丢失了   反序列化。我们正在开放   我们方面的文档错误   改进文档   此

答案 1 :(得分:0)

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

您的示例代码未反序列化。我添加了一些反序列化的代码,并且正如我所期望的那样往返。我没有看到日期向后移动一天,或向前移动一天。

我注意到无论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>