两个日期之间的月份差异

Rau*_*auf 306 .net c# vb.net date

如何计算C#中两个日期之间的月份差异?

DateDiff()在C#中是否有相当于VB的方法.我需要找出两个相隔数年的日期之间的差异.文档说我可以使用TimeSpan:

TimeSpan ts = date1 - date2;
Run Code Online (Sandbox Code Playgroud)

但这给了我几天的数据.我不想将这个数字除以30,因为不是每个月都是30天,而且因为两个操作数值彼此相距很远,我担心除以30可能会给我一个错误的值.

有什么建议?

Ada*_*lph 434

假设月份的日期无关紧要(即2011.1.1和2010.12.31之间的差异为1),date1> date2给出正值,date2> date1为负值

((date1.Year - date2.Year) * 12) + date1.Month - date2.Month
Run Code Online (Sandbox Code Playgroud)

或者,假设您希望两个日期之间的"平均月份"大致相同,则以下内容应适用于所有但非常大的日期差异.

date1.Subtract(date2).Days / (365.25 / 12)
Run Code Online (Sandbox Code Playgroud)

请注意,如果您要使用后一种解决方案,那么您的单元测试应说明您的应用程序设计使用的最宽日期范围,并相应地验证计算结果.


更新(感谢Gary)

如果使用"平均月份"方法,则用于"每年平均天数"的稍微准确的数字是365.2425.

  • 我认为有必要考虑Day组件.像这样的东西`(date1.Year - date2.Year)*12 + date1.Month - date2.Month +(date1.Day> = date2.Day?0:-1)` (18认同)
  • 为了说明亚当说的话,我花了数年时间为Acturaries编写代码.一些计算是按天数划分的,每周计算得到30个.有时计算月份假定_每个日期从本月的第一天开始,相应地计算整月.在计算日期时没有_best_方法.除非你**是你正在为其编写代码的客户,否则请将其链接起来,并将其澄清,可能是您的客户会计. (6认同)
  • @Kurru - 365/12只是一个月平均长度的近似度量.这是一个不准确的措施.对于较小的日期范围,可以容忍这种不准确性,但是对于非常大的日期范围,这种不准确性可能变得很大. (3认同)
  • 365.2425 是公历中稍微更准确的天数(如果您使用的是公历)。然而,按照 DateTime.MaxValue(10000 年 1 月 1 日)计算,只有大约 59 天的差异。此外,根据您的观点,一年的定义可能会有很大不同https://en.wikipedia.org/wiki/Year。 (3认同)
  • @DrunkCoder取决于给定系统的要求。在某些情况下,您的解决方案可能确实是最佳选择。例如,重要的是要考虑两个日期跨越一个月的31天,一个月的30天,2月的28天或2月的29天时会发生什么。如果公式结果满足系统要求,那么显然是正确的选择。如果不是,则需要其他的东西。 (2认同)

Kir*_*oll 199

这是一个全面的解决方案,返回a DateTimeSpan,类似于a TimeSpan,除了它包括除时间组件之外的所有日期组件.

用法:

void Main()
{
    DateTime compareTo = DateTime.Parse("8/13/2010 8:33:21 AM");
    DateTime now = DateTime.Parse("2/9/2012 10:10:11 AM");
    var dateSpan = DateTimeSpan.CompareDates(compareTo, now);
    Console.WriteLine("Years: " + dateSpan.Years);
    Console.WriteLine("Months: " + dateSpan.Months);
    Console.WriteLine("Days: " + dateSpan.Days);
    Console.WriteLine("Hours: " + dateSpan.Hours);
    Console.WriteLine("Minutes: " + dateSpan.Minutes);
    Console.WriteLine("Seconds: " + dateSpan.Seconds);
    Console.WriteLine("Milliseconds: " + dateSpan.Milliseconds);
}
Run Code Online (Sandbox Code Playgroud)

输出:

年:1
个月:5
天:27
小时:1
分钟:36
秒:50
毫秒:0

为方便起见,我将逻辑集中到DateTimeSpan结构中,但您可以将方法移动到CompareDates您认为合适的位置.另请注意,哪一个日期先于另一个日期并不重要.

public struct DateTimeSpan
{
    public int Years { get; }
    public int Months { get; }
    public int Days { get; }
    public int Hours { get; }
    public int Minutes { get; }
    public int Seconds { get; }
    public int Milliseconds { get; }

    public DateTimeSpan(int years, int months, int days, int hours, int minutes, int seconds, int milliseconds)
    {
        Years = years;
        Months = months;
        Days = days;
        Hours = hours;
        Minutes = minutes;
        Seconds = seconds;
        Milliseconds = milliseconds;
    }

    enum Phase { Years, Months, Days, Done }

    public static DateTimeSpan CompareDates(DateTime date1, DateTime date2)
    {
        if (date2 < date1)
        {
            var sub = date1;
            date1 = date2;
            date2 = sub;
        }

        DateTime current = date1;
        int years = 0;
        int months = 0;
        int days = 0;

        Phase phase = Phase.Years;
        DateTimeSpan span = new DateTimeSpan();
        int officialDay = current.Day;

        while (phase != Phase.Done)
        {
            switch (phase)
            {
                case Phase.Years:
                    if (current.AddYears(years + 1) > date2)
                    {
                        phase = Phase.Months;
                        current = current.AddYears(years);
                    }
                    else
                    {
                        years++;
                    }
                    break;
                case Phase.Months:
                    if (current.AddMonths(months + 1) > date2)
                    {
                        phase = Phase.Days;
                        current = current.AddMonths(months);
                        if (current.Day < officialDay && officialDay <= DateTime.DaysInMonth(current.Year, current.Month))
                            current = current.AddDays(officialDay - current.Day);
                    }
                    else
                    {
                        months++;
                    }
                    break;
                case Phase.Days:
                    if (current.AddDays(days + 1) > date2)
                    {
                        current = current.AddDays(days);
                        var timespan = date2 - current;
                        span = new DateTimeSpan(years, months, days, timespan.Hours, timespan.Minutes, timespan.Seconds, timespan.Milliseconds);
                        phase = Phase.Done;
                    }
                    else
                    {
                        days++;
                    }
                    break;
            }
        }

        return span;
    }
}
Run Code Online (Sandbox Code Playgroud)

  • @KirkWoll 谢谢。但是为什么 DateTimeSpan 为这个日期时间差返回 `34` 天实际上是 `35` http://www.timeanddate.com/date/durationresult.html?d26=01&amp;m1=11&amp;y1=2012&amp;d2=31&amp;m2=12&amp;y2=2012 (2认同)
  • 我为一个类似的问题写了一个答案http://stackoverflow.com/a/17537472/1737957,该问题测试了建议的答案(并发现其中大多数不起作用)。这个答案是为数不多的有效答案之一(根据我的测试套件)。我的回答中有 github 链接。 (2认同)

Mon*_*ong 35

你可以做到

if ( date1.AddMonths(x) > date2 )
Run Code Online (Sandbox Code Playgroud)

  • 如果 date1 = 2018-10-28 和 date2 = 2018-12-21 呢?答案将是 2。而正确答案应该是 3。因为日期范围是 3 个月。如果我们只计算月份而忽略天数。所以这个答案是不正确的。 (3认同)
  • 我是否遗漏了一些东西......这是对日期是否至少相差给定月份数的真/假检查,而不是对该月份数的计算,这正是我认为o/p要求的。 (2认同)

Gui*_*e86 32

如果你想要确切的完整月数,总是积极的(2000-01-15,2000-02-14返回0),考虑整个月是你到达下个月的同一天(类似于年龄计算)

public static int GetMonthsBetween(DateTime from, DateTime to)
{
    if (from > to) return GetMonthsBetween(to, from);

    var monthDiff = Math.Abs((to.Year * 12 + (to.Month - 1)) - (from.Year * 12 + (from.Month - 1)));

    if (from.AddMonths(monthDiff) > to || to.Day < from.Day)
    {
        return monthDiff - 1;
    }
    else
    {
        return monthDiff;
    }
}
Run Code Online (Sandbox Code Playgroud)

编辑原因:旧代码在某些情况下不正确,例如:

new { From = new DateTime(1900, 8, 31), To = new DateTime(1901, 8, 30), Result = 11 },

Test cases I used to test the function:

var tests = new[]
{
    new { From = new DateTime(1900, 1, 1), To = new DateTime(1900, 1, 1), Result = 0 },
    new { From = new DateTime(1900, 1, 1), To = new DateTime(1900, 1, 2), Result = 0 },
    new { From = new DateTime(1900, 1, 2), To = new DateTime(1900, 1, 1), Result = 0 },
    new { From = new DateTime(1900, 1, 1), To = new DateTime(1900, 2, 1), Result = 1 },
    new { From = new DateTime(1900, 2, 1), To = new DateTime(1900, 1, 1), Result = 1 },
    new { From = new DateTime(1900, 1, 31), To = new DateTime(1900, 2, 1), Result = 0 },
    new { From = new DateTime(1900, 8, 31), To = new DateTime(1900, 9, 30), Result = 0 },
    new { From = new DateTime(1900, 8, 31), To = new DateTime(1900, 10, 1), Result = 1 },
    new { From = new DateTime(1900, 1, 1), To = new DateTime(1901, 1, 1), Result = 12 },
    new { From = new DateTime(1900, 1, 1), To = new DateTime(1911, 1, 1), Result = 132 },
    new { From = new DateTime(1900, 8, 31), To = new DateTime(1901, 8, 30), Result = 11 },
};
Run Code Online (Sandbox Code Playgroud)

  • 在我看来,这是预期的行为,或者至少是我期望的行为.我准备了整整一个月,当你到达同一天(或下个月,就像在这种情况下). (2认同)

Che*_*hen 22

我通过MSDN在VB.NET中检查了这个方法的用法,似乎它有很多用法.C#中没有这样的内置方法.(即使这不是一个好主意)你可以用C#调用VB.

  1. 添加Microsoft.VisualBasic.dll到您的项目作为参考
  2. 使用 Microsoft.VisualBasic.DateAndTime.DateDiff 在你的代码

  • 为什么你认为这不是一个好主意?直观地说,我猜这个库只是运行时的另一个.NET库.请注意,我在这里扮演魔鬼的拥护者,我也不愿意这样做,因为它只是"感觉不对"(作弊)但我想知道是否有任何令人信服的技术理由不这样做. (7认同)
  • @AdamRalph:没有理由不去做.这些库是用100%托管代码实现的,所以它与其他所有代码都是一样的.唯一可以想象的区别是必须加载"Microsoft.VisualBasic.dll"模块,但是这样做的时间可以忽略不计.没有理由因为你选择用C#编写程序而欺骗自己完全测试和有用的功能.(这也适用于`My.Application.SplashScreen`之类的东西.) (3认同)
  • 如果你知道它是用C#编写的,你会改变主意吗?它是.通过相同的逻辑,使用System.Data和PresentationFramework也是作弊,它的大部分内容都是用C++/CLI编写的. (3认同)
  • @AdamRalph:脑海里出现的那种"奇怪的包袱"有什么特别的例子吗?或者你是说纯粹假设?是的,它可能会破坏你的一些C#伙伴们的思想,他们一直在写一些史诗般的代码来执行一些你可以用正确的"using"语句在一行中做的事情,但我怀疑会有什么严重的伤害. (3认同)

Chi*_*rag 10

无论日期如何,要在几个月内(包括开始和结束)获得差异:

DateTime start = new DateTime(2013, 1, 1);
DateTime end = new DateTime(2014, 2, 1);
var diffMonths = (end.Month + end.Year * 12) - (start.Month + start.Year * 12);
Run Code Online (Sandbox Code Playgroud)

  • 想象一下,`start`和`end`是完全相同的.然后你得到的结果是1.这是怎么回事?为什么在结果中加1?谁在投票给这个答案: - /? (4认同)
  • 听起来不像两件物品给我的区别.2和2有什么区别?真的是1吗?我建议差异是0. (3认同)

jen*_*ent 6

我只需要一些简单的东西来满足例如只输入月份/年份的就业日期,因此需要不同的年份和月份.这就是我使用的,这里仅用于实用性

public static YearsMonths YearMonthDiff(DateTime startDate, DateTime endDate) {
    int monthDiff = ((endDate.Year * 12) + endDate.Month) - ((startDate.Year * 12) + startDate.Month) + 1;
    int years = (int)Math.Floor((decimal) (monthDiff / 12));
    int months = monthDiff % 12;
    return new YearsMonths {
        TotalMonths = monthDiff,
            Years = years,
            Months = months
    };
}
Run Code Online (Sandbox Code Playgroud)

.NET小提琴


Edw*_*rey 6

使用Noda时间

LocalDate start = new LocalDate(2013, 1, 5);
LocalDate end = new LocalDate(2014, 6, 1);
Period period = Period.Between(start, end, PeriodUnits.Months);
Console.WriteLine(period.Months); // 16
Run Code Online (Sandbox Code Playgroud)

(示例来源)


Mor*_*rgs 5

以下是我为获得准确的月份差异所做的贡献:

namespace System
{
     public static class DateTimeExtensions
     {
         public static Int32 DiffMonths( this DateTime start, DateTime end )
         {
             Int32 months = 0;
             DateTime tmp = start;

             while ( tmp < end )
             {
                 months++;
                 tmp = tmp.AddMonths( 1 );
             }

             return months;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

用法:

Int32 months = DateTime.Now.DiffMonths( DateTime.Now.AddYears( 5 ) );
Run Code Online (Sandbox Code Playgroud)

您可以创建另一个名为 DiffYears 的方法,并应用与上面完全相同的逻辑,并在 while 循环中应用 AddYears 而不是 AddMonths。