DateTime.AddMonths adding only month not days

How about like this? It solves the Jan 30th problem that would occur with the current best answer.

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

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

And generalized for multiple months:

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

The code is tested against February issues, leap year and New Year transition. All test passed.

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

I don't know what you want to achieve, but you could add one day, add a month and subtract one day.

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

EDIT:

As one of the commenters points out, this sometimes gives the wrong result. After reading your updated question, I think the easiest way of calculating the date you want is:

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

This extension method returns next month's date. When the current date is the last day of the month, it will return the last day of next month.


If you mean that the resultant date should be the same distance from the end of the month, then you're into custom code - something like (not fully tested, especially re 28/30/31 months):

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(months) - delta;
    }

}

Tags:

C#

Datetime