вычисление разницы в месяцах между двумя датами


в C# / .NET TimeSpan и TotalDays,TotalMinutes и т. д. но я не могу найти формулу для общей разницы месяцев. Переменные дни в месяц и високосные годы продолжают сбивать меня с толку. Как я могу получить всего месяцев?

Edit Извините, что не более ясно: я знаю, что на самом деле не могу получить это от TimeSpan но я думал, что с помощью TotalDays и TotalMinutes было бы хорошим примером, чтобы выразить то, что я искал ... за исключением того, что я пытаюсь получить общее Месяцы.

Пример: 25 Декабря 2009 - 6 Октября 2009 = 2 Полных Месяца. С 6 октября по 5 ноября-0 месяцев. 6 ноября, 1 месяц. 6 декабря, 2 месяца

24 107

24 ответа:

вы не сможете получить это от TimeSpan, потому что "месяц" - это переменная единица измерения. Вам придется рассчитать его самостоятельно, и вам придется выяснить, как именно вы хотите, чтобы он работал.

например, должны даты, как July 5, 2009 и August 4, 2009 выход один месяц или ноль месяцев разница? Если вы говорите, что он должен дать один, то как насчет July 31, 2009 и August 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));
}

(Я понимаю, что это старый вопрос, но...)

Это относительно больно делать в pure. NET. я бы рекомендовал свой собственный Нода Времени библиотека, которая специально разработана для таких вещей, как этот:

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

может быть, вы не хотите знать о месячных дробях; как насчет этого кода?


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"


вы должны будете определить, что вы подразумеваете под TotalMonths для начала.
Простое определение ставит месяц в 30,4 дня (365,25 / 12).

кроме того, любое определение, включая дроби, кажется бесполезным, и более общее целочисленное значение (целые месяцы между датами) также зависит от нестандартных бизнес-правил.

вы должны решить это самостоятельно от даты и времени. Как вы справляетесь с заглушенными днями в конце, будет зависеть от того, для чего вы хотите его использовать.

один из методов будет считать месяц, а затем исправить в течение нескольких дней в конце. Что-то вроде:

   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;

Я написал очень простой метод расширения на DateTime и DateTimeOffset для этого. Я хотел, чтобы он работал точно так же, как 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)));

Я бы сделал так:

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;
}
case TipoIntervalo.Mes:
    retorno = inicio.AddMonths(-fim.Month).Month.ToString();
    break;
case TipoIntervalo.Ano:
    retorno = (inicio.Year - fim.Year).ToString();
    break;

есть не так много четких ответов на это, потому что вы всегда предполагаете вещи.

Это решение вычисляет между двумя датами месяцы между предполагая, что вы хотите сохранить день месяца для сравнения, (это означает, что день месяца рассматривается в расчете)

например, если у вас есть дата 30 января 2012 года, 29 февраля 2012 года не будет месяц, но 01 марта 2013 года будет.

Он был протестирован довольно тщательно, вероятно, очистит его позже, когда мы его используем, и принимает две даты вместо промежутка времени, что, вероятно, лучше. Надеюсь, это поможет кому-нибудь еще.

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
}

старый вопрос я знаю, но может помочь кому-то. Я использовал @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);
}

проблема с месяцами заключается в том, что на самом деле это не простая мера - они не являются постоянным размером. Вам нужно будет определить свои правила для того, что вы хотите включить, и работать оттуда. Например, с 1 января по 1 февраля - вы можете утверждать, что там участвуют 2 месяца, или вы можете сказать, что это один месяц. Тогда как насчет "1 января 20: 00 "до" 1 февраля 00:00 " - это не совсем полный месяц. Это 0? 1? а как насчет наоборот (с 1 января 00: 00 до 1 февраля 20: 00)... 1? 2?

первый определите правила, тогда вам придется кодировать его самостоятельно, я боюсь...

если вы хотите иметь результат 1 между 28th Feb и 1st March:

DateTime date1, date2;
int monthSpan = (date2.Year - date1.Year) * 12 + date2.Month - date1.Month

этой библиотека вычисляет разницу месяцев, учитывая все части 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

