Bounz Bounz - 24 days ago 11
C# Question

.Net Framework issue with RTZ2 time zone

It seems we have found an issue with RTZ2 timezone (Russian Standard Time) in .Net Framework 4.5.

If you try to convert time between 2014-01-01 00:00:00 and 2014-01-01 00:59:59 (in RTZ2 timezone) to UTC, you get an error:

The supplied DateTime represents an invalid time. For example, when the clock is adjusted forward, any time in the period that is skipped is invalid.


Example (https://dotnetfiddle.net/rNbp8F):

var rtz2 = TimeZoneInfo.FindSystemTimeZoneById("Russian Standard Time");
var moment = new DateTime(2014, 1, 1);
var utc = TimeZoneInfo.ConvertTimeToUtc(moment, rtz2); // throws an exception


Any ideas how to fix this?

Answer

This is probably related to KB3012229, which was fixed with .NET 4.6.

If you have .NET 4.6 installed, the exception will not be thrown - even if you are targeting .NET 4.0 through 4.5.2 - because they are all in-place upgrades.

The exception does reproduce on .NET 3.5, or on .NET 4.0 through .NET 4.5.2 if you do not have .NET 4.6 installed.

There are a few things you can do here:

  • Option 1: Leave your code as-is, and update to the latest .NET 4.6 (or install one of the hotfixes now available in the KB article)

  • Option 2: Change your code to use a function such as this:

    private DateTime Rtz2ToUtc(DateTime dt)
    {
        if (dt.Kind == DateTimeKind.Utc)
            return dt;
    
        if (dt.Year < 2011)
        {
            var tz = TimeZoneInfo.FindSystemTimeZoneById("Russian Standard Time");
            return TimeZoneInfo.ConvertTimeToUtc(dt, tz);
        }
    
        var transition = new DateTime(2014, 10, 26, 2, 0, 0);
        var offset = TimeSpan.FromHours(dt < transition ? 4 : 3);
        return new DateTimeOffset(dt, offset).UtcDateTime;
    }
    
  • Option 3: Change your code to use Noda Time and TZDB time zones:

    private DateTime Rtz2ToUtc(DateTime dt)
    {
        if (dt.Kind == DateTimeKind.Utc)
            return dt;
    
        DateTimeZone tz = DateTimeZoneProviders.Tzdb["Europe/Moscow"];
        LocalDateTime ldt = LocalDateTime.FromDateTime(dt);
        return ldt.InZoneLeniently(tz).ToDateTimeUtc();
    }
    

Personally, I prefer Option 3, as TZDB time zones are much more accurate than Windows time zones. You can read more in the timezone tag wiki.