The .NET DateTime type has lots of issues, as has been noted many times. However, it is appealing due to its (seeming) simplicity. An important task when working with timestamps is comparing them.
It seems that when you compare two timestamps t1 and t2, you would expect that
t1 == t2 is valid only if both timestamps represent the same time of day, at the same day, referring to the same time zone. For example, the timestamps
2014-10-03T14:00:00Z (which represents a UTC timestamp) and
2014-10-03T14:00+02:00 designate two different points in time. The second timestamp is representing a point in time in a time zone that has an offset of two hours to UTC (a.k.a. Zulu time, thus the Z). 14:00 in this time zone is two hours earlier than 14:00 in UTC.
Let’s consider an example. The United Kingdom is basically using UTC. Not considering daylight savings time, there is a zero-offset between local time in the UK and UTC. The time zone used in Germany has an offset of +01:00 to UTC. This means that in Germany, noon happens one hour earlier than in the UK. During daylight savings time, Germany has a two-hour offset to UTC. So, when you have a local time stamp in Germany’s time zone, it has a one- or two-hour offset to UTC. 14:00 in Germany’s time zone is not equal to 14:00 UTC.
But is this true in .NET’s DateTime?
See what happens when you compare DateTime objects:
var dt_Unspec = new DateTime(2014, 10, 3, 14, 00, 00);
var dt_Local = new DateTime(2014, 10, 3, 14, 00, 00, DateTimeKind.Local);
var dt_Utc = new DateTime(2014, 10, 3, 14, 00, 00, DateTimeKind.Utc);
Console.WriteLine(dt_Unspec == dt_Local);
Console.WriteLine(dt_Unspec == dt_Utc);
Console.WriteLine(dt_Local == dt_Utc);
The output of this code is:
According to .NET, all these DateTime objects refer to the same point in time!
This is, to say the least, somewhat unexpected. However, maybe there is some deeper reason why .NET yields that result?
First, there is this somewhat ominous variable
dt_Unspec, which is created withoug giving an explicit time zone. The result is a timestamp for which the time zone is not specified. There are many situations where this makes sense in software. Sometimes, you just don’t care about the time zone, or you are not sure yet which time zone should be applied. Therefore, you can leave that information open and convert the timestamp later on. If you convert it into UTC, .NET assumes it was LocalTime and vice versa. It’s like a “quantum” time zone.
When comparing the unspecified time zone with a specified one, it makes sense to assume that both timestamps are in the same time zone. After all, the timestamp with the unspecified time zone could be in the same time zone, which would be made explicit after a conversion. From this point of view, the first two comparisons should yield
But if the first two comparisons yield
True, it is a necesssary consequence that the third comparison also yields
True. After all, what would you expect from the comparison
a == b when you already know that
a == c and
b == c? If both
c, you would expect that also
b, wouldn’t you?
On the other hand, this has interesting consequences. If you convert a timestamp into a different time zone and compare the new value with the original one, they will be considered unequal:
var d1 = DateTime.Now;
var d2 = d1.ToUniversalTime();
Console.WriteLine(d1 == d2);
False, as we are expecting by now. (Note that my computer is running in a time zone that has a non-zero offset to UTC. You might get different results if you are running in the UTC time zone itself or a time zone with a zero offset.)
When comparing timestamps, .NET is ignoring the time zone. And from what we have seen here, this seems logical within this framework, since it is legal to use a “unspecified” time zone in .NET.
I am quite sure that I am not the first to note this quirk of DateTime. In defense of Microsoft, coming up with a simple-to-use but universally correct date/time library is not an easy job. For business applications, DateTime might be well-suited, if you are careful. And then there are alternatives like DateTimeOffset (which I should investigate further). Java has its own problems with its date/time structures in the JDK. And of course, there is always Joda-Time and its relative Noda Time, which are better suited for complex tasks.