OK, what am I missing here? MSDN says the following with regard to 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.
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.
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:
<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.
Here’s what the W3C says about 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.
The fundamental problem is that if I do the following:
- Construct (or otherwise receive) a UTC DateTime value.
- Serialize to XML with a schema defining that field as xsd:date
- Deserialize that XML back to a DateTime.
- Convert the DateTime to UTC (which should have no effect since the “roundtrip” should have preserved this).
Or the following:
- Deserialize an XML document containing a UTC xsd:date object (eg. 2009-08-18Z).
- Serialize it back to a new XML document without touching it.
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:
[XmlElement(DataType = "date")]
public DateTime Date {
get { return _dt; }
set { _dt = value.Kind == DateTimeKind.Unspecified ?
DateTime.SpecifyKind(value, DateTimeKind.Utc) :
value.ToUniversalTime(); }
}
I opened a Connect issue and got this back from Microsoft, confirming my fears: