【问题标题】:How to calculate actual months difference (calendar year not approximation) between two given dates in C#?如何计算 C# 中两个给定日期之间的实际月差(日历年不是近似值)?
【发布时间】:2010-07-20 01:57:27
【问题描述】:

示例:给定以下两个日期,finish 总是大于或等于 start

开始 = 2001 年 1 月 1 日

完成 = 2002 年 3 月 15 日

所以从 2001 年 1 月 1 日到 2002 年 2 月结束

月 = 12 + 2 = 14

2002 年 3 月

15/30 = 0.5

所以总计相差 14.5 个月。

手工计算很容易,但我如何优雅地编码呢?目前我有很多 if else 和 while 循环的组合来实现我想要的,但我相信那里有更简单的解决方案。

更新:输出需要精确(不是近似值),例如: 如果从 2001 年 1 月 1 日开始,到 2001 年 4 月 16 日结束,则输出应为 1 + 1 + 1= 3(一月、二月和三月)和 16 / 31 = 0.516 个月,因此总数为 3.516。

另一个例子是,如果我从 2001 年 7 月 5 日开始并在 2002 年 7 月 10 日结束,那么输出应该是到 2002 年 6 月结束的 11 个月,并且 (31-5)/31 = 0.839 和 10/31 = 0.323 个月,所以总数是 11 + 0.839 + 0.323 = 12.162。

我扩展了 Josh Stodola 的代码和 Hightechrider 的代码:

public static decimal GetMonthsInRange(this IDateRange thisDateRange)
{
    var start = thisDateRange.Start;
    var finish = thisDateRange.Finish;

    var monthsApart = Math.Abs(12*(start.Year - finish.Year) + start.Month - finish.Month) - 1;

    decimal daysInStartMonth = DateTime.DaysInMonth(start.Year, start.Month);
    decimal daysInFinishMonth = DateTime.DaysInMonth(finish.Year, finish.Month);

    var daysApartInStartMonth = (daysInStartMonth - start.Day + 1)/daysInStartMonth;
    var daysApartInFinishMonth = finish.Day/daysInFinishMonth;

    return monthsApart + daysApartInStartMonth + daysApartInFinishMonth;
}

【问题讨论】:

  • 感谢那些提供近似解决方案的人,但我需要一种优雅的方法来按日历月/年/日计算月差。这就是为什么我当前的解决方案包含 if else for each 和 while 循环。
  • 您的计算似乎假设结束时间包括当天的全部时间 - 这是您真正想要的吗? 3 月 15 日,DateTime 值的时间为午夜,这意味着在可能的 31 天中,3 月仅过去了 14 天。
  • @Hightechrider,开始日总是 00:00:00:1 毫秒,结束日总是 23:59:59:999。因此,如果开始和结束在同一天,则计为 1 天。

标签: c# .net datetime


【解决方案1】:

我之前给出了int 的答案,然后意识到您要求更准确的答案。我累了,所以我删除并上床睡觉。这么多,我无法入睡!出于某种原因,这个问题真的困扰着我,我不得不解决它。所以你去...

static void Main(string[] args)
{
    decimal diff;

    diff = monthDifference(new DateTime(2001, 1, 1), new DateTime(2002, 3, 15));
    Console.WriteLine(diff.ToString("n2")); //14.45

    diff = monthDifference(new DateTime(2001, 1, 1), new DateTime(2001, 4, 16));
    Console.WriteLine(diff.ToString("n2")); //3.50

    diff = monthDifference(new DateTime(2001, 7, 5), new DateTime(2002, 7, 10));
    Console.WriteLine(diff.ToString("n2")); //12.16

    Console.Read();
}

static decimal monthDifference(DateTime d1, DateTime d2)
{
    if (d1 > d2)
    {
        DateTime hold = d1;
        d1 = d2;
        d2 = hold;
    }

    int monthsApart = Math.Abs(12 * (d1.Year-d2.Year) + d1.Month - d2.Month) - 1;
    decimal daysInMonth1 = DateTime.DaysInMonth(d1.Year, d1.Month);
    decimal daysInMonth2 = DateTime.DaysInMonth(d2.Year, d2.Month);

    decimal dayPercentage = ((daysInMonth1 - d1.Day) / daysInMonth1)
                          + (d2.Day / daysInMonth2);
    return monthsApart + dayPercentage;
}

现在我将做个好梦。晚安:)

【讨论】:

  • 感谢您的回答。最初它似乎很完美,直到我进行了一系列测试。我注意到您假设 d1 始终是一个完整的日历月。因此,这不适用于以下情况:从 2001 年 4 月 15 日开始并在 2001 年 6 月 15 日结束。您的代码返回 2.5,但实际上它是 2 个半月和 1 个整月,应该返回 2 个月的差异。
  • 我找不到任何“简单”的解决方案,所以我从开始月份到结束月份逐月循环,如果是整月或部分月,则每个月都进行计算,然后总和。但我不知道是否存在任何更简单的解决方案。但是对于像 2001 年 7 月 10 日开始和 2002 年 7 月 9 日结束这样的情况,这仍然不起作用,这实际上应该返回 12 个月,但代码返回 11 点,所以我不得不单独手动处理这些情况。好痛苦。
  • 你指的是哪个公式?上面评论中描述的那个(愚蠢的)?
  • @Jeffrey 我稍微改进了函数,现在它更符合您的测试用例:)
  • 这个计算不正确。例如:2008-02-22 与 2016-12-23。结果将类似于 105.9,这是不正确的。它应该类似于 106.045。我在另一条评论中添加了正确的计算。
【解决方案2】:

你想要的可能是接近这个的东西......这几乎遵循你关于如何计算它的解释:

var startofd1 = d1.AddDays(-d1.Day + 1);
var startOfNextMonthAfterd1 = startofd1.AddMonths(1);      // back to start of month and then to next month
int daysInFirstMonth = (startOfNextMonthAfterd1 - startofd1).Days;
double fraction1 = (double)(daysInFirstMonth - (d1.Day - 1)) / daysInFirstMonth;     // fractional part of first month remaining

var startofd2 = d2.AddDays(-d2.Day + 1);
var startOfNextMonthAfterd2 = startofd2.AddMonths(1);      // back to start of month and then to next month
int daysInFinalMonth = (startOfNextMonthAfterd2 - startofd2).Days;
double fraction2 = (double)(d2.Day - 1) / daysInFinalMonth;     // fractional part of last month

// now find whole months in between
int monthsInBetween = (startofd2.Year - startOfNextMonthAfterd1.Year) * 12 + (startofd2.Month - startOfNextMonthAfterd1.Month);

return monthsInBetween + fraction1 + fraction2;

NB 这还没有经过很好的测试,但它展示了如何通过在问题值的月初找到众所周知的日期然后解决它们来处理此类问题。

虽然日期时间计算的循环总是一个坏主意:请参阅http://www.zuneboards.com/forums/zune-news/38143-cause-zune-30-leapyear-problem-isolated.html

【讨论】:

  • 如果开始和结束在同一个月内,这会起作用吗?
  • 它似乎在一个简短的测试中(monthsInBetween 变为-1)......找到一个它不起作用的例子,我会看看。就像我说的“这还没有经过很好的测试”,但它应该为您提供一个良好的开端,以非循环方式进行精确计算。
  • 你对 -1 的事情是正确的。我更新了问题以包含正确的方法(我相信)。现在我不确定谁的答案可以接受。
【解决方案3】:

根据你希望你的逻辑如何工作,这至少会给你一个不错的近似值:

// 365 days per year + 1 day per leap year = 1461 days every 4 years
// But years divisible by 100 are not leap years
// So 1461 days every 4 years - 1 day per 100th year = 36524 days every 100 years
// 12 months per year = 1200 months every 100 years
const double DaysPerMonth = 36524.0 / 1200.0;

double GetMonthsDifference(DateTime start, DateTime finish)
{
    double days = (finish - start).TotalDays;
    return days / DaysPerMonth;
}

【讨论】:

  • 它在数学上很聪明,但我认为它并不现实,因为它仍然是假设和近似值。
  • 杰弗里是对的。他希望 2 月 1 日和 3 月 1 日之间的时间差正好是 1 个月,但你称它为小于 1 个月。
  • @Jeffrey:这就是我说“体面近似”的原因(我在你明确你想要的结果之前发布了这个答案)。如果您愿意,这将是一个不错的选择,例如,将结果四舍五入到最接近的 0.5 个月。我会更新我的建议,但看起来你现在有很多其他想法。
  • @Gabe:是的,你是对的。只是我在明确要求明确要求之前发布了这个答案。抛开OP的方式只是一个想法。
  • @Dan Tao,你知道有时需要时间和人们的回答来缩小​​确切的要求。通过仅仅经历那个“整个过程”让我思考问题。但无论如何,谢谢你,你是对的,如果要求计算出大约几个月的差异,那么你的解决方案将是迄今为止最好的。
【解决方案4】:

做到这一点的一种方法是你会看到很多:

private static int monthDifference(DateTime startDate, DateTime endDate)
{
    int monthsApart = 12 * (startDate.Year - endDate.Year) + startDate.Month - endDate.Month;
    return Math.Abs(monthsApart);
}

但是,您想要这没有给出的“部分月份”。但是,将苹果(1 月/3 月/5 月/7 月/8 月/10 月/12 月)与橙子(4 月/6 月/9 月/11 月)甚至有时是椰子的香蕉(2 月)进行比较有什么意义呢?

An alternative 是导入 Microsoft.VisualBasic 并执行此操作:

    DateTime FromDate;
    DateTime ToDate;
    FromDate = DateTime.Parse("2001 Jan 01");
    ToDate = DateTime.Parse("2002 Mar 15");

    string s = DateAndTime.DateDiff (DateInterval.Month, FromDate,ToDate, FirstDayOfWeek.System, FirstWeekOfYear.System ).ToString();

但是又一次:

返回值 计算 DateInterval.Month 纯粹来自年份和月份部分 论据

[Source]

