6

The next code shows wrong local time if current location is Moscow:

DateTime dt = new DateTime(2010, 1, 1, 10, 0, 0, 0, DateTimeKind.Utc);
Console.WriteLine(dt + " - " +  dt.ToLocalTime());

dt = new DateTime(2010, 7, 1, 10, 0, 0, 0, DateTimeKind.Utc);
Console.WriteLine(dt + " - " + dt.ToLocalTime());

Output:

01.01.2010 10:00:00 - 01.01.2010 14:00:00
01.07.2010 10:00:00 - 01.07.2010 15:00:00

It should be 13:00 and 14:00. How to fix it?

P.S. OS - Windows 7 Enterprise.

Rover
  • 2,203
  • 3
  • 24
  • 44
  • 1
    possible duplicate of [C# - Convert UTC/GMT time to local time](http://stackoverflow.com/questions/179940/c-sharp-convert-utc-gmt-time-to-local-time) – bzlm Aug 08 '13 at 10:45
  • Moscow is supposed to be 4 hours ahead of UTC [http://en.wikipedia.org/wiki/Moscow_Time] – James Aug 08 '13 at 10:45
  • What OS are you running this on? (I ask because XP may have issues) – Matthew Watson Aug 08 '13 at 10:47
  • 3
    Just as a side-note, I hear [Nodatime](https://code.google.com/p/noda-time/) is great with this sort of stuff, and a lot more accurate than DateTime. Maybe check it out. – JMK Aug 08 '13 at 10:48
  • 3
    @judgeja: But not in January 2010. Back then it was UTC+3 – Daniel Hilgarth Aug 08 '13 at 10:48
  • if you want a quick fix, then `dt.ToLocalTime().AddHours(-1)` – Praveen Aug 08 '13 at 10:51
  • 4
    @MatthewWatson is correct about the XP issue. The documentation of `ToLocalTime` explicitly states this: *"On Windows XP systems, the ToLocalTime method **recognizes only the current adjustment rule** when converting from UTC to local time. As a result, conversions for periods before the current adjustment rule came into effect may not accurately reflect the difference between UTC and local time."*. In other words: It uses the *current* rules, no matter whether they were in effect at the date you are converting. And that's exactly what you are seeing. – Daniel Hilgarth Aug 08 '13 at 10:52
  • dt.ToLocalTime().AddHours(-1) - it's hard to handle in all usage places. It's the last what I will try, but if nobody help me, I will :( – Rover Aug 08 '13 at 10:53
  • @Rover: You shouldn't use this, because it will mess up current times. This can't be even considered a hack. Using this would be a bug. – Daniel Hilgarth Aug 08 '13 at 10:56
  • @Daniel well if he manually checks if the date is before 27 March 2011 and knows his program will be run in moscow it could be a last resort hack? – James Aug 08 '13 at 10:58
  • @daniel-hilgarth, I use Windows 7. Do you know how to retrieve correct UTC datetime? – Rover Aug 08 '13 at 11:00
  • @judgeja: He would also have to check that the program is running on XP and that the locale of the computer is actually Moscow. Only in that case it no longer is a bug but "only" a messy hack. However, this hack would not solve that problem for all other locations for which the rules changed. – Daniel Hilgarth Aug 08 '13 at 11:01
  • 1
    @Rover: In my Windows 7 the results seem to be correct. However, you still might want to check out Noda Time as suggested by JMK. – Daniel Hilgarth Aug 08 '13 at 11:05
  • @Rover: Can you please tell me the value of `TimeZoneInfo.Local.Id`? – Daniel Hilgarth Aug 08 '13 at 11:07
  • TimeZoneInfo.Local.Id = "Russian Standard Time" – Rover Aug 08 '13 at 11:12

2 Answers2

7

Looks like the reason is that when the new Russian laws have taken effect, Microsoft guys, instead of making Moscow time have permanent DST on, changed it's base UTC offset and disabled DST switching. Since timezone info does not contain any historic definitions of UTC offset (as opposed to historic DST switching), this broke the old datetime conversions. Hence, the results are as follows:

  • 01/01/2011 10:00:00 = 01/01/2011 14:00:00 because it's "GMT+4"
  • 01/07/2010 10:00:00 = 01/07/2010 15:00:00 because it's "GMT+4 + DST"

That's clearly an error in the Microsoft time library. At least they could have represented it as DST being permanently on....

Example code:

void Main()
{
    DateTime dt = new DateTime(2010, 1, 1, 10, 0, 0, 0, DateTimeKind.Utc);
    UTCToLocal(dt);

    dt = new DateTime(2010, 7, 1, 10, 0, 0, 0, DateTimeKind.Utc);
    UTCToLocal(dt);
}

void UTCToLocal(DateTime datetime) {
    if (datetime.Kind != DateTimeKind.Utc)
        throw new ApplicationException();

    Console.WriteLine("UTC: {0} MOW: {1} IsDST: {2}", datetime, datetime.ToLocalTime(),TimeZoneInfo.Local.IsDaylightSavingTime(datetime.ToLocalTime()));
}

Output:

UTC: 01/01/2010 10:00:00 MOW: 01/01/2010 14:00:00 IsDST: False
UTC: 01/07/2010 10:00:00 MOW: 01/07/2010 15:00:00 IsDST: True

You can clearly see that the summer datetime is being handled as DST one.

EDIT: The solution for the problem is in the next answer, I'll just leave mine here as a background.

DarkWanderer
  • 8,739
  • 1
  • 25
  • 56
3

This is a bug introduced into Windows historical timezone data. There is a hotfix available (this applies to Windows 7, Server 2008 R2 and others, not just XP).

Hotfix: Update for 2011 calendar history in Windows operating systems

As JMK said in the comments, you may want to consider the excellent library NodaTime instead, particularly if you are distributing your program to users who may not have this hotfix.

Colin Pickard
  • 45,724
  • 13
  • 98
  • 148
  • Yes, this bug (and the hotfix) are applicable to almost every supported version of Windows. The only exceptions I can see are Windows 8 and Server 2012. – Colin Pickard Aug 08 '13 at 11:44
  • 1
    I can't install this update: "The update is not applicable to your computer." Language and OS is correct. – Rover Aug 09 '13 at 07:22