Поведение DateTime.AddYears в високосный год

Может ли кто-нибудь объяснить математическое или просто рассуждение за вычислениями високосного года в .NET при использовании метода AddYears в DateTime?

  • Если вы возьмете 29 февраля 2012 года и добавите год, вы получите 28 февраля 2013 года, а не 1 марта 2013 года (день до года).
  • Если вы добавите один год до 31 января 2012 года, вы получите 31 января 2013 года (в тот же день через год).

Я думаю, что большинство людей полагают, что "один год с 29.02.leapX равен 01.03.leapX + 1".

Пример:

// 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

Ответ 1

Я думаю, что большинство людей полагают, что "один год с 29.02.leapX равен 01.03.leapX + 1".

Я бы не стал. Обычно я ожидаю усечения. Это принципиально похоже на добавление одного месяца к 30 января - я ожидаю получить последний день в феврале. В обоих случаях вы добавляете "большую единицу" (месяц или год), а "меньшая единица" (день) усекается, чтобы вписаться в комбинацию год/месяц.

(Вот как Joda Time и Noda Time ведут себя тоже, кстати.)

Как упоминал Тим в комментариях, этот документировал:

Метод AddYears вычисляет полученный год с учетом високосных лет. Месяц и временная часть результирующего объекта DateTime остаются такими же, как этот экземпляр.

Таким образом, месяц должен оставаться в феврале; год изменится в зависимости от того, сколько лет добавляется, очевидно - поэтому день должен корректироваться, чтобы оставаться в силе.

Ответ 2

С вашей точки зрения, 1 марта 2012 года станет 2 марта 2012 года, когда вы добавите год. Если вы добавите этот сдвиг для всех предшествующих високосных лет, вы будете искать ваш расчет в массовом движении. Единственный разумный ответ - вернуть 28 февраля за непиковые годы.

Ответ 3

Это интересно, тем не менее..

например эта функция иногда используется:

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;
}

Если человек родился 29 февраля 2016 года, эта функция сделает вывод, что он достиг 1 года 28 февраля 2016 года.

Я отметил примеры функций Excel в соответствии с:

=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