【讨论】:

    【解决方案5】:

    刚刚改进了 Josh 的回答

        static decimal monthDifference(DateTime d1, DateTime d2)
        {
            if (d1 > d2)
            {
                DateTime hold = d1;
                d1 = d2;
                d2 = hold;
            }
    
            decimal monthsApart = Math.Abs((12 * (d1.Year - d2.Year)) + d2.Month - d1.Month - 1);
    
    
            decimal daysinStartingMonth = DateTime.DaysInMonth(d1.Year, d1.Month);
            monthsApart = monthsApart + (1-((d1.Day - 1) / daysinStartingMonth));
    
            //  Replace (d1.Day - 1) with d1.Day incase you DONT want to have both inclusive difference.
    
    
    
            decimal daysinEndingMonth = DateTime.DaysInMonth(d2.Year, d2.Month);
            monthsApart = monthsApart + (d2.Day / daysinEndingMonth);
    
    
            return monthsApart;
        } 
    

    【讨论】:

      【解决方案6】:

      答案完美无缺,虽然代码的简洁性使其非常小,但我不得不将所有内容分解为带有命名变量的较小函数,以便我能够真正理解发生了什么......所以,基本上我只是带走了 Josh Stodola 的代码和 Hightechrider 的代码在 Jeff 的评论中提到,并用 cmets 解释发生了什么以及为什么进行计算,希望这可以帮助其他人:

          [Test]
          public void Calculate_Total_Months_Difference_Between_Two_Dates()
          {
              var startDate = DateTime.Parse( "10/8/1996" );
      
              var finishDate = DateTime.Parse( "9/8/2012" );  // this should be now:
      
      
              int numberOfMonthsBetweenStartAndFinishYears = getNumberOfMonthsBetweenStartAndFinishYears( startDate, finishDate );
      
      
              int absMonthsApartMinusOne = getAbsMonthsApartMinusOne( startDate, finishDate, numberOfMonthsBetweenStartAndFinishYears );
      
      
              decimal daysLeftToCompleteStartMonthPercentage = getDaysLeftToCompleteInStartMonthPercentage( startDate );
      
      
              decimal daysCompletedSoFarInFinishMonthPercentage = getDaysCompletedSoFarInFinishMonthPercentage( finishDate );
      
              // .77 + .26 = 1.04
              decimal totalDaysDifferenceInStartAndFinishMonthsPercentage = daysLeftToCompleteStartMonthPercentage + daysCompletedSoFarInFinishMonthPercentage;
      
      
              // 13 + 1.04 = 14.04 months difference.
              decimal totalMonthsDifference = absMonthsApartMinusOne + totalDaysDifferenceInStartAndFinishMonthsPercentage;
      
              //return totalMonths;
      
          }
      
          private static int getNumberOfMonthsBetweenStartAndFinishYears( DateTime startDate, DateTime finishDate )
          {
              int yearsApart = startDate.Year - finishDate.Year;
      
              const int INT_TotalMonthsInAYear = 12;
      
              // 12 * -1 = -12
              int numberOfMonthsBetweenYears = INT_TotalMonthsInAYear * yearsApart;
      
              return numberOfMonthsBetweenYears;
          }
      
          private static int getAbsMonthsApartMinusOne( DateTime startDate, DateTime finishDate, int numberOfMonthsBetweenStartAndFinishYears )
          {
              // This may be negative i.e. 7 - 9 = -2
              int numberOfMonthsBetweenStartAndFinishMonths = startDate.Month - finishDate.Month;
      
              // Absolute Value Of Total Months In Years Plus The Simple Months Difference Which May Be Negative So We Use Abs Function
              int absDiffInMonths = Math.Abs( numberOfMonthsBetweenStartAndFinishYears + numberOfMonthsBetweenStartAndFinishMonths );
      
              // Subtract one here because we are going to use a perecentage difference based on the number of days left in the start month
              // and adding together the number of days that we've made it so far in the finish month.
              int absMonthsApartMinusOne = absDiffInMonths - 1;
      
              return absMonthsApartMinusOne;
          }
      
          /// <summary>
          /// For example for 7/8/2012 there are 24 days left in the month so about .77 percentage of month is left.
          /// </summary>
          private static decimal getDaysLeftToCompleteInStartMonthPercentage( DateTime startDate )
          {
              // startDate = "7/8/2012"
      
              // 31
              decimal daysInStartMonth = DateTime.DaysInMonth( startDate.Year, startDate.Month );
      
              // 31 - 8 = 23 
              decimal totalDaysInStartMonthMinusStartDay = daysInStartMonth - startDate.Day;
      
              // add one to mark the day as being completed. 23 + 1 = 24
              decimal daysLeftInStartMonth = totalDaysInStartMonthMinusStartDay + 1;
      
              // 24 / 31 = .77 days left to go in the month
              decimal daysLeftToCompleteInStartMonthPercentage = daysLeftInStartMonth / daysInStartMonth;
      
              return daysLeftToCompleteInStartMonthPercentage;
          }
      
          /// <summary>
          /// For example if the finish date were 9/8/2012 we've completed 8 days so far or .24 percent of the month
          /// </summary>
          private static decimal getDaysCompletedSoFarInFinishMonthPercentage( DateTime finishDate )
          {
              // for septebmer = 30 days in month.
              decimal daysInFinishMonth = DateTime.DaysInMonth( finishDate.Year, finishDate.Month );
      
              // 8 days divided by 30 = .26 days completed so far in finish month.
              decimal daysCompletedSoFarInFinishMonthPercentage = finishDate.Day / daysInFinishMonth;
      
              return daysCompletedSoFarInFinishMonthPercentage;
          }
      

      【讨论】:

        【解决方案7】:

        此解决方案计算整个月份,然后根据时间段的结束添加部分月份。这样,它总是计算日期之间的完整月份,然后根据剩余天数计算部分月份。

        public decimal getMonthDiff(DateTime date1, DateTime date2) {
            // Make parameters agnostic
            var earlyDate = (date1 < date2 ? date1 : date2);
            var laterDate = (date1 > date2 ? date1 : date2);
        
            // Calculate the change in full months
            decimal months = ((laterDate.Year - earlyDate.Year) * 12) + (laterDate.Month - earlyDate.Month) - 1;
        
            // Add partial months based on the later date
            if (earlyDate.Day <= laterDate.Day) {
                decimal laterMonthDays = DateTime.DaysInMonth(laterDate.Year, laterDate.Month);
                decimal laterPartialMonth = ((laterDate.Day - earlyDate.Day) / laterMonthDays);
                months += laterPartialMonth + 1;
            } else {
                var laterLastMonth = laterDate.AddMonths(-1);
                decimal laterLastMonthDays = DateTime.DaysInMonth(laterLastMonth.Year, laterLastMonth.Month);
                decimal laterPartialMonth = ((laterLastMonthDays - earlyDate.Day + laterDate.Day) / laterLastMonthDays);
                months += laterPartialMonth;
            }
            return months;
        }
        

        【讨论】:

          【解决方案8】:

          下面的计算是根据荷兰税务局希望计算月份的方式。这意味着,例如,当开始日是 2 月 22 日时,3 月 23 日的结果应该高于 1,而不仅仅是 0.98。

              private decimal GetMonthDiffBetter(DateTime date1, DateTime date2)
              {
                  DateTime start = date1 < date2 ? date1 : date2;
                  DateTime end = date1 < date2 ? date2 : date1;
          
                  int totalYearMonths = (end.Year - start.Year) * 12;
                  int restMonths = end.Month - start.Month;
                  int totalMonths = totalYearMonths + restMonths;
          
                  decimal monthPart = (decimal)end.Day / (decimal)start.Day;
                  return totalMonths - 1 + monthPart;
              }`
          

          【讨论】:

            【解决方案9】:

            这应该可以带你去你需要去的地方:

            DateTime start = new DateTime(2001, 1, 1);
            DateTime finish = new DateTime(2002, 3, 15);
            double diff = (finish - start).TotalDays / 30;
            

            【讨论】:

            • 这里不考虑每个月天数的变化。
            【解决方案10】:

            作为 TimeSpan 对象的框架,它是减去两个日期的结果。

            减法已经在考虑 2 月的各种选项(每月 28/29 天)所以我认为这是最佳做法 得到它后,您可以按照自己喜欢的方式对其进行格式化

                    DateTime dates1 = new DateTime(2010, 1, 1);
                    DateTime dates2 = new DateTime(2010, 3, 15);
                    var span = dates1.Subtract(dates2);
                    span.ToString("your format here");
            

            【讨论】:

            • 计算部分月份需要该月的天数。将日期减去 TimeSpan 后,该月的天数就会丢失。
            【解决方案11】:
                private Double GetTotalMonths(DateTime future, DateTime past)
                {
                    Double totalMonths = 0.0;
            
                    while ((future - past).TotalDays > 28 )
                    {
                        past = past.AddMonths(1);
                        totalMonths += 1;
                    }
            
                    var daysInCurrent = DateTime.DaysInMonth(future.Year, future.Month);
                    var remaining = future.Day - past.Day;
            
                    totalMonths += ((Double)remaining / (Double)daysInCurrent);
                    return totalMonths;
                }
            

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 2010-12-04
              • 2014-04-07
              • 2022-10-02
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多