141

在 C#/.NETTimeSpan中有TotalDays,TotalMinutes等,但我无法计算出总月差的公式。每月变化的天数和闰年不断让我失望。我怎样才能得到TotalMonths

编辑抱歉没有更清楚:我知道我实际上无法从中得到这个,TimeSpan但我认为使用TotalDaysandTotalMinutes将是一个很好的例子来表达我正在寻找的东西......除了我试图获得 Total Months 。

示例:2009 年 12 月 25 日 - 2009 年 10 月 6 日 = 2 TotalMonths。10 月 6 日至 11 月 5 日等于 0 个月。11月6日,1个月。12月6日,2个月

4

27 回答 27

243

您将无法从 a 中得到它TimeSpan,因为“月”是一个可变的计量单位。您必须自己计算,并且必须弄清楚您希望它如何工作。

例如,日期应该July 5, 2009August 4, 2009一个月还是零个月?如果你说它应该产生一个,那么July 31, 2009andAugust 1, 2009呢?是一个月吗它只是Month日期值的差异,还是与实际时间跨度更相关?确定所有这些规则的逻辑非常重要,因此您必须确定自己的规则并实施适当的算法。

如果你想要的只是几个月的差异——完全不考虑日期值——那么你可以使用这个:

public static int MonthDifference(this DateTime lValue, DateTime rValue)
{
    return (lValue.Month - rValue.Month) + 12 * (lValue.Year - rValue.Year);
}

请注意,这会返回一个相对差异,这意味着如果rValue大于lValue,则返回值将为负数。如果你想要一个绝对的差异,你可以使用这个:

public static int MonthDifference(this DateTime lValue, DateTime rValue)
{
    return Math.Abs((lValue.Month - rValue.Month) + 12 * (lValue.Year - rValue.Year));
}
于 2009-10-06T14:39:26.997 回答
56

(我意识到这是一个老问题,但是......)

这在纯 .NET 中是相对痛苦的。我会推荐我自己的Noda Time库,它是专门为这样的事情设计的:

LocalDate start = new LocalDate(2009, 10, 6);
LocalDate end = new LocalDate(2009, 12, 25);
Period period = Period.Between(start, end);
int months = period.Months;

(还有其他选项,例如,如果您只想要跨年数月,您可以使用Period period = Period.Between(start, end, PeriodUnits.Months);

于 2014-08-12T12:24:34.877 回答
28

也许您不想了解月份分数;这段代码呢?


public static class DateTimeExtensions
{
    public static int TotalMonths(this DateTime start, DateTime end)
    {
        return (start.Year * 12 + start.Month) - (end.Year * 12 + end.Month);
    }
}

//  Console.WriteLine(
//     DateTime.Now.TotalMonths(
//         DateTime.Now.AddMonths(-1))); // prints "1"


于 2009-10-06T15:04:15.027 回答
14

我已经写了一个非常简单的扩展方法DateTimeDateTimeOffset做到这一点。我希望它的工作方式与TotalMonths属性的工作方式完全一样TimeSpan:即返回两个日期之间的完整月份数,忽略任何部分月份。因为它基于DateTime.AddMonths()它尊重不同的月份长度并返回人类理解为几个月的时间。

(不幸的是,您不能将它作为 TimeSpan 上的扩展方法来实现,因为它不会保留所使用的实际日期的知识,而且几个月来它们很重要。)

代码和测试都可以在 GitHub 上找到。代码非常简单:

public static int GetTotalMonthsFrom(this DateTime dt1, DateTime dt2)
{
    DateTime earlyDate = (dt1 > dt2) ? dt2.Date : dt1.Date;
    DateTime lateDate = (dt1 > dt2) ? dt1.Date : dt2.Date;

    // Start with 1 month's difference and keep incrementing
    // until we overshoot the late date
    int monthsDiff = 1;
    while (earlyDate.AddMonths(monthsDiff) <= lateDate)
    {
        monthsDiff++;
    }

    return monthsDiff - 1;
}

它通过了所有这些单元测试用例:

// Simple comparison
Assert.AreEqual(1, new DateTime(2014, 1, 1).GetTotalMonthsFrom(new DateTime(2014, 2, 1)));
// Just under 1 month's diff
Assert.AreEqual(0, new DateTime(2014, 1, 1).GetTotalMonthsFrom(new DateTime(2014, 1, 31)));
// Just over 1 month's diff
Assert.AreEqual(1, new DateTime(2014, 1, 1).GetTotalMonthsFrom(new DateTime(2014, 2, 2)));
// 31 Jan to 28 Feb
Assert.AreEqual(1, new DateTime(2014, 1, 31).GetTotalMonthsFrom(new DateTime(2014, 2, 28)));
// Leap year 29 Feb to 29 Mar
Assert.AreEqual(1, new DateTime(2012, 2, 29).GetTotalMonthsFrom(new DateTime(2012, 3, 29)));
// Whole year minus a day
Assert.AreEqual(11, new DateTime(2012, 1, 1).GetTotalMonthsFrom(new DateTime(2012, 12, 31)));
// Whole year
Assert.AreEqual(12, new DateTime(2012, 1, 1).GetTotalMonthsFrom(new DateTime(2013, 1, 1)));
// 29 Feb (leap) to 28 Feb (non-leap)
Assert.AreEqual(12, new DateTime(2012, 2, 29).GetTotalMonthsFrom(new DateTime(2013, 2, 28)));
// 100 years
Assert.AreEqual(1200, new DateTime(2000, 1, 1).GetTotalMonthsFrom(new DateTime(2100, 1, 1)));
// Same date
Assert.AreEqual(0, new DateTime(2014, 8, 5).GetTotalMonthsFrom(new DateTime(2014, 8, 5)));
// Past date
Assert.AreEqual(6, new DateTime(2012, 1, 1).GetTotalMonthsFrom(new DateTime(2011, 6, 10)));
于 2014-08-05T10:08:14.067 回答
9

您必须首先定义 TotalMonths 的含义。
一个简单的定义是一个月为 30.4 天 (365.25 / 12)。

除此之外,任何包括分数的定义似乎都没有用,更常见的整数值(日期之间的整月)也取决于非标准的业务规则。

于 2009-10-06T14:41:21.087 回答
8

您需要根据日期时间自行解决。最后你如何处理存根日将取决于你想用它做什么。

一种方法是计算月份,然后在最后修正天数。就像是:

   DateTime start = new DateTime(2003, 12, 25);
   DateTime end = new DateTime(2009, 10, 6);
   int compMonth = (end.Month + end.Year * 12) - (start.Month + start.Year * 12);
   double daysInEndMonth = (end - end.AddMonths(1)).Days;
   double months = compMonth + (start.Day - end.Day) / daysInEndMonth;
于 2009-10-06T14:55:02.813 回答
3

我会这样做:

static int TotelMonthDifference(this DateTime dtThis, DateTime dtOther)
{
    int intReturn = 0;

    dtThis = dtThis.Date.AddDays(-(dtThis.Day-1));
    dtOther = dtOther.Date.AddDays(-(dtOther.Day-1));

    while (dtOther.Date > dtThis.Date)
    {
        intReturn++;     
        dtThis = dtThis.AddMonths(1);
    }

    return intReturn;
}
于 2009-10-06T14:45:37.257 回答
3

对此没有很多明确的答案,因为您总是在假设事情。

此解决方案计算两个日期之间的月份,假设您要保存月份中的某天以进行比较,(意味着在计算中考虑了该月的某天)

例如,如果您的日期为 2012 年 1 月 30 日,则 2012 年 2 月 29 日不是一个月,而是 2013 年 3 月 1 日。

它已经过非常彻底的测试,可能会在我们使用它的时候清理它,并且使用两个日期而不是时间跨度,这可能更好。希望这对其他人有帮助。

private static int TotalMonthDifference(DateTime dtThis, DateTime dtOther)
{
    int intReturn = 0;
    bool sameMonth = false;

    if (dtOther.Date < dtThis.Date) //used for an error catch in program, returns -1
        intReturn--;

    int dayOfMonth = dtThis.Day; //captures the month of day for when it adds a month and doesn't have that many days
    int daysinMonth = 0; //used to caputre how many days are in the month

    while (dtOther.Date > dtThis.Date) //while Other date is still under the other
    {
        dtThis = dtThis.AddMonths(1); //as we loop, we just keep adding a month for testing
        daysinMonth = DateTime.DaysInMonth(dtThis.Year, dtThis.Month); //grabs the days in the current tested month

        if (dtThis.Day != dayOfMonth) //Example 30 Jan 2013 will go to 28 Feb when a month is added, so when it goes to march it will be 28th and not 30th
        {
            if (daysinMonth < dayOfMonth) // uses day in month max if can't set back to day of month
                dtThis.AddDays(daysinMonth - dtThis.Day);
            else
                dtThis.AddDays(dayOfMonth - dtThis.Day);
        }
        if (((dtOther.Year == dtThis.Year) && (dtOther.Month == dtThis.Month))) //If the loop puts it in the same month and year
        {
            if (dtOther.Day >= dayOfMonth) //check to see if it is the same day or later to add one to month
                intReturn++;
            sameMonth = true; //sets this to cancel out of the normal counting of month
        }
        if ((!sameMonth)&&(dtOther.Date > dtThis.Date))//so as long as it didn't reach the same month (or if i started in the same month, one month ahead, add a month)
            intReturn++;
    }
    return intReturn; //return month
}
于 2014-01-07T18:55:31.370 回答
3

当您想要整整几个月时,接受的答案非常有效。

我需要半个月。这是我想出了几个月的解决方案:

    /// <summary>
    /// Calculate the difference in months.
    /// This will round up to count partial months.
    /// </summary>
    /// <param name="lValue"></param>
    /// <param name="rValue"></param>
    /// <returns></returns>
    public static int MonthDifference(DateTime lValue, DateTime rValue)
    {
        var yearDifferenceInMonths = (lValue.Year - rValue.Year) * 12;
        var monthDifference = lValue.Month - rValue.Month;

        return yearDifferenceInMonths + monthDifference + 
            (lValue.Day > rValue.Day
                ? 1 : 0); // If end day is greater than start day, add 1 to round up the partial month
    }

我还需要与部分年份相同的年份差异。这是我想出的解决方案:

    /// <summary>
    /// Calculate the differences in years.
    /// This will round up to catch partial months.
    /// </summary>
    /// <param name="lValue"></param>
    /// <param name="rValue"></param>
    /// <returns></returns>
    public static int YearDifference(DateTime lValue, DateTime rValue)
    {
        return lValue.Year - rValue.Year +
               (lValue.Month > rValue.Month // Partial month, same year
                   ? 1
                   : ((lValue.Month = rValue.Month) 
                     && (lValue.Day > rValue.Day)) // Partial month, same year and month
                   ? 1 : 0);
    }
于 2018-06-26T13:44:04.740 回答
2

我知道的老问题,但可能会帮助某人。我在上面使用了@Adam 接受的答案,但随后检查了差异是 1 还是 -1,然后检查它是否是一个完整日历月的差异。所以 21/07/55 和 20/08/55 不会是一个完整的月份,但 21/07/55 和 21/07/55 会是。

/// <summary>
/// Amended date of birth cannot be greater than or equal to one month either side of original date of birth.
/// </summary>
/// <param name="dateOfBirth">Date of birth user could have amended.</param>
/// <param name="originalDateOfBirth">Original date of birth to compare against.</param>
/// <returns></returns>
public JsonResult ValidateDateOfBirth(string dateOfBirth, string originalDateOfBirth)
{
    DateTime dob, originalDob;
    bool isValid = false;

    if (DateTime.TryParse(dateOfBirth, out dob) && DateTime.TryParse(originalDateOfBirth, out originalDob))
    {
        int diff = ((dob.Month - originalDob.Month) + 12 * (dob.Year - originalDob.Year));

        switch (diff)
        {
            case 0:
                // We're on the same month, so ok.
                isValid = true;
                break;
            case -1:
                // The month is the previous month, so check if the date makes it a calendar month out.
                isValid = (dob.Day > originalDob.Day);
                break;
            case 1:
                // The month is the next month, so check if the date makes it a calendar month out.
                isValid = (dob.Day < originalDob.Day);
                break;
            default:
                // Either zero or greater than 1 month difference, so not ok.
                isValid = false;
                break;
        }
        if (!isValid)
            return Json("Date of Birth cannot be greater than one month either side of the date we hold.", JsonRequestBehavior.AllowGet);
    }
    else
    {
        return Json("Date of Birth is invalid.", JsonRequestBehavior.AllowGet);
    }
    return Json(true, JsonRequestBehavior.AllowGet);
}
于 2011-09-01T13:20:46.253 回答
2
case IntervalType.Month:
    returnValue = start.AddMonths(-end.Month).Month.ToString();
    break;
case IntervalType.Year:
    returnValue = (start.Year - end.Year).ToString();
    break;
于 2012-11-07T19:23:44.600 回答
1

几个月的问题在于它实际上并不是一个简单的衡量标准——它们的大小不是恒定的。您需要为要包含的内容定义规则,然后从那里开始工作。例如 1 月 1 日至 2 月 1 日 - 你可以说那里涉及 2 个月,或者你可以说是 1 个月。那么“1 Jan 20:00”到“1 Feb 00:00”呢?那还不是整整一个月。那是0吗?1?反过来呢(1 月 1 日 00:00 至 2 月 1 日 20:00)... 1?2?

首先定义规则,然后你必须自己编写代码,恐怕......

于 2011-03-17T12:36:56.090 回答
1

如果您想在and1之间得到结果: 28th Feb1st March

DateTime date1, date2;
int monthSpan = (date2.Year - date1.Year) * 12 + date2.Month - date1.Month
于 2011-03-17T12:37:02.197 回答
1

这个库计算月份的差异,考虑到 DateTime 的所有部分:

// ----------------------------------------------------------------------
public void DateDiffSample()
{
  DateTime date1 = new DateTime( 2009, 11, 8, 7, 13, 59 );
  Console.WriteLine( "Date1: {0}", date1 );
  // > Date1: 08.11.2009 07:13:59
  DateTime date2 = new DateTime( 2011, 3, 20, 19, 55, 28 );
  Console.WriteLine( "Date2: {0}", date2 );
  // > Date2: 20.03.2011 19:55:28

  DateDiff dateDiff = new DateDiff( date1, date2 );

  // differences
  Console.WriteLine( "DateDiff.Years: {0}", dateDiff.Years );
  // > DateDiff.Years: 1
  Console.WriteLine( "DateDiff.Quarters: {0}", dateDiff.Quarters );
  // > DateDiff.Quarters: 5
  Console.WriteLine( "DateDiff.Months: {0}", dateDiff.Months );
  // > DateDiff.Months: 16
  Console.WriteLine( "DateDiff.Weeks: {0}", dateDiff.Weeks );
  // > DateDiff.Weeks: 70
  Console.WriteLine( "DateDiff.Days: {0}", dateDiff.Days );
  // > DateDiff.Days: 497
  Console.WriteLine( "DateDiff.Weekdays: {0}", dateDiff.Weekdays );
  // > DateDiff.Weekdays: 71
  Console.WriteLine( "DateDiff.Hours: {0}", dateDiff.Hours );
  // > DateDiff.Hours: 11940
  Console.WriteLine( "DateDiff.Minutes: {0}", dateDiff.Minutes );
  // > DateDiff.Minutes: 716441
  Console.WriteLine( "DateDiff.Seconds: {0}", dateDiff.Seconds );
  // > DateDiff.Seconds: 42986489

  // elapsed
  Console.WriteLine( "DateDiff.ElapsedYears: {0}", dateDiff.ElapsedYears );
  // > DateDiff.ElapsedYears: 1
  Console.WriteLine( "DateDiff.ElapsedMonths: {0}", dateDiff.ElapsedMonths );
  // > DateDiff.ElapsedMonths: 4
  Console.WriteLine( "DateDiff.ElapsedDays: {0}", dateDiff.ElapsedDays );
  // > DateDiff.ElapsedDays: 12
  Console.WriteLine( "DateDiff.ElapsedHours: {0}", dateDiff.ElapsedHours );
  // > DateDiff.ElapsedHours: 12
  Console.WriteLine( "DateDiff.ElapsedMinutes: {0}", dateDiff.ElapsedMinutes );
  // > DateDiff.ElapsedMinutes: 41
  Console.WriteLine( "DateDiff.ElapsedSeconds: {0}", dateDiff.ElapsedSeconds );
  // > DateDiff.ElapsedSeconds: 29
} // DateDiffSample
于 2011-05-07T21:10:12.183 回答
1

下面实际上是您可以做到的最准确的方法,因为“1 个月”的定义会根据月份的不同而变化,并且其他答案都没有考虑到这一点!如果您想了解有关框架中未内置的问题的更多信息,您可以阅读这篇文章:A Real Timespan Object With .Years & .Months(但是,阅读该文章对于理解和使用以下功能不是必需的,它可以 100% 工作,没有其他人喜欢使用的近似值的固有不准确性 - 并且可以随意将 .ReverseIt 函数替换为您的框架上可能具有的内置 .Reverse 函数(这里只是为了完整性)。

请注意,您可以获得任意数量的日期/时间精度、秒和分钟,或秒、分钟和天,最长可达数年(其中包含 6 个部分/段)。如果您指定前两个并且它已超过一年,它将返回“1 年零 3 个月前”并且不会返回其余部分,因为您请求了两个片段。如果它只有几个小时,那么它只会返回“2 小时 1 分钟前”。当然,如果您指定 1、2、3、4、5 或 6 个段(最大为 6,因为秒、分钟、小时、天、月、年仅产生 6 种类型),则同样的规则适用。它还将纠正语法问题,例如“分钟”与“分钟”,具体取决于它是否为 1 分钟或更长,所有类型都相同,并且生成的“字符串”在语法上始终是正确的。

以下是一些使用示例: bAllowSegments 标识要显示多少段...即:如果为 3,则返回字符串将是(例如)... "3 years, 2 months and 13 days"(不包括小时、分钟和秒作为前 3 时间类别被返回),但是,如果日期是较新的日期,例如几天前的日期,则指定相同的段 (3) 将"4 days, 1 hour and 13 minutes ago"改为返回,因此它会考虑所有因素!

如果 bAllowSegments 为 2 它将返回"3 years and 2 months",如果 6 (最大值)将返回"3 years, 2 months, 13 days, 13 hours, 29 minutes and 9 seconds",但是,请注意它会NEVER RETURN像这样"0 years, 0 months, 0 days, 3 hours, 2 minutes and 13 seconds ago",因为它知道前 3 个段中没有日期数据并忽略它们,即使您指定 6 个段,所以别担心:)。当然,如果有一个段中包含 0,它会在形成字符串时考虑到这一点,并且会显示为"3 days and 4 seconds ago"并忽略“0小时”部分!如果您喜欢,请享受并发表评论。

 Public Function RealTimeUntilNow(ByVal dt As DateTime, Optional ByVal bAllowSegments As Byte = 2) As String
  ' bAllowSegments identifies how many segments to show... ie: if 3, then return string would be (as an example)...
  ' "3 years, 2 months and 13 days" the top 3 time categories are returned, if bAllowSegments is 2 it would return
  ' "3 years and 2 months" and if 6 (maximum value) would return "3 years, 2 months, 13 days, 13 hours, 29 minutes and 9 seconds"
  Dim rYears, rMonths, rDays, rHours, rMinutes, rSeconds As Int16
  Dim dtNow = DateTime.Now
  Dim daysInBaseMonth = Date.DaysInMonth(dt.Year, dt.Month)

  rYears = dtNow.Year - dt.Year
  rMonths = dtNow.Month - dt.Month
  If rMonths < 0 Then rMonths += 12 : rYears -= 1 ' add 1 year to months, and remove 1 year from years.
  rDays = dtNow.Day - dt.Day
  If rDays < 0 Then rDays += daysInBaseMonth : rMonths -= 1
  rHours = dtNow.Hour - dt.Hour
  If rHours < 0 Then rHours += 24 : rDays -= 1
  rMinutes = dtNow.Minute - dt.Minute
  If rMinutes < 0 Then rMinutes += 60 : rHours -= 1
  rSeconds = dtNow.Second - dt.Second
  If rSeconds < 0 Then rSeconds += 60 : rMinutes -= 1

  ' this is the display functionality
  Dim sb As StringBuilder = New StringBuilder()
  Dim iSegmentsAdded As Int16 = 0

  If rYears > 0 Then sb.Append(rYears) : sb.Append(" year" & If(rYears <> 1, "s", "") & ", ") : iSegmentsAdded += 1
  If bAllowSegments = iSegmentsAdded Then GoTo parseAndReturn

  If rMonths > 0 Then sb.AppendFormat(rMonths) : sb.Append(" month" & If(rMonths <> 1, "s", "") & ", ") : iSegmentsAdded += 1
  If bAllowSegments = iSegmentsAdded Then GoTo parseAndReturn

  If rDays > 0 Then sb.Append(rDays) : sb.Append(" day" & If(rDays <> 1, "s", "") & ", ") : iSegmentsAdded += 1
  If bAllowSegments = iSegmentsAdded Then GoTo parseAndReturn

  If rHours > 0 Then sb.Append(rHours) : sb.Append(" hour" & If(rHours <> 1, "s", "") & ", ") : iSegmentsAdded += 1
  If bAllowSegments = iSegmentsAdded Then GoTo parseAndReturn

  If rMinutes > 0 Then sb.Append(rMinutes) : sb.Append(" minute" & If(rMinutes <> 1, "s", "") & ", ") : iSegmentsAdded += 1
  If bAllowSegments = iSegmentsAdded Then GoTo parseAndReturn

  If rSeconds > 0 Then sb.Append(rSeconds) : sb.Append(" second" & If(rSeconds <> 1, "s", "") & "") : iSegmentsAdded += 1

parseAndReturn:

  ' if the string is entirely empty, that means it was just posted so its less than a second ago, and an empty string getting passed will cause an error
  ' so we construct our own meaningful string which will still fit into the "Posted * ago " syntax...

  If sb.ToString = "" Then sb.Append("less than 1 second")

  Return ReplaceLast(sb.ToString.TrimEnd(" ", ",").ToString, ",", " and")

 End Function

当然,您将需要一个“ReplaceLast”函数,它接受一个源字符串和一个指定需要替换的参数,以及另一个指定要替换它的参数,它只替换该字符串的最后一次出现...如果您没有或不想实现它,我已经包含了我的一个,所以在这里,它可以“按原样”工作,无需修改。我知道 reverseit 函数不再需要(存在于 .net 中),但 ReplaceLast 和 ReverseIt 函数是从 pre-.net 时代遗留下来的,所以请原谅它看起来多么过时(仍然 100% 有效,一直在使用em 使用了十多年,可以保证它们没有错误)... :)。干杯。