ниже на самом деле самый точный способ, которым вы можете это сделать, так как определение "1 месяц" меняется в зависимости от того, какой месяц он есть, и не из других ответов учитывают это! Если вы хотите получить дополнительную информацию о проблеме, которая не встроена в фреймворк, вы можете прочитать этот пост:Реальный Объект Timespan С.Годы. & Месяцы (однако, читая этот пост не обязательно понимать и использовать функцию ниже, она работает на 100%, без присущих неточностей аппроксимации другие любят использовать - и не стесняйтесь заменить .Функция ReverseIt со встроенным модулем .Обратная функция у вас может быть на вашем фреймворке (это просто здесь для полноты картины).

обратите внимание, что вы можете получить любое количество дат/раз точность, секунды и минуты, или секунды, минуты и дни, в любом месте до лет (который будет содержать 6 частей/сегментов). Если вы укажете два лучших, и ему больше года, он вернется "1 год и 3 месяца назад" и не вернет остальных, потому что вы запросили два сегмента. если ему всего несколько часов, то он вернется только "2 часа и 1 минуту назад". Конечно, те же правила применяются, если вы указываете 1, 2, 3, 4, 5 или 6 сегментов (максимумы в 6, потому что секунды, минуты, часы, дни, месяцы, годы составляют только 6 типов). Это также исправит проблемы граммера, такие как" минуты "против" минуты", в зависимости от того, если это 1 минута или больше, то же самое для всех типов, и сгенерированная" строка " всегда будет грамматически правильной.

здесь вот несколько примеров для использования: bAllowSegments определяет, сколько сегментов для отображения... ie: если 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" функция, которая принимает исходную строку и аргумент, указывающий, что нужно заменить, и другой arg, указывающий, что вы хотите заменить его, и он заменяет только последнее вхождение этой строки... я включил свой, если у вас его нет или вы не хотите его реализовать, так что вот он, он будет работать "как есть" без каких-либо изменений. Я знаю, что функция reverseit больше не нужна (существует в .net), но ReplaceLast и функция ReverseIt переносятся из pre-.net дней, поэтому, пожалуйста, извините, как это может выглядеть (все еще работает 100% tho, использует их более десяти лет, может гарантировать, что они не содержат ошибок)... :). овации.

<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 

принятый ответ Отлично работает, когда вы хотите полные месяцы.

мне нужны были частичные месяцы. Это решение я придумал для частичных месяцев:

    /// <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.Day > rValue.Day // Partial month, same year and month
                   ? 1 : 0);
    }

Если вы хотите точное число, вы не можете просто от промежутка времени, так как вам нужно знать, с какими месяцами вы имеете дело, и имеете ли вы дело с високосным годом, как вы сказали.

либо пойти на приблизительное число, или сделать некоторые ерзать с исходной даты

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

Если вы можете получить время, преобразованное из Григорианской даты в Юлианский день числа, вы можете просто создать оператор для сравнения номера дня zulian, который может быть типом double, чтобы получить месяцы, дни, секунды и т. д. Ознакомьтесь с приведенной выше ссылкой для алгоритма преобразования из григорианского в Юлианский.

нет встроенного способа сделать это точно в idiomatic-c#. Есть некоторые обходные пути, такие как этот пример CodeProject что люди закодировали, хотя.

Если вы имеете дело с месяцами и годами, вам нужно что-то, что знает, сколько дней каждый месяц имеет и какие годы високосные годы.

введите Григорианскому Календарю (и другие культурные особенности календарь реализации).

хотя календарь не предоставляет методы для прямого вычисления разницы между двумя точками во времени, у него есть такие методы, как

DateTime AddWeeks(DateTime time, int weeks)
DateTime AddMonths(DateTime time, int months)
DateTime AddYears(DateTime time, int years)
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;

метод возвращает список, который содержит 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;
        }
    }

вот мой вклад, чтобы получить разницу в месяцах, которые я нашел, чтобы быть точным:

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 и применить точно такую же логику, как выше, и AddYears вместо AddMonths в цикле while.

путь поздно к игре, но я думаю, что это может быть полезно для кого-то. Большинство людей склонны измерять месяц за месяцем по дате, исключая тот факт, что месяцы бывают в разных вариациях. Используя эту структуру мысли, я создал один лайнер, который сравнивает даты для нас. Используя следующий процесс.

  1. любое количество лет более 1 при сравнении год будет умножен на 12, нет ни одного случая, когда это может быть равно менее чем 1 полный год.
  2. если конец года больше нам нужно оценить, если текущий день больше или равен предыдущему дню 2А. Если до конца дня больше или равно берем текущий месяц и затем добавить 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)));
    
    DateTime dtEnd = DateTime.Now.AddDays(59);
    DateTime dtBegin = DateTime.Now;
    var diff = (dtEnd-dtBegin).TotalDays;
    lblDateDiff.Text = Math.Floor(diff/30).ToString() + " month('s) and " + (diff%30).ToString() + " days";

возвращает 1 месяц ('s) и 29 дней. Конечно, это зависит от месяцев 30 дней.. но это очень близко к правильному значению для дней и месяцев между датами.