Wow, this is a Double Whammy! I just stumbled across this post and wasn't going to post anything at all since it's so old and the OP didn't show any code. But then curiosity got the best of me so I checked it out.
Using just the .NET BCL:
string tzid1 = "W. Australia Standard Time"; // Perth
TimeZoneInfo tz1 = TimeZoneInfo.FindSystemTimeZoneById(tzid1);
string tzid2 = "Sri Lanka Standard Time"; // Sri Jeyawardenepura
TimeZoneInfo tz2 = TimeZoneInfo.FindSystemTimeZoneById(tzid2);
DateTime dt1 = new DateTime(2006, 1, 1, 2, 0, 0);
Debug.WriteLine(dt1); // 1/1/2006 2:00:00 AM
DateTime dt2 = TimeZoneInfo.ConvertTime(dt1, tz1, tz2);
Debug.WriteLine(dt2); // 12/31/2005 11:30:00 PM
DateTime dt3 = TimeZoneInfo.ConvertTime(dt2, tz2, tz1);
Debug.WriteLine(dt3); // 1/1/2006 3:00:00 AM
Sure enough, there is the discrepancy that the OP described. At first I thought this must be due to some kind of DST issue, so I checked for Sri Lanka and Perth. While both had a transition in 2006, neither were anywhere close to it for this date. Still, I thought I should check using DateTimeOffset
to avoid any ambiguity issues:
string tzid1 = "W. Australia Standard Time"; // Perth
TimeZoneInfo tz1 = TimeZoneInfo.FindSystemTimeZoneById(tzid1);
string tzid2 = "Sri Lanka Standard Time"; // Sri Jeyawardenepura
TimeZoneInfo tz2 = TimeZoneInfo.FindSystemTimeZoneById(tzid2);
DateTime dt = new DateTime(2006, 1, 1, 2, 0, 0);
DateTimeOffset dto1 = new DateTimeOffset(dt, tz1.GetUtcOffset(dt));
Debug.WriteLine(dto1); // 1/1/2006 2:00:00 AM +08:00
DateTimeOffset dto2 = TimeZoneInfo.ConvertTime(dto1, tz2);
Debug.WriteLine(dto2); // 12/31/2005 11:30:00 PM +05:30
DateTimeOffset dto3 = TimeZoneInfo.ConvertTime(dto2, tz1);
Debug.WriteLine(dto3); // 1/1/2006 3:00:00 AM +09:00
And it's still off. You can see that it thinks the target time should be at +09:00
, but Perth didn't switch to that until December 3rd 2006. In January it was clearly still +08:00
.
So then I thought... Noda Time to the rescue!
First let's check using the same Windows .NET BCL time zones.
string tzid1 = "W. Australia Standard Time"; // Perth
DateTimeZone tz1 = DateTimeZoneProviders.Bcl[tzid1];
string tzid2 = "Sri Lanka Standard Time"; // Sri Jeyawardenepura
DateTimeZone tz2 = DateTimeZoneProviders.Bcl[tzid2];
LocalDateTime ldt1 = new LocalDateTime(2006, 1, 1, 2, 0, 0);
ZonedDateTime zdt1 = ldt1.InZoneStrictly(tz1);
Debug.WriteLine(zdt1.ToDateTimeOffset()); // 1/1/2006 2:00:00 AM +08:00
ZonedDateTime zdt2 = zdt1.WithZone(tz2);
Debug.WriteLine(zdt2.ToDateTimeOffset()); // 12/31/2005 11:30:00 PM +05:30
ZonedDateTime zdt3 = zdt1.WithZone(tz1);
Debug.WriteLine(zdt3.ToDateTimeOffset()); // 1/1/2006 2:00:00 AM +08:00
Hey, that seems like it fixed it, right? If so, that would mean that the problem isn't with the Windows time zone data, because Noda Time's BCL provider uses the exact same data. So there must be something actually defective in TimeZoneInfo.ConvertTime
. There's Whammy #1.
So just to check that it's all good and well, let's try the same thing with IANA TZDB data. It's known to be more accurate after all:
string tzid1 = "Australia/Perth";
DateTimeZone tz1 = DateTimeZoneProviders.Tzdb[tzid1];
string tzid2 = "Asia/Colombo"; // Sri Jeyawardenepura
DateTimeZone tz2 = DateTimeZoneProviders.Tzdb[tzid2];
LocalDateTime ldt1 = new LocalDateTime(2006, 1, 1, 2, 0, 0);
ZonedDateTime zdt1 = ldt1.InZoneStrictly(tz1);
Debug.WriteLine(zdt1.ToDateTimeOffset()); // 1/1/2006 2:00:00 AM +08:00
ZonedDateTime zdt2 = zdt1.WithZone(tz2);
Debug.WriteLine(zdt2.ToDateTimeOffset()); // 1/1/2006 12:00:00 AM +06:00
ZonedDateTime zdt3 = zdt1.WithZone(tz1);
Debug.WriteLine(zdt3.ToDateTimeOffset()); // 1/1/2006 2:00:00 AM +08:00
And there, my friends, is Whammy #2. Notice that the middle time is using a +06:00
offset? I thought this was in error, but when I checked once more here it turns out that the TZDB data is correct. Sri Lanka was at +06:00
at that time. It didn't switch to +05:30
until April.
So to recap the Whammys:
- The Windows
TimeZoneInfo.ConvertTime
function appears to be flawed.
- The Windows Time Zone data for the
"Sri Lanka Standard Time"
zone is incorrect.
All the better to just use Noda Time and TZDB always!
UPDATE
Thanks to Jon Skeet for helping identify that the first problem is with the way that the "W. Australia Standard Time"
zone is being interpreted by the TimeZoneInfo
class.
I dug much deeper into the .NET Framework reference source code, and I believe this is happening in the private static method TimeZoneInfo.GetIsDaylightSavingsFromUtc
. I believe that they are not taking into account that DST doesn't always start and stop in the same calendar year.
In this case, they are applying the 2006 adjustment rule with the 2005 year, and getting an endTime
of 1/2/2005
before the startTime
of 12/4/2005
. They do attempt to reconcile that this should be in 2006 (by incorrectly adding a year), but they don't consider that data is in reversed order.
This problem will probably show up for any time zones that start their DST in the winter (such as Australia), and it will show up in one form or another any time the transition rule changes - which it did in 2006.
I've raised an issue on Microsoft Connect here.
The "second whammy" I mentioned is just because the historical data for Sri Lanka doesn't exist in the Windows time zone registry keys.