Let’s say you have some code that compares two DateTimes to determine access of something important that can expire…

DateTime expirationTime = new DateTime(2018, 1, 1, 0, 30, 0, DateTimeKind.Utc); DateTime inputTime = new DateTime(2018, 1, 1, 0, 31, 0, DateTimeKind.Utc); if (inputTime > expirationTime) Console.WriteLine("Expired"); else Console.WriteLine("Not expired");

The expected output is, of course, “Expired”. We all know that 31 > 30.

Now, what do you expect to output if the input time was the same value but represented in a different time zone?

DateTime expirationTime = new DateTime(2018, 1, 1, 0, 30, 0, DateTimeKind.Utc); DateTime inputTimeLocal = new DateTime(2018, 1, 1, 0, 31, 0, DateTimeKind.Utc).ToLocalTime(); if (inputTimeLocal > expirationTime) Console.WriteLine("Expired"); else Console.WriteLine("Not expired");

Both ‘inputTime’ and ‘inputTimeLocal’ represent times at the same moment, but the actual values (day/hour) have changed. Should the program compare the “literal” times or “actual”/UTC times? Well, we haven’t changed the DateTime value so the comparison should still come out true and the output “Expired”, right?

Nope.

Not if ‘local time’ refers to anywhere west of the UTC +0 time zone. For that, even though the actual time has not changed, the result is now magically “Not expired”.

If local time is New York (which is UTC -5) then this code will claim the input time is “Not expired” for an extra 5 hours! And if local time is Japan (which is UTC +9) then this code will claim the input time is “Expired” 9 hours early! This could be a very costly mistake in some situations and is a very easy mistake to make.

Comparing DateTimes do NOT take into consideration what type each values are in and if they are different. Instead, the comparison will merely compare the RAW numeric representations and ignore the type. A good way to stay safe is by making sure sure all values are converted to UTC first before any comparisons.

Here is a full example demonstrating this:

DateTime timeA = DateTime.Now; DateTime timeAUtc = timeA.ToUniversalTime(); DateTime timeB = (timeA + TimeSpan.FromHours(1)); DateTime timeBUtc = timeB.ToUniversalTime(); Console.Write("Local timezone: "); Console.Write($"{TimeZoneInfo.Local.DisplayName} "); Console.Write($"({TimeZoneInfo.Local.BaseUtcOffset}, "); Console.WriteLine($"DaylightSavings={TimeZoneInfo.Local.IsDaylightSavingTime(DateTime.Now)})"); Console.WriteLine(); Console.WriteLine($"timeA = {timeA}"); Console.WriteLine($"timeAUtc = {timeAUtc}"); Console.WriteLine($"timeB = {timeB}"); Console.WriteLine($"timeBUtc = {timeBUtc}"); Console.WriteLine(); Console.WriteLine($"timeB - timeA = {timeB - timeA}"); Console.WriteLine($"timeB - timeAUtc = {timeB - timeAUtc}"); Console.WriteLine($"timeBUtc - timeA = {timeBUtc - timeA}"); Console.WriteLine($"timeBUtc - timeAUtc = {timeBUtc - timeAUtc}"); /* My output: Local timezone: (UTC-06:00) Central Time (US & Canada) (-06:00:00, DaylightSavings=True) timeA = 5/6/2018 8:21:57 AM timeAUtc = 5/6/2018 1:21:57 PM timeB = 5/6/2018 9:21:57 AM timeBUtc = 5/6/2018 2:21:57 PM timeB - timeA = 01:00:00 timeB - timeAUtc = -04:00:00 timeBUtc - timeA = 06:00:00 timeBUtc - timeAUtc = 01:00:00 */