DateTime.AddMonths добавление только месяц, а не дни - PullRequest
20 голосов
/ 17 июня 2010

Допустим, у меня есть 28 февраля 2010 и добавляю один месяц к этой дате, используя AddMonths(1) ...
, в результате получается 28 марта , но не 31 марта , что я хочу.
Есть ли способ немного подправить, чтобы это работало без добавления пользовательского кода?

Редактировать: мне не нужен последний деньмесяца, на самом деле мне нужно добавить один месяц, но когда это последний день месяца, мне нужно найти последний день следующего месяца.

Ответы [ 15 ]

36 голосов
/ 17 июня 2010

Я не знаю, чего вы хотите достичь, но вы можете добавить один день, добавить месяц и вычесть один день.

DateTime nextMonth = date.AddDays(1).AddMonths(1).AddDays(-1);

EDIT

Как указывает один из комментаторов, иногда это дает неверный результат. Прочитав ваш обновленный вопрос, я думаю, что самый простой способ вычислить желаемую дату:

public static DateTime NextMonth(this DateTime date)
{
   if (date.Day != DateTime.DaysInMonth(date.Year, date.Month))
      return date.AddMonths(1);
   else 
      return date.AddDays(1).AddMonths(1).AddDays(-1);
}

Этот метод расширения возвращает дату следующего месяца. Если текущая дата является последним днем ​​месяца, он вернет последний день следующего месяца.

7 голосов
/ 17 июня 2010

Если вы имеете в виду, что результирующая дата должна находиться на одинаковом расстоянии от конца месяца, то у вас есть пользовательский код - что-то вродемесяцев):

class Program
{
    static void Main()
    {
        var when = DateTime.Today;
        DateTime fromEndOfNextMonth = when.AddMonthsRelativeToEndOfMonth(1);
    }

}
public static class DateTimeExtensions
{
    public static DateTime AddMonthsRelativeToEndOfMonth(
               this DateTime when, int months)
    {
        if (months == 0) return when;
        DateTime startOfNextMonth = when;
        int month = when.Month;
        while (startOfNextMonth.Month == month)
        {
            startOfNextMonth = startOfNextMonth.AddDays(1);
        }
        TimeSpan delta = startOfNextMonth - when;
        return startOfNextMonth.AddMonths(1) - delta;
    }

}
4 голосов
/ 05 марта 2013

Как насчет этого?Это решает 30 января проблему, которая возникнет с лучшим текущим ответом.

        public static DateTime AddJustMonths(this DateTime @this, int months)
        {
            var firstDayOfTargetMonth = new DateTime(@this.Year, @this.Month, 1).AddMonths(months);
            var lastDayofTargetMonth = DateTime.DaysInMonth(firstDayOfTargetMonth.Year, firstDayOfTargetMonth.Month);

            var targetDay = @this.Day > lastDayofTargetMonth ? lastDayofTargetMonth : @this.Day;

            return new DateTime(firstDayOfTargetMonth.Year, firstDayOfTargetMonth.Month, targetDay);
        }
3 голосов
/ 28 сентября 2015
public static DateTime NextMonth(DateTime date)
{
    DateTime nextMonth = date.AddMonths(1);

    if (date.Day != DateTime.DaysInMonth(date.Year, date.Month)) //is last day in month
    {
        //any other day then last day
        return nextMonth;
    }
    else
    {
       //last day in the month will produce the last day in the next month
       return date.AddDays(DateTime.DaysInMonth(nextMonth.Year, nextMonth.Month));
    }
}

И обобщенно за несколько месяцев:

public static DateTime AddMonthToEndOfMonth(DateTime date, int numberOfMonths)
{
    DateTime nextMonth = date.AddMonths(numberOfMonths);

    if (date.Day != DateTime.DaysInMonth(date.Year, date.Month)) //is last day in month
    {
        //any other day then last day
        return nextMonth;
    }
    else
    {
        //if date was end of month, add remaining days
        int addDays = DateTime.DaysInMonth(nextMonth.Year, nextMonth.Month) - nextMonth.Day;
        return nextMonth.AddDays(addDays);
    }
}

