Beware of Daylight Saving Time Transitions in .NET

Back in 1980, Daylight saving time (DST) started on April 27th. But calling the IsDaylightSavingTime method in System.TimeZoneInfo class for April 15, 1980 returns true. The following test fails:

  1. [TestMethod]
  2. public void DST_Started_On_April_27_1980()
  3. {
  4.     var ts = new DateTime(1980, 4, 15, 12, 0, 0);
  5.     var isDst = Utils.EasternTimeZone.IsDaylightSavingTime(ts);
  6.     Assert.IsFalse(isDst);
  7. }

Let’s do some sleuthing and get to the bottom of this.

At the time of writing this (in 2013), daylight saving time (DST) in the U.S. starts at 2:00 a.m. on the second Sunday of March and ends at 2:00 a.m. on the first Sunday of November. It has been this way since 2007. But it was not always this way. These transition rules have changed many times in the past. You can get the lowdown on the history of DST transitions on Wikipedia. Yes, that is the official site! (Just kidding.)

System.TimeZoneInfo is incorrectly using the DST transition rules that were in effect in 1987, even for the dates before 1987.

From 1987 to 2006, DST started on the first Sunday of April and ended on the last Sunday of October. But back in 1980, different rules were in effect. DST started on last Sunday of April…not the first Sunday. After digging into how System.TimeZoneInfo is handling DST transitions, I discovered that it is incorrectly using the DST transition rules that were in effect in 1987, even for the dates before 1987.

The reason for this behavior is that the default AdjustmentRules that govern DST transitions in TimeZoneInfo implementation have only two entries. One for dates after 2007 and one before 2007. The AdjustmentRule for all the dates before 2007 specifies the DST transition rules in effect from 1987 thru 2006. It is possible to create a CustomTimeZoneInfo and use that instead of the built-in “Eastern Standard Time”, but you will have to encode all the DST transition rules yourself.

IANA Time Zone database is more comprehensive. You can see an example of rules in the Olson/IANA/Tzdb database here.

NodaTime library by Jon Skeet exposes Tzdb as one of the DateTimeZoneProviders. Using NodaTime to check whether April 15, 1980 falls in DST correctly returns as false. This test passes:

  1. [TestMethod]
  2. public void Test_NodaTime_IsDaylightSavingTime()
  3. {
  4.     var ts = new DateTime(1980, 4, 15, 12, 0, 0);
  5.     var local = LocalDateTime.FromDateTime(ts);
  6.     var zdt = Utils.TzEast.ResolveLocal(local, Utils.CustomResolver);
  7.     Assert.IsFalse(zdt.IsDaylightSavingTime());
  8. }

NodaTime does not have a built-in IsDaylightSavingTime. I am calculating it based on this stackoverflow response by Matt Johnson.

Using NodaTime results in a UTC conversion that correctly incorporates actual DST transitions for dates before 1987.

Comparing UTC values, one for every day since 1900, makes is it clear that using NodaTime results in a conversion that correctly incorporates actual DST transitions for dates before 1987. To see the actual converted values using NodaTime as well as System.TimeZoneInfo, run TestRunner.ListUtcConvertedValues() from here and look for the output “dstNoda.csv” file. Or you can download the output file from here.

I have created an experimental visual studio project and published it on github.