Tuesday, October 23, 2012

Avoid XmlConvert.ToString(DateTime)

XmlConvert is the utility class that controls fundamental .Net data types being converted to/from their XML representations. For dates, the DateTimeOffset overloads are less ambigious than the DateTime ones, and as a result the latter have been depricated. But you'd still expect it to basically work for the fundamental scenarios.

Nope. For DateTimeKind=UTC dates, it's totally broken.

Ttry this (in Linqpad):



	var localTime = DateTime.Now;
	var utcTime = localTime.ToUniversalTime();
	
	localTime.Dump("Local Time");
	utcTime.Dump("UTC time");
	utcTime.Kind.Dump("UTC Kind");
	utcTime.ToString("yyyy-MM-ddTHH:mm:ss K").Dump("Expected XML");
	XmlConvert.ToString(utcTime).Dump("Actual XML from XML Convert");

Gives:

Local Time 23/10/2012 11:11:11 AM
UTC time 23/10/2012 3:11:11 AM
UTC Kind UTC
Expected XML 2012-10-23T03:11:11 Z
Actual XML from XML Convert 2012-10-23T03:11:11.0773940+08:00


You'll notice immediately (because of the highlighting), that it's done something really really dumb. It's made out that the UTC time is actually a local time in my local timezone offset (+8). It's screwed up the XML representation, and as a result changed the actual time value being passed, even though it knows (based on DateTime.Kind==DateTimeKind.Utc, that the ToUniversalTime() method always gives you) that it's a UTC date.

This is because internally that method does this:

[Obsolete("Use XmlConvert.ToString() that takes in XmlDateTimeSerializationMode")]
public static string ToString(DateTime value)
{
    return XmlConvert.ToString(value"yyyy-MM-ddTHH:mm:ss.fffffffzzzzzz");
}

...and those z's add the local timezone offset from your PC. This is wrong for a DateTime with Kind=UTC. A better implementation would be as per my example above, ie:

    return XmlConvert.ToString(value, "yyyy-MM-ddTHH:mm:ss.fffffffK");

The K specifier is not so dumb, and adds the time zone to local time datetime instances, and adds the Z to UTC instances. Or alternatively an if{}else{} based on value.Kind==DateTimeKind.UTC.

Anything but what it does right now.

Why do I care? Well WCF generates webservices proxies using DateTime, not DateTimeOffset, so I want to make damn sure it's not making the same mistake.

Popular Posts