Search code examples
.netdatetimetestingtimezonedst

Daylight Saving Time transition start time tests fail for 'GTB Standard Time' on Windows


I wrote several tests for converting times from a specific timezone to UTC.

The rules say that in Europe the clocks are shifted forward at 01:00 UTC, for 'GTB Standard Time' this means +2 UTC, e.g. 03:00:00 .

However, I discovered that there are discrepancies in tests for different platforms (Linux vs Windows) using TimeZoneInfo.IsDaylightSavingTime(dateTime) method.

I created a new test object for DST transition date and time:

var testDate = new DateTime(2019, 03, 31, 03, 20, 00); //20 minutes after Daylight Saving Time start
...

And passed it in for TimeZoneInfo 'timeZone' object, where time zone was set to 'GTB Standard Time':

...
    var timeZone = TimeZoneInfo.FindSystemTimeZoneById("GTB Standard Time");
    return timeZone.IsDaylightSavingTime(testDate);
}

This returns False, although 03:20:00 locally on 2019-03-31 the daylight saving should already be ON. This should have returned True.

I managed to repeat this test only on Windows using built-in database for .Net timezones. With tzdata db (NodaTime for example) returns True for this test.

Confusing part was that I manually threw an error for the timeZone object to see what extra information comes from TimeZoneInfo object and found that DST start is actually correct:

timeZone.GetAdjustmentRules().First().DaylightTransitionStart.TimeOfDay

returns 03:00:00

Could someone confirm this and also is there a way to check where does windows take DST times? Can I make current test pass on both windows and linux without using any external libraries?


Solution

  • The local time for this time zone goes from 02:59:59 to 04:00:00 on that particular day, thus 3:20:00 is invalid. It falls into the gap created by the forward transition.

    Per the docs for TimeZoneInfo.IsDaylightSavingTime(DateTime) (emphasis mine)

    If the dateTime parameter specifies an invalid time, the method call throws an ArgumentException if the value of the dateTime parameter's Kind property is DateTimeKind.Local; otherwise, the method returns false.

    If you can, use DateTimeOffset types instead, or use DateTime with DateTimeKind.Utc. With either, there is no possibility of invalid or ambiguous local time.

    If you can't do that, then test your input values using IsInvalidTime and IsAmbiguousTime on the TimeZoneInfo object, then you can make a decision in your code about how you want to handle results. For example, you may want to move 3:20 to 4:20 for that instance.

    In the case of ambiguous time, be aware that all methods on TimeZoneInfo (IsDaylightSavingTime, GetUtcOffset, etc.) will assume the standard time by default, which comes second sequentially. One almost always wants the daylight time in these scenarios. You can get the two available offsets by calling GetAmbiguousTimeOffsets and choose the larger one.

    Also, you mentioned Noda Time - A much better API, IMHO. There, such things are handled by "resolvers" The default "lenient" resolver is used if you call LocalDateTime.InZoneLeniently. There are other resolvers, and you can specify your own. See the docs starting here.