<Extension()> _ 
Public Function ReplaceLast(ByVal sReplacable As String, ByVal sReplaceWhat As String, ByVal sReplaceWith As String) As String 
    ' let empty string arguments run, incase we dont know if we are sending and empty string or not. 
    sReplacable = sReplacable.ReverseIt 
    sReplacable = Replace(sReplacable, sReplaceWhat.ReverseIt, sReplaceWith.ReverseIt, , 1) ' only does first item on reversed version! 
    Return sReplacable.ReverseIt.ToString 
End Function 

<Extension()> _ 
Public Function ReverseIt(ByVal strS As String, Optional ByVal n As Integer = -1) As String 
    Dim strTempX As String = "", intI As Integer 

    If n > strS.Length Or n = -1 Then n = strS.Length 

    For intI = n To 1 Step -1 
        strTempX = strTempX + Mid(strS, intI, 1) 
    Next intI 

    ReverseIt = strTempX + Right(strS, Len(strS) - n) 

End Function 
于 2012-02-15T05:16:40.980 回答
0

如果你想要确切的数字,你不能只从时间跨度,因为你需要知道你正在处理的月份,以及你是否正在处理闰年,就像你说的那样。

要么找一个大概的数字,要么对原始的 DateTimes 做一些烦躁

于 2009-10-06T14:44:20.287 回答
0

http://www.astro.uu.nl/~strous/AA/en/reken/juliaansedag.html

如果您可以将时间从公历日期转换为儒略日数,您可以创建一个运算符来比较 zulian 日数,可以键入 double 来获取月、日、秒等。查看以上用于从公历转换为儒略的算法的链接。

于 2009-10-06T15:03:57.680 回答
0

在 idiomatic-c# 中没有内置的方法可以准确地做到这一点。有一些解决方法,例如人们编写的这个 CodeProject 示例。

于 2011-03-17T12:37:23.753 回答
0

如果您要处理月份和年份,则需要知道每个月有多少天以及哪些年份是闰年的东西。

输入公历(和其他特定于文化的日历实现)。

虽然 Calendar 没有提供直接计算两个时间点之间差异的方法,但它确实具有诸如

DateTime AddWeeks(DateTime time, int weeks)
DateTime AddMonths(DateTime time, int months)
DateTime AddYears(DateTime time, int years)
于 2011-06-04T08:26:08.907 回答
0
DateTime start = new DateTime(2003, 12, 25);
DateTime end = new DateTime(2009, 10, 6);
int compMonth = (end.Month + end.Year * 12) - (start.Month + start.Year * 12);
double daysInEndMonth = (end - end.AddMonths(1)).Days;
double months = compMonth + (start.Day - end.Day) / daysInEndMonth;
于 2013-07-25T09:24:14.683 回答
0

该方法返回一个包含 3 个元素的列表,第一个是年,第二个是月,最后一个元素是天:

public static List<int> GetDurationInEnglish(DateTime from, DateTime to)
    {
        try
        {
            if (from > to)
                return null;

            var fY = from.Year;
            var fM = from.Month;
            var fD = DateTime.DaysInMonth(fY, fM);

            var tY = to.Year;
            var tM = to.Month;
            var tD = DateTime.DaysInMonth(tY, tM);

            int dY = 0;
            int dM = 0;
            int dD = 0;

            if (fD > tD)
            {
                tM--;

                if (tM <= 0)
                {
                    tY--;
                    tM = 12;
                    tD += DateTime.DaysInMonth(tY, tM);
                }
                else
                {
                    tD += DateTime.DaysInMonth(tY, tM);
                }
            }
            dD = tD - fD;

            if (fM > tM)
            {
                tY--;

                tM += 12;
            }
            dM = tM - fM;

            dY = tY - fY;

            return new List<int>() { dY, dM, dD };
        }
        catch (Exception exception)
        {
            //todo: log exception with parameters in db

            return null;
        }
    }
于 2015-06-01T10:35:08.550 回答
0

这是我发现准确的月份差异的贡献:

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

用法:

Int32 months = DateTime.Now.DiffMonths( DateTime.Now.AddYears( 5 ) );

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

于 2016-08-29T09:39:10.947 回答
0

比赛迟到了,但我想这可能对某人有所帮助。大多数人倾向于逐月测量,不包括月份有不同变化的事实。使用这种思想框架,我创建了一个班轮来为我们比较日期。使用以下过程。

  1. 比较年份时,任何超过 1 年的年份都将乘以 12,在任何情况下都不会小于 1 整年。
  2. 如果结束年份更大,我们需要评估当前日期是否大于或等于前一天 2A。如果结束日期大于或等于我们取当前月份,然后加上 12 个月减去开始月份 2B 的月份。如果结束日期小于开始日期,我们执行与上述相同的操作,只是在减去之前将开始月份加 1
  3. 如果结束年份不是更大,我们的表现与 2A/2B 相同,但不添加 12 个月,因为我们不需要在一年左右进行评估。

        DateTime date = new DateTime(2003, 11, 25);
        DateTime today = new DateTime(2004, 12, 26);
        var time = (today.Year - date.Year > 1 ? (today.Year - date.Year - 1) * 12 : 0) +  (today.Year > date.Year ? (today.Day >= date.Day ? today.Month + 12 - date.Month : today.Month + 12 - (date.Month + 1)) : (today.Day >= date.Day ? today.Month - date.Month : today.Month - (date.Month + 1)));
    
于 2017-06-08T10:49:54.463 回答
0

我对这个答案的看法也使用了扩展方法,但它可以返回正面或负面的结果。

public static int MonthsBefore(this DateTime dt1, DateTime dt2)
{
    (DateTime early, DateTime late, bool dt2After) = dt2 > dt1 ? (dt1,dt2,true) : (dt2,dt1,false);
    DateTime tmp; // Save the result so we don't repeat work
    int months = 1;
    while ((tmp = early.AddMonths(1)) <= late)
    {
        early = tmp;
        months++;
    }
    return (months-1)*(dt2After ? 1 : -1);
}

几个测试:

// Just under 1 month's diff
Assert.AreEqual(0, new DateTime(2014, 1, 1).MonthsBefore(new DateTime(2014, 1, 31)));
// Just over 1 month's diff
Assert.AreEqual(1, new DateTime(2014, 1, 1).MonthsBefore(new DateTime(2014, 2, 2)));    
// Past date returns NEGATIVE
Assert.AreEqual(-6, new DateTime(2012, 1, 1).MonthsBefore(new DateTime(2011, 6, 10)));
于 2019-10-26T20:54:02.913 回答
0

结合上面的两个答案,另一种扩展方法是:

public static int ElapsedMonths(this DateTime date1, DateTime date2)
{
    DateTime earlierDate = (date1 > date2) ? date2 : date1;
    DateTime laterDate = (date1 > date2) ? date1 : date2;
    var eMonths = (laterDate.Month - earlierDate.Month) + 12 * (laterDate.Year - earlierDate.Year) - 
                                            ((earlierDate.Day > laterDate.Day) ? 1 : 0);
    return eMonths;
}

感谢@AdamRobinson 和@MarkWhittaker

于 2019-12-04T12:17:24.320 回答
0

我们是这样做的:

int DifferenceInMonth(DateTime startDate, DateTime endDate) {
    long ToTicks(DateTime date) => new DateTimeOffset(date).Ticks;
    var daysPerMonth = 30.4;
    return (int)Math.Round((ToTicks(endDate) - ToTicks(startDate)) / TimeSpan.TicksPerDay / daysPerMonth);
}
于 2020-10-26T21:06:55.987 回答
-1

接受的答案是非常不正确的:

对于这些日期:ldate = 2020-08-30 和 rdate = 2020-08-01,我们有一个月,但接受的答案返回 0。

对于这些日期:ldate = 2020-08-30 和 rdate = 2020-10-01,我们有三个月的时间,但接受的答案返回 -2。

这是计算两个日期之间月数的正确方法(可能不是唯一方法,但正确):

  • 您不必检查哪个日期低于其他日期。

  • 从一个月的第一天到最后一天的间隔算作一个月。

      public static int GetMontsBetween(DateTime date1, DateTime date2)
      {
          int monthCount = 0;
          int direction = date1 < date2 ? 1 : -1;
          date2 = date2.AddDays(direction);
    
          while (date1.Year != date2.Year || date1.Month != date2.Month)
          {
              date1 = date1.AddMonths(direction);
              monthCount++;
          }
    
          return monthCount;
      } 
    
于 2021-02-27T16:34:52.847 回答