【问题标题】:DateTime.AddMonths adding only month not daysDateTime.AddMonths 只添加月份而不是天
【发布时间】:2010-06-17 09:26:52
【问题描述】:

假设,我有 2010 年 2 月 28 日,并使用 AddMonths(1)...
为该日期添加一个月 结果日期是 3 月 28 日,但不是我想要的 3 月 31 日
有没有办法稍微调整一下,这样就可以在不添加自定义代码的情况下工作?

编辑:我不需要一个月的最后一天,实际上我需要添加一个月,但是当它是一个月的最后一天时,我需要找到下个月的最后一天。

【问题讨论】:

  • 如果有任何答案满足您的需求,请点击旁边的“打勾”接受。
  • 对算法进行微调:当日期是 2 月 27 日或 2 月 1 日时,您期望什么?

标签: c# datetime


【解决方案1】:

我不知道你想达到什么目标,但你可以加一天,加一个月,减一天。

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

编辑

正如其中一位评论者指出的那样,这有时会给出错误的结果。阅读您更新的问题后,我认为计算您想要的日期的最简单方法是:

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

此扩展方法返回下个月的日期。当前日期为当月最后一天时,返回下个月最后一天。

【讨论】:

  • 聪明 - 如果他保证已经有一个月的最后一天,那就行了。
  • 如果他没有,结果和AddMonths(1)一样
  • 1 月 30 日不是这样。
  • 1 月 30 日有什么解决办法吗?
  • 为什么不只是 new Date(date.Year, date.Month, 1).AddMonths(1).AddDays(-1) in else?
【解决方案2】:
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);
    }
}

代码针对 2 月问题、闰年和新年过渡进行了测试。所有测试都通过了。

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

【讨论】:

  • 像魅力一样工作!
【解决方案3】:

如果您的意思是生成的日期应该与当月 end 的距离相同,那么您就进入了自定义代码 - 类似于(未完全测试,尤其是 28/30/ 31 个月):

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

}

【讨论】:

  • 扩展方法的最后一行应该是return startOfNextMonth.AddMonths(months) - delta;
【解决方案4】:

这样怎么样?它解决了当前最佳答案可能出现的 1 月 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);
        }

【讨论】:

    【解决方案5】:

    如果当前日期是当前月份的最后一天,此代码将添加月数并跳转到目标月份的最后一天。请注意,如果不完全自定义日期,对于 1 月 30 日的问题基本上没有解决方案

    如果日期是唯一的状态,那么无论您如何处理从 1 月 30 日起提前一个月的跳跃,您都必须选择将结果解释为 2 月的最后一天 还是简单地当月 28 日。你不能同时拥有这两个,因为日期是你唯一的状态。没有“标志”告诉您2 月 28 日原本是 1 月的最后一天

    实际上这意味着 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);
    }
    

    如果我错了,请告诉我。我真的很想解决这个问题。

    【讨论】:

      【解决方案6】:

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

      【讨论】:

        【解决方案7】:

        不 - 它没有考虑到这一点。一路都是自定义代码!

        您的代码是否只对月份的最后一天感兴趣,或者您是否希望代码在任何日期上添加一个月,但考虑到提供的日期是该月的最后一天?

        【讨论】:

        • 是的,我认为几个月的最后一天也足够了...我会尽快标记答案:)
        【解决方案8】:

        我现在解决了这个问题,通过使用 GetLastDayInCurrentMonth 检查它是否是一个月的最后一天。如果是这样,我使用

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

        如果不是最后一天,我就用 AddMonths(1)

        谢谢大家!

        【讨论】:

          【解决方案9】:
          public static class DateTimeExtensions
          {
              public static DateTime AddMonthsCustom(this DateTime source, int months)
              {
                  DateTime result = source.AddMonths(months);
                  if (source.Day != DateTime.DaysInMonth(source.Year, source.Month))
                      return result;
          
                  return new DateTime(result.Year, result.Month,
                                      DateTime.DaysInMonth(result.Year, result.Month),
                                      result.Hour, result.Minute, result.Second,
                                      result.Millisecond, result.Kind);
              }
          }
          

          【讨论】:

            【解决方案10】:
            if(yourDate.Day == DaysInMonth(yourDate.Year,yourDate.Month)) //check for last day
                yourDate.AddDays(DateTime.DaysInMonth(yourDate.Year,(yourDate.Month+1)%12));
            

            【讨论】:

            • 如果你的约会是在 12 月呢?
            • @Philippe Leybaert:感谢您发现这一点。检查我的编辑。
            【解决方案11】:

            这个呢?它可以作为扩展方法添加任意数量的月份(即dateDue.AddSmarthMonths(6);),并考虑 28 日之后的 1 月的任何一天“本月的最后一天”。

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

            【讨论】:

              【解决方案12】:

              你可以试试这个

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

              【讨论】:

                【解决方案13】:

                这会将 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);
                        }
                    }
                

                【讨论】:

                  【解决方案14】:

                  在一行中给出下个月的最后一天:

                  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.Feb 10),加 2 个月(= 1. Apr 10),减去 1 ms(= 31. Mar 10),可选截止时间

                  【讨论】:

                    【解决方案15】:

                    如果给定日期是当月的最后一天,则下面提到的扩展方法将产生月的最后一天,否则就像在 datetime API 中一样。

                    public static DateTime AddMonthsE(this DateTime value,int numberOfMonths)
                    {           
                        bool isEndDate = DateTime.DaysInMonth(value.Year, value.Month) == value.Day;
                        if(isEndDate)
                        {
                            var newDateTime = value.AddMonths(numberOfMonths);
                            return new DateTime(newDateTime.Year, newDateTime.Month, DateTime.DaysInMonth(newDateTime.Year, newDateTime.Month));
                        }
                        return value.AddMonths(numberOfMonths);
                    }
                    

                    输入 2/28/2010 12:00:00 AM 输出加上 1 个月 3/31/2010 12:00:00 AM

                    【讨论】:

                      【解决方案16】:

                      你也可以试试这个

                      int numberofmonths = Convert.ToInt32(textBox2.Text); //number of target months as integer
                      dateTimePicker1.Text = DateTime.Parse(strDate).Date.AddMonths(numberofmonths).ToString("d"); //new date displayed
                      

                      【讨论】:

                        【解决方案17】:

                        如果您想在下个月始终保持同一天,使用起来会不会更简单:

                        DateTime nextMonth = new DateTime(thisMonth.Year, thisMonth.Month + 1, thisMonth.Day);

                        【讨论】:

                        • 我认为如果下个月的天数比当前月的天数少,这就是失败的地方。例如:thisMonth = Jan 30。没有 Feb 30,所以你会从那里的 DateTime 构造函数中抛出异常。
                        【解决方案18】:

                        尝试重载 day 属性并将其设置为 32。如果这是在 JavaScript 中完成的,我相信它默认返回到您所在月份的最后一天。

                        【讨论】:

                        • 如果您的意思是 set 日期属性 - 它是不可变的(这是 C# - 请参阅问题上的标签)。
                        • 哇!对不起。我没有意识到你不能在 C# 中做到这一点。
                        猜你喜欢
                        • 1970-01-01
                        • 1970-01-01
                        • 1970-01-01
                        • 2019-10-19
                        • 1970-01-01
                        • 1970-01-01
                        • 2023-02-17
                        • 1970-01-01
                        • 2023-03-04
                        相关资源
                        最近更新 更多