.Net Framework issue with RTZ2 time zone

2019-07-01 14:22发布

问题:

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?

回答1:

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.



回答2:

Try this out :

var rtz2 = TimeZoneInfo.FindSystemTimeZoneById("Russian Standard Time");
var offset = new DateTimeOffset(new DateTime(2014, 1, 1));
var timeSpan = rtz2.GetUtcOffset(offset);

You can now create the UTC DateTime by using timeSpan :

var utc = offset.Add(timeSpan);
// 1/1/2014 4:00:00 AM -06:00