I just noticed what seems like a ridiculous flaw with DateTime comparison.
DateTime d = DateTime.Now;
DateTime dUtc = d.ToUniversalTime();
d == dUtc; // false
d.Equals(dUtc); //false
DateTime.Compare(d, dUtc) == 0; // false
It appears that all comparison operations on DateTimes fail to do any type of smart conversion if one is DateTimeKind.Local and one is DateTimeKind.UTC. Is the a better way to reliably compare DateTimes aside from always converting both involved in the comparison to utc time?
When you call
.Equalor.Compare, internally the value.InternalTicksis compared, which is aulongwithout its first two bits. This field is unequal, because it has been adjusted a couple of hours to represent the time in the universal time: when you callToUniversalTime(), it adjusts the time with an offset of the current system’s local timezone settings.You should see it this way: the DateTime object represents a time in an unnamed timezone, but not a universal time plus timezone. The timezone is either Local (the timezone of your system) or UTC. You might consider this a lack of the DateTime class, but historically it has been implemented as "number of ticks since 1970" and doesn’t contain timezone info.
When converting to another timezone, the time is — and should be — adjusted. This is probably why Microsoft chose to use a method as opposed to a property, to emphasize that an action is taken when converting to UTC.
Originally I wrote here that the structs are compared and the flag for
System.DateTime.Kindis different. This is not true: it is the amount of ticks that differs:To safely compare two dates, you could convert them to the same kind. If you convert any date to universal time before comparing you’ll get the results you’re after:
Converting to UTC time without changing the local time
Instead of converting to UTC (and in the process leaving the time the same, but the number of ticks different), you can also overwrite the
DateTimeKindand set it to UTC (which changes the time, because it is now in UTC, but it compares as equal, as the number of ticks is equal).I guess that
DateTimeis one of those rare types that can be bitwise unequal, but compare as equal, or can be bitwise equal (the time part) and compare unequal.Changes in .NET 6
In .NET 6.0, we now have
TimeOnlyandDateOnly. You can use these to store "just the time of day", of "just the date of the year". Combine these in a struct and you’ll have a Date & Time struct without the historical nuisances of the originalDateTime.Alternatives
Working properly with
DateTime,TimeZoneInfo, leap seconds, calendars, shifting timezones, durations etc is hard in .NET. I personally preferNodaTimeby Jon Skeet, which gives control back to the programmer in a meaningful an unambiguous way.Often, when you’re not interested in the timezones per se, but just the offsets, you can get by with
DateTimeOffset.This insightful post by Jon Skeet explains in great depth the troubles a programmer can face when trying to circumvent all DateTime issues when just storing everything in UTC.
Background info from the source
If you check the
DateTimestruct in the .NET source, you’ll find a note that explains how originally (in .NET 1.0) theDateTimewas just the number of ticks, but that later they added the ability to store whether it was Universal or Local time. If you serialize, however, this info is lost.This is the note in the source: