如何在c#中计算两个日期之间的月差?

c#中是否有相当于VB的DateDiff()方法。我需要找出相隔数年的两个日期之间的月差。文档说我可以像这样使用TimeSpan:

TimeSpan ts = date1 - date2;

但这里的数据是以天为单位的。我不想把这个数字除以30,因为不是每个月都是30天,而且两个操作数的值相差很大,所以我担心除以30可能会得到错误的值。

有什么建议吗?


当前回答

假设这个月的日期不相关(即2011.1.1和2010.12.31之间的差为1),date1 > date2为正值,date2 > date1为负值

((date1.Year - date2.Year) * 12) + date1.Month - date2.Month

或者,假设你想要两个日期之间的“平均月”的大致数字,下面的方法应该适用于所有日期,但日期差异非常大。

date1.Subtract(date2).Days / (365.25 / 12)

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


更新(感谢Gary)

如果使用“平均月份”方法,“每年平均天数”的更准确数字是365.2425。

其他回答

你可以这样做

if ( date1.AddMonths(x) > date2 )

假设这个月的日期不相关(即2011.1.1和2010.12.31之间的差为1),date1 > date2为正值,date2 > date1为负值

((date1.Year - date2.Year) * 12) + date1.Month - date2.Month

或者,假设你想要两个日期之间的“平均月”的大致数字,下面的方法应该适用于所有日期,但日期差异非常大。

date1.Subtract(date2).Days / (365.25 / 12)

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


更新(感谢Gary)

如果使用“平均月份”方法,“每年平均天数”的更准确数字是365.2425。

扩展的Kirks结构与ToString(格式)和持续时间(长ms)

 public struct DateTimeSpan
{
    private readonly int years;
    private readonly int months;
    private readonly int days;
    private readonly int hours;
    private readonly int minutes;
    private readonly int seconds;
    private readonly int milliseconds;

    public DateTimeSpan(int years, int months, int days, int hours, int minutes, int seconds, int milliseconds)
    {
        this.years = years;
        this.months = months;
        this.days = days;
        this.hours = hours;
        this.minutes = minutes;
        this.seconds = seconds;
        this.milliseconds = milliseconds;
    }

    public int Years { get { return years; } }
    public int Months { get { return months; } }
    public int Days { get { return days; } }
    public int Hours { get { return hours; } }
    public int Minutes { get { return minutes; } }
    public int Seconds { get { return seconds; } }
    public int Milliseconds { get { return milliseconds; } }

    enum Phase { Years, Months, Days, Done }


    public string ToString(string format)
    {
        format = format.Replace("YYYY", Years.ToString());
        format = format.Replace("MM", Months.ToString());
        format = format.Replace("DD", Days.ToString());
        format = format.Replace("hh", Hours.ToString());
        format = format.Replace("mm", Minutes.ToString());
        format = format.Replace("ss", Seconds.ToString());
        format = format.Replace("ms", Milliseconds.ToString());
        return format;
    }


    public static DateTimeSpan Duration(long ms)
    {
        DateTime dt = new DateTime();
        return CompareDates(dt, dt.AddMilliseconds(ms));
    }


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

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

似乎DateTimeSpan解决方案使许多人满意。我不知道。让我们考虑一下:

BeginDate = 1972/2/29销售= 1972/4/28。

基于DateTimeSpan的答案是:

1年(s), 2个月(s)和0天(s)

我实现了一个方法,在此基础上,答案是:

1年、1个月及28天

显然没有两个月的时间。我想说的是,因为我们在开始日期的月末,剩下的实际上是整个3月加上结束日期(4月)的月份所经过的天数,所以1个月零28天。

如果你读到这里,你有兴趣,我把方法贴在下面。我在评论中解释了我所做的假设,因为有多少个月,月份的概念是一个不断变化的目标。多次测试,看看答案是否有意义。我通常选择相邻年份的考试日期,一旦我确认了答案,我就会前后移动一两天。到目前为止,它看起来不错,我相信你会发现一些bug:D。代码可能看起来有点粗糙,但我希望它足够清楚:

static void Main(string[] args) {
        DateTime EndDate = new DateTime(1973, 4, 28);
        DateTime BeginDate = new DateTime(1972, 2, 29);
        int years, months, days;
        GetYearsMonthsDays(EndDate, BeginDate, out years, out months, out days);
        Console.WriteLine($"{years} year(s), {months} month(s) and {days} day(s)");
    }

    /// <summary>
    /// Calculates how many years, months and days are between two dates.
    /// </summary>
    /// <remarks>
    /// The fundamental idea here is that most of the time all of us agree
    /// that a month has passed today since the same day of the previous month.
    /// A particular case is when both days are the last days of their respective months 
    /// when again we can say one month has passed.
    /// In the following cases the idea of a month is a moving target.
    /// - When only the beginning date is the last day of the month then we're left just with 
    /// a number of days from the next month equal to the day of the month that end date represent
    /// - When only the end date is the last day of its respective month we clearly have a 
    /// whole month plus a few days after the the day of the beginning date until the end of its
    /// respective months
    /// In all the other cases we'll check
    /// - beginingDay > endDay -> less then a month just daysToEndofBeginingMonth + dayofTheEndMonth
    /// - beginingDay < endDay -> full month + (endDay - beginingDay)
    /// - beginingDay == endDay -> one full month 0 days
    /// 
    /// </remarks>
    /// 
    private static void GetYearsMonthsDays(DateTime EndDate, DateTime BeginDate, out int years, out int months, out int days ) {
        var beginMonthDays = DateTime.DaysInMonth(BeginDate.Year, BeginDate.Month);
        var endMonthDays = DateTime.DaysInMonth(EndDate.Year, EndDate.Month);
        // get the full years
        years = EndDate.Year - BeginDate.Year - 1;
        // how many full months in the first year
        var firstYearMonths = 12 - BeginDate.Month;
        // how many full months in the last year
        var endYearMonths = EndDate.Month - 1;
        // full months
        months = firstYearMonths + endYearMonths;           
        days = 0;
        // Particular end of month cases
        if(beginMonthDays == BeginDate.Day && endMonthDays == EndDate.Day) {
            months++;
        }
        else if(beginMonthDays == BeginDate.Day) {
            days += EndDate.Day;
        }
        else if(endMonthDays == EndDate.Day) {
            days += beginMonthDays - BeginDate.Day;
        }
        // For all the other cases
        else if(EndDate.Day > BeginDate.Day) {
            months++;
            days += EndDate.Day - BeginDate.Day;
        }
        else if(EndDate.Day < BeginDate.Day) {                
            days += beginMonthDays - BeginDate.Day;
            days += EndDate.Day;
        }
        else {
            months++;
        }
        if(months >= 12) {
            years++;
            months = months - 12;
        }
    }

你可以使用野田时间https://nodatime.org/

LocalDate start = new LocalDate(2010, 1, 5);
LocalDate end = new LocalDate(2012, 6, 1);
Period period = Period.Between(start, end, PeriodUnits.Months);
Console.WriteLine(period.Months);