Код проверен на февральские выпуски, високосный год и новогодний переход.Все испытания пройдены.

enter image description here

[TestMethod]
public void AddMonthTest_January()
{
    for (int i = 1; i <= 28; i++)
    {
        Assert.AreEqual(new DateTime(2015, 2, i), NextMonth(new DateTime(2015, 1, i)));
    }
    Assert.AreEqual(new DateTime(2015, 2, 28), NextMonth(new DateTime(2015, 1, 29)));
    Assert.AreEqual(new DateTime(2015, 2, 28), NextMonth(new DateTime(2015, 1, 30)));
    Assert.AreEqual(new DateTime(2015, 2, 28), NextMonth(new DateTime(2015, 1, 31)));
}

[TestMethod]
public void AddMonthTest_February()
{
    Assert.AreEqual(new DateTime(2015, 3, 31), NextMonth(new DateTime(2015, 2, 28)));

    for (int i = 1; i <= 27; i++)
    {
        Assert.AreEqual(new DateTime(2015, 3, i), NextMonth(new DateTime(2015, 2, i)));
    }            
}

[TestMethod]
public void AddMonthTest_March()
{
    Assert.AreEqual(new DateTime(2015, 4, 30), NextMonth(new DateTime(2015, 3, 31)));

    for (int i = 1; i <= 30; i++)
    {
        Assert.AreEqual(new DateTime(2015, 4, i), NextMonth(new DateTime(2015, 3, i)));
    }
}

[TestMethod]
public void AddMonthTest_December()
{            
    for (int i = 1; i <= 31; i++)
    {
        Assert.AreEqual(new DateTime(2016, 1, i), NextMonth(new DateTime(2015, 12, i)));
    }
}

[TestMethod]
public void AddMonthTest_January_LeapYear()
{
    for (int i = 1; i <= 29; i++)
    {
        Assert.AreEqual(new DateTime(2016, 2, i), NextMonth(new DateTime(2016, 1, i)));
    }            
    Assert.AreEqual(new DateTime(2016, 2, 29), NextMonth(new DateTime(2016, 1, 30)));
    Assert.AreEqual(new DateTime(2016, 2, 29), NextMonth(new DateTime(2016, 1, 31)));
}

[TestMethod]
public void AddMonthTest_February_LeapYear()
{
    Assert.AreEqual(new DateTime(2016, 3, 31), NextMonth(new DateTime(2016, 2, 29)));

    for (int i = 1; i <= 28; i++)
    {
        Assert.AreEqual(new DateTime(2016, 3, i), NextMonth(new DateTime(2016, 2, i)));
    }
}

[TestMethod]
public void AddHalfYearTest_January_LeapYear()
{        
    for (int i = 1; i <= 31; i++)
    {
        Assert.AreEqual(new DateTime(2016, 7, i), new DateTime(2016, 1, i).AddMonthToEndOfMonth(6));
    }
}

[TestMethod]
public void AddHalfYearTest_February_LeapYear()
{
    Assert.AreEqual(new DateTime(2016, 8, 31), new DateTime(2016, 2, 29).AddMonthToEndOfMonth(6));

    for (int i = 1; i <= 28; i++)
    {
        Assert.AreEqual(new DateTime(2016, 8, i), new DateTime(2016, 2, i).AddMonthToEndOfMonth(6));
    }
}

[TestMethod]
public void AddHalfYearTest_December()
{
    Assert.AreEqual(new DateTime(2016, 6, 30), new DateTime(2015, 12, 31).AddMonthToEndOfMonth(6));
    for (int i = 1; i <= 30; i++)
    {
        Assert.AreEqual(new DateTime(2016, 6, i), new DateTime(2015, 12, i).AddMonthToEndOfMonth(6));
    }
}
2 голосов
/ 24 февраля 2015

