Our government loves to change local time or enable|disable daylight saving time.
MS deployed patch for Russia to take new time change into account.
Now there is the question whether history of the changes exists?
When I get UTC time of the day in 01.01.2000 system should remember that Moscow time zone had +3 UTC. (in summer +4) at that moment.
For 01.01.2012 we had +4 UTC for both winter and summer. And soon we will have +3 UTC.
simple test shows that .NET does not keep records about the changes:
var t = new DateTime(2012,1,1);
// UTC +4 expected
System.Console.WriteLine(t.ToLocalTime());
// UTC +4 expected
t = new DateTime(2012,06,1);
System.Console.WriteLine(t.ToLocalTime());
// UTC +3 expected
t = new DateTime(2000,1,1);
System.Console.WriteLine(t.ToLocalTime());
// UTC +4 expected
t = new DateTime(2000,6,1);
System.Console.WriteLine(t.ToLocalTime());
Does some additional API exist to cope with the problem?
Update:
Have found class TimeZoneInfo and related AdjustmentRule class. Left to test whether customization of TimeZoneInfo.Local
time zone affects DateTime APIs.
Update 2:
Seems like UTC offsets are not stored as history and AdjustmentRule
changes only daylight time during the year.
Most of the United States begins Daylight Saving Time at 2:00 a.m. on the second Sunday in March and reverts to standard time on the first Sunday in November. In the U.S., each time zone switches at a different time.
Samoa will switch time zones by redrawing the international dateline. Samoans jump into the future. The change will occur at midnight on December 29, 2011, taking the Pacific island nation straight into December 31, 2011.
DST is the practice of resetting clocks ahead by an hour in spring, and behind by an hour in autumn (or fall). Clocks in the US will “fall back” an hour on Sunday, signalling the end of Daylight Saving Time (DST) this year. In Europe, the same happened on October 25.
.NET tracks some history, but it is not always accurate. You've stumbled upon one of the inaccuracies.
.NET imports all of it's time zone information from Windows via the registry, as described here and here. If you look in the registry at HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones\Russian Standard Time\Dynamic DST
you'll find that it only tracks information from 2010 forward for this time zone. Testing dates in year 2000 is not going to work well, as it will fall back to the earliest rule available (2010).
Base UTC offset information is tracked in the registry, but not in the AdjustmentRule
class that .NET imports it into. If you check the adjustment rules for this time zone, you'll find that 2012 and 2013 are not imported at all:
var tz = TimeZoneInfo.FindSystemTimeZoneById("Russian Standard Time");
foreach (var rule in tz.GetAdjustmentRules())
{
Console.WriteLine("{0:d} - {1:d}", rule.DateStart, rule.DateEnd);
}
OUTPUT:
1/1/0001 - 12/31/2010
1/1/2011 - 12/31/2011
1/1/2014 - 12/31/2014
Even though they exist in the Windows registry, 2012 and 2013 are not imported because they have no daylight saving time adjustments.
This creates a problem when the base offset changes - like it has for this time zone. Since it is currently +3, and the two year where it was +4 were not imported, then it will look like it was +3 for those missing years.
There is no good solution for this using TimeZoneInfo
. Even if you try to create your own custom time zones, you'll have trouble fitting this kind of change into the data structures available.
Fortunately, there is another option. You can use standard IANA time zones, via the Noda Time library.
The following code uses Noda Time to match what you wrote in your original code:
DateTimeZone tz = DateTimeZoneProviders.Tzdb.GetSystemDefault();
Console.WriteLine(Instant.FromUtc(2012, 1, 1, 0, 0).InZone(tz).LocalDateTime);
Console.WriteLine(Instant.FromUtc(2012, 6, 1, 0, 0).InZone(tz).LocalDateTime);
Console.WriteLine(Instant.FromUtc(2000, 1, 1, 0, 0).InZone(tz).LocalDateTime);
Console.WriteLine(Instant.FromUtc(2000, 6, 1, 0, 0).InZone(tz).LocalDateTime);
If your local time zone is not already set for Moscow, you can change the first line to:
DateTimeZone tz = DateTimeZoneProviders.Tzdb["Europe/Moscow"];
OUTPUT:
1/1/2012 4:00:00 AM
6/1/2012 4:00:00 AM
1/1/2000 3:00:00 AM
6/1/2000 4:00:00 AM
The problem described above of AdjustmentRule
not tracking base offset changes was described in Microsoft Support article KB3012229, and subsequently fixed in .NET Framework 4.6 and also in .NET Core.
In the reference sources, one can see that AdjustmentRule
now keeps a m_baseUtcOffsetDelta
field. While this field is not exposed via a public property, it does factor in to the calculations, and it does reflect in serialization if you use the FromSerializedString
and ToSerializedString
methods (if anyone actually uses those).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With