вычисление разницы в месяцах между двумя датами - PullRequest
116 голосов
/ 06 октября 2009

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

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

Пример: 25 декабря 2009 г. - 6 октября 2009 г. = 2 месяца. 6 октября по 5 ноября равняется 0 месяцам. 6 ноября 1 месяц. 6 декабря, 2 месяца

Ответы [ 24 ]

209 голосов
/ 06 октября 2009

Вы не сможете получить это от 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));
}
47 голосов
/ 12 августа 2014

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

Это относительно больно делать в чистом .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);)

26 голосов
/ 06 октября 2009

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


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"


9 голосов
/ 06 октября 2009

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

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

8 голосов
/ 06 октября 2009

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

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

   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;
7 голосов
/ 05 августа 2014

Я написал очень простой метод расширения для 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)));
3 голосов
/ 07 января 2014

На этот вопрос не так много ясных ответов, потому что вы всегда принимаете что-то.

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

Например, если у вас дата 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
}
3 голосов
/ 06 октября 2009

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

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;
}
2 голосов
/ 01 сентября 2011

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

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

Мне понадобились неполные месяцы. Это решение, которое я придумал за неполные месяцы:

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