Этот код добавит количество месяцев и перейдет к последнему дню целевого месяца, если текущий день является последним днем ​​текущего месяца.Обратите внимание, что принципиально нет решения для решения проблемы 30 января без выполнения полностью пользовательских дат:

Если дата является единственным состоянием, то независимо от того, как вы справляетесь с переходом на один месяц впередс 30 января вы должны выбрать, интерпретировать ли результат как последний день февраля или просто 28-е число текущего месяца .Вы не можете иметь оба, так как дата - ваше единственное государство.Не существует «флага», который бы указывал на то, что этого самого 28 февраля изначально был единственным датой последнего дня января .

Фактически это означает, что Jan30.AddMonthsCustom (1) .AddMonthsCustom (1)! = Jan30.AddMonthsCustom (2) и что в конечном итоге любая 30-я, 29-я и 28-я дата заканчивается в последний день месяца, если вы продолжаете размножаться.

public static DateTime AddMonthsCustom(this DateTime date, int months)
{

    // Check if we are done quickly.
    if(months == 0)
        return;

    // Lookup the target month and its last day.
    var targetMonth = new DateTime(date.Year, date.Month, 1).AddMonths(months);
    var lastDay = DateTime.DaysInMonth(targetMonth.Year, targetMonth.Month);

    // If we are starting out on the last day of the current month, then jump
    // to the last day of the target month.
    if (date.Day == DateTime.DaysInMonth(date.Year, date.Month))
        return new DateTime(targetMonth.Year, targetMonth.Month, lastDay);

    // If the target month cannot accomodate the current day, jump to the 
    // last day of the target month.
    if (date.Day > lastDay)
        return new DateTime(targetMonth.Year, targetMonth.Month, lastDay);

    // Simply jump to the current day in the target month.
    return new DateTime(targetMonth.Year, targetMonth.Month, date.Day);
}

Если я все же ошибаюсь, пожалуйста,дай мне знать.Мне бы очень хотелось, чтобы это было решено.

1 голос
/ 16 января 2018

То, что предложил rashleighp, почти правильно, но не работает, например, добавление 1 месяца к 2016-02-29 (результат должен быть 2016-03-31) или 2017-02-28 (результат должен быть 2017-03-31)

Эта модифицированная версия должна работать, включая все особые случаи.

public static DateTime AddMonthsCustom(this DateTime source, int months)
{
    var firstDayOfTargetMonth = new DateTime(source.Year, source.Month, 1).AddMonths(months);
    var lastDayofSourceMonth = DateTime.DaysInMonth(source.Year, source.Month);
    var lastDayofTargetMonth = DateTime.DaysInMonth(firstDayOfTargetMonth.Year, firstDayOfTargetMonth.Month);

    var targetDay = source.Day > lastDayofTargetMonth ? lastDayofTargetMonth : source.Day;
    if (source.Day == lastDayofSourceMonth)
        targetDay = lastDayofTargetMonth;

    return new DateTime(
        firstDayOfTargetMonth.Year, 
        firstDayOfTargetMonth.Month, 
        targetDay, 
        source.Hour, 
        source.Minute, 
        source.Second, 
        source.Millisecond, 
        source.Kind);
}

Все тесты NUnit, указанные ниже, пройдены:

[TestCase("2017-01-01T01:01:01.0010000Z", "2016-12-01T01:01:01.0010000Z", 1)]
[TestCase("2017-02-01T01:01:01.0010000Z", "2016-12-01T01:01:01.0010000Z", 2)]
[TestCase("2017-03-31T01:01:01.0010000Z", "2016-12-31T01:01:01.0010000Z", 3)]
[TestCase("2016-03-28T01:01:01.0010000Z", "2016-02-28T01:01:01.0010000Z", 1)]
[TestCase("2016-03-31T01:01:01.0010000Z", "2016-02-29T01:01:01.0010000Z", 1)]
[TestCase("2017-03-31T01:01:01.0010000Z", "2017-02-28T01:01:01.0010000Z", 1)]
[TestCase("2016-02-29T01:01:01.0010000Z", "2016-01-31T01:01:01.0010000Z", 1)]
[TestCase("2017-02-28T01:01:01.0010000Z", "2017-01-31T01:01:01.0010000Z", 1)]
[TestCase("2016-12-01T01:01:01.0010000Z", "2017-01-01T01:01:01.0010000Z", -1)]
[TestCase("2016-12-01T01:01:01.0010000Z", "2017-02-01T01:01:01.0010000Z", -2)]
[TestCase("2016-12-31T01:01:01.0010000Z", "2017-03-31T01:01:01.0010000Z", -3)]
[TestCase("2016-02-28T01:01:01.0010000Z", "2016-03-28T01:01:01.0010000Z", -1)]
public void DateTimeExtensions_AddMonthsCustom(DateTime expected, DateTime dateTime, int months)
{
    // Arrange
    expected = expected.ToUniversalTime();
    dateTime = dateTime.ToUniversalTime();

    // Act
    DateTime result = dateTime.AddMonthsCustom(months);

    // Assert
    Assert.AreEqual(expected.Kind, result.Kind);
    Assert.AreEqual(expected, result);
}
0 голосов
/ 14 июня 2018

Дает последний день следующего месяца в одной строке:

var t1 = new DateTime(2010,2,28); 
var t2 = t1.AddDays((t1.Day * -1) + 1).AddMonths(2).AddMilliseconds(-1).Date;
// t2: {31.03.2010 00:00:00}

(Операции: получить первый день текущего месяца (= 1 февраля 10), добавить 2 месяца (= 1 апреля 10), вычесть на 1 мс (= 31. 10 марта), дополнительное время отсечения

0 голосов
/ 23 марта 2018

Это добавит numMonths к someDate и, если someDate является концом месяца, возвращаемое значение будет концом месяца, в противном случае он просто добавляет AddMonths (numMonths)

private DateTime AddMonthsRetainingEOM(DateTime someDate, int numMonths)
    {
        if (someDate.AddDays(1).Day == 1)
        {
            // someDate is EOM
            someDate = someDate.AddMonths(numMonths);
            // keep adding days if new someDate is not EOM
            while (someDate.AddDays(1).Day != 1)
            {
                someDate = someDate.AddDays(1);
            }
            return someDate;
        }
        else
        {
            // not EOM - Just add months
            return someDate.AddMonths(numMonths);
        }
    }
0 голосов
/ 03 сентября 2016

вы можете попробовать это

private void datTimPkerFrom_ValueChanged(object sender, EventArgs e)
{
    int DaysInMonth = DateTime.DaysInMonth(datTimPkerFrom.Value.Year, datTimPkerFrom.Value.Month);

    if (DaysInMonth == 31)
    {
        datTimPkerTo.Value = datTimPkerFrom.Value.AddDays(30);
    }
    else if (DaysInMonth == 30)
    {
        datTimPkerTo.Value = datTimPkerFrom.Value.AddDays(29);
    }
    else if (DaysInMonth == 29)
    {
        datTimPkerTo.Value = datTimPkerFrom.Value.AddDays(28);
    }
    else
    {
        datTimPkerTo.Value = datTimPkerFrom.Value.AddDays(27);
    }
}
0 голосов
/ 29 апреля 2015

А как насчет этого?Он может добавить столько месяцев, сколько вам нужно, в качестве метода расширения - т.е. dateDue.AddSmarthMonths(6); - и считает любой день января после 28 "последним днем ​​месяца".

    public static DateTime AddSmartMonths(this DateTime d, int nMonths)
    {
        int year = d.Year;
        int month = d.Month;
        int day = d.Day;

        if ((day == 30) && (day < DateTime.DaysInMonth(year, month)))
            d = d.AddDays(1);
        else if ((month == 1) && (day > 28))
            d = new DateTime(year, month, 31);

        return d.AddMonths(nMonths);
    }
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...