krembanan
krembanan

Reputation: 1428

Behavior of DateTime.AddYears on leap year

Can anyone explain the mathematical or simply the reasoning behind the leap year calculations in .NET when using AddYears method on DateTime?

I think most people would assume that "one year from 29.02.leapX is 01.03.leapX+1".

Example:

// Testing with 29th Feb
var now1 = DateTime.Parse("2012-02-29 15:00:00");

var results1 = new DateTime[]
{
    now1.AddYears(1),
    now1.AddYears(2),
    now1.AddYears(3),
    now1.AddYears(4)
};

foreach(var dt in results1)
{
    Console.WriteLine(dt.ToString("s"));
}

// Output:
// 2013-02-28T15:00:00
// 2014-02-28T15:00:00
// 2015-02-28T15:00:00
// 2016-02-29T15:00:00


// Testing with 31st Jan
var now2 = DateTime.Parse("2012-01-31 13:00:00");

var results2 = new DateTime[]
{
    now2.AddYears(1),
    now2.AddYears(2),
    now2.AddYears(3),
    now2.AddYears(4)
};

foreach(var dt in results2)
{
    Console.WriteLine(dt.ToString("s"));
}

// Output:
// 2013-01-31T13:00:00
// 2014-01-31T13:00:00
// 2015-01-31T13:00:00
// 2016-01-31T13:00:00

Upvotes: 16

Views: 11117

Answers (3)

Allan F
Allan F

Reputation: 2288

It is interesting, nether-the-less ..

e.g. this function is sometimes used:

private static int Age(DateTime birthDate, DateTime asAtDate)
{
    // Calculate age in years
    int age = asAtDate.Year - birthDate.Year;
    if (asAtDate < birthDate.AddYears(age)) age--;
    if (age < 0) age = 0;
    return age;
}

If a person was born on 29Feb2016, this function is going to conclude they have reached age 1 on 28Feb2017.

I noted Excel Function examples as per:

=DATEDIF(DATE(2016,2,28),DATE(2017,2,28),"Y")     
   gives result of 1
=DATEDIF(DATE(2016,2,29),DATE(2017,2,28),"Y")
   gives result of 0
=DATEDIF(DATE(2016,2,29),DATE(2017,3,1),"Y")
   gives result of 1
=DATEDIF(DATE(2016,3,1),DATE(2017,3,1),"Y")
   gives result of 1

Upvotes: 0

Lazarus
Lazarus

Reputation: 43064

With your rationale then 1-Mar-2012 would become 2-Mar-2012 when you added a year. If you add this shift for all prior leap years then you are going to find your calculation massively adrift. The only sensible response is to return 28-Feb for non-leap years.

Upvotes: 3

Jon Skeet
Jon Skeet

Reputation: 1499800

I think most people would assume that "one year from 29.02.leapX is 01.03.leapX+1".

I wouldn't. I would normally expect truncation. It's fundamentally similar to adding one month to January 30th - I'd expect to get the last day in February. In both cases, you're adding a "larger unit" (month or year) and a "smaller unit" (day) is being truncated to fit in with the year/month combination.

(This is how Joda Time and Noda Time behave too, btw.)

As Tim mentioned in comments, it's documented that way too:

The AddYears method calculates the resulting year taking into account leap years. The month and time-of-day part of the resulting DateTime object remains the same as this instance.

So the month has to stay as February; the year will change based on how many years are being added, obviously - so the day has to adjust to stay valid.

Upvotes: 19

Related Questions