views:

351

answers:

3

I encountered what may be a leap year in .NET's DateTime handling, specifically ToLocalTime(). Here's some code which reproduces the problem (I'm in the Pacific time zone):

DateTime dtStartLocal = DateTime.Parse("2009-02-28T23:00:00.0-08:00");
DateTime dtEndLocal = dtStartLocal.AddYears(3);
DateTime dtStartUtc = dtStartLocal.ToUniversalTime();
DateTime dtEndUtc = dtStartUtc.AddYears(3);
DateTime dtEndLocal2 = dtEndUtc.ToLocalTime();
DateTime dtStartLocal2 = dtStartUtc.ToLocalTime();
Console.WriteLine("START: 1={0}, 2={0}", dtStartLocal, dtStartLocal2);
Console.WriteLine("END  : 1={0}, 2={1}", dtEndLocal, dtEndLocal2);
Console.ReadLine();

The output is:

START: 1=2/28/2009 11:00:00 PM, 2=2/28/2009 11:00:00 PM
END : 1=2/28/2012 11:00:00 PM, 2=2/29/2012 11:00:00 PM

Notice the variable which I did ToUniversalTime().AddYears(3).ToLocalTime() is different than just AddYears(3), it's one day ahead.

Has anyone encountered this? If this is expected, can someone explain the logic behind it?

NOTE: Yes, the best approach is to work entirely in UTC and not flip flop between them. This isn't something which is effecting me, but a peculiarity I encountered. Essentially I misunderstood how AddYears() worked and now I can see why it's doing what it's doing (see my selected answer below).

A: 

Print the timezone/correction factor. When you do the .ToUniversialTime() it essentially adds the 8 hours from your original time ("-08:00"), which would put it at 11:00 the next day starting from 23:00 hours February 28th. So when you add 3 years to it, it's the 11:00 AM on the 29th. Had you done 2 years, it would have been March 1st, it has nothing to do with the leap year.

Bearddo
+8  A: 

I think that this is working correctly.

DateTime dtStartUtc = dtStartLocal.ToUniversalTime();

PST is UTC-8. Therefore, this converts the time to March 1, 2009, 07:00:00.

DateTime dtEndUtc = dtStartUtc.AddYears(3);

This adds three years to the previous time, putting it at March 1, 2012, 07:00:00.

DateTime dtEndLocal2 = dtEndUtc.ToLocalTime();

This converts the end time back to PST, which would be February 29, 2012, 11:00:00.

I'd say this is just a side affect of converting between local and UTC time.

Andy
Nice explanation. This is a good example of why you should use UTC datetimes for your date arithmetic.
Michael Haren
No, this is a good example of why doing date arithmetic in different timezones is bad. You could do every operation in local time and still get the same result as if you did it in UTC. So long as you're consistent, it doesn't matter.
Whatsit
@Whatsit: in this case yes, but if wee add DST into the mix, UTC is the only reasonable way
Michael Haren
Thanks, it appears I misunderstood how AddYears() worked. It makes sense that it simply decrements the year property, but I thought it worked off of the LENGTH of a year. Goof that I am ...
Neil C. Obremski
A: 

This behaviour isn't incorrect, as far as I can tell. When you convert the local time to UTC it effectively pushes it into the next day; March 1st. When you add three years, it stays as March 1st. Convert it back to local time and it rolls back to the previous day, which because 2012 is a leap year, is February 29th.

Whatsit