Разница в месяцах между двумя датами


334

Как рассчитать разницу в месяцах между двумя датами в C #?

Есть ли эквивалентный DateDiff()метод VB в C #. Мне нужно найти разницу в месяцах между двумя датами, которые разделены годами. Документация говорит, что я могу использовать TimeSpanкак:

TimeSpan ts = date1 - date2;

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

Какие-либо предложения?


27
Определите «разницу в месяцах». Какая разница в месяцах между «май 1,2010» и «июнь 16,2010»? 1,5, 1 или что-то еще?
Ченг Чен

7
Или, чтобы подчеркнуть этот момент далее, какая разница в месяцах между 31 декабря 2010 года и 1 января 2011 года? В зависимости от дневного времени это может быть разница всего в 1 секунду; Вы бы посчитали это разницей в один месяц?
stakx - больше не вносит вклад

Вот простой и короткий код на тот случай, если вы все еще не можете получить ответ, см. Этот POST stackoverflow.com/questions/8820603/…
wirol

11
Дэнни: 1 месяц и 15 дней. Staxx: 0 месяцев и 1 день. Дело в том, чтобы получить компонент месяца . Это кажется довольно очевидным для меня и является хорошим вопросом.
Кирк Уолл

Ответы:


462

Предполагая, что день месяца не имеет значения (т.е. разница между 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)

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


Обновление (с благодарностью Гэри )

При использовании метода «средних месяцев» чуть более точное число для «среднего числа дней в году» составляет 365,2425 .


3
@Kurru - 365/12 является лишь приблизительным показателем средней продолжительности месяца в днях. Это неточная мера. Для небольших диапазонов дат эта неточность может быть допущена, но для очень больших диапазонов дат эта неточность может стать значительной.
Адам Ральф

21
Я думаю, что необходимо учитывать компонент дня. Как то так (date1.Year - date2.Year) * 12 + date1.Month - date2.Month + (date1.Day >= date2.Day ? 0 : -1)
DrunkCoder

2
@DrunkCoder это зависит от требований данной системы. В некоторых случаях ваше решение действительно может быть лучшим выбором. Например, важно учитывать, что происходит, когда две даты охватывают 31 день, 30 дней, 28 дней февраля или 29 дней февраля. Если результаты вашей формулы дают то, что требуется системе, тогда это, безусловно, правильный выбор. Если нет, то требуется что-то еще.
Адам Ральф

6
Чтобы поддержать то, что сказал Адам, я потратил годы на написание кода для актуариев. Некоторые расчеты были разделены на количество дней, округлены до 30, чтобы получить месячный показатель . Иногда при подсчете месяцев, предполагаемых, что каждая дата начинается с первого числа месяца, подсчитывайте целые месяцы соответственно . Там нет лучшего метода, когда дело доходит до расчета дат. Если вы не являетесь клиентом, для которого пишете код, продвигайте его обратно по цепочке и разъясняйте его, возможно, вашим бухгалтером-заказчиком.
Бинарный Беспорядок

1
365.2425 - немного более точное число дней в григорианском календаре, если вы используете его. Однако, согласно DateTime.MaxValue (1 января, 10000), разница составляет всего лишь 59 дней. Кроме того, определение года может сильно отличаться в зависимости от вашей перспективы en.wikipedia.org/wiki/Year .
Гэри

208

Здесь представлено комплексное решение для возврата 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);
}

Выходы:

Годы: 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;
    }
}

2
@KirkWoll спасибо. Но почему это DateTimeSpan возвращает 34дни этой разницы времени дата на самом деле это 35 timeanddate.com/date/...
Deeptechtons

@Deeptechtons, хороший улов. Было несколько проблем, на которые вы обратили мое внимание: как с датой начала, так 31и с датой, которая проходит через месяцы с меньшим количеством дней. Я перевернул логику (так что она идет от раннего к более позднему, чем наоборот) и теперь накапливает месяцы без изменения текущей даты (и, таким образом, проходя через месяцы с меньшим количеством дней). До сих пор не совсем уверен, какой идеальный результат должно быть при сравнении 10/31/2012с 11/30/2012. Прямо сейчас результат 1месяц.
Кирк Волл

@KirkWoll спасибо за обновление, возможно, я получил еще несколько ошибок, позвольте мне подтвердить это после некоторых тестов Good Job :)
Deeptechtons

1
Я написал ответ stackoverflow.com/a/17537472/1737957 на аналогичный вопрос, который проверил предложенные ответы (и обнаружил, что большинство из них не работают). Этот ответ - один из немногих, который действительно работает (согласно моему набору тестов). Ссылка на github на мой ответ.
JWG

@KirkWoll - этот ответ не работает для крайних случаев, когда дата начала имеет значение дня больше, чем месяц до даты, или если исходная дата является високосным днем. Попробуйте 2020-02-29к 2021-06-29- она возвращает «1y ого 1d», но значение должно быть «1y ого 0d», верно?
Загадочность

37

Вы могли бы сделать

if ( date1.AddMonths(x) > date2 )

Это так просто и прекрасно работает для меня. Я был приятно удивлен, увидев, что он работает должным образом при расчете даты с конца 1 месяца до даты в конце следующего месяца, в которой меньше дней. Например .. 1-31-2018 + 1 месяц = ​​28 февраля 218
lucky.expert

Это одно из лучших решений.
barnacle.m

Действительно простое и эффективное решение! Лучший ответ предложен.
Седрик

2
Что если date1 = 2018-10-28 и date2 = 2018-12-21? Ответ будет 2. в то время как правильный ответ должен быть 3. Из-за диапазона дат на 3 месяца. если мы считаем только месяцы, игнорируя дни. Так что этот ответ НЕ верен.
Tommix

Более логичным было бы: if ( date1.AddMonths(x).Month == date2.Month )тогда вы просто используете x + 1 для подсчета месяцев
Tommix

34

Если вам нужно точное число полных месяцев, всегда положительное (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;
    }
}

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

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

Просто, чтобы избежать путаницы с другими людьми, я думаю, что это решение не является правильным. Использование тестового примера: new { From = new DateTime(2015, 12, 31), To = new DateTime(2015, 6, 30), Result = 6 } тест не
пройден,

Добавлен краткий обзор исправления, которое я предлагаю здесь
Кристиан Бадила

Я не уверен, что понял
Guillaume86

Я скопировал тестовый пример вручную, и в нем есть ошибка. Неисправные спецификации должны быть: new { From = new DateTime(2015, 12, 31), To = new DateTime(2016, 06, 30), Result = 6 }. «Ошибка» заключается в to.Day < from.Dayкоде, который не учитывает, что месяцы могут заканчиваться другим «днем месяца». В этом случае с 31 декабря 2015 года до 30 июня 2016 года пройдет 6 полных месяцев (с июня 30 дней), но ваш код вернется 5.
Кристиан Бадила

3
По моему мнению, это ожидаемое поведение, или, по крайней мере, такое поведение я ожидаю. Я уточнил полный месяц, когда вы достигнете того же дня (или следующего месяца, как в этом случае).
Guillaume86

22

Я проверил использование этого метода в VB.NET через MSDN, и кажется, что он имеет много применений. В C # такого встроенного метода нет. (Даже это не очень хорошая идея) вы можете вызывать VB в C #.

  1. Добавить Microsoft.VisualBasic.dllв свой проект в качестве ссылки
  2. используйте Microsoft.VisualBasic.DateAndTime.DateDiff в своем коде

7
Почему вы думаете, что это не очень хорошая идея? Интуитивно я догадываюсь, что библиотека - это «просто еще одна библиотека .NET» для среды выполнения. Заметьте, я играю здесь адвоката дьявола, я бы тоже не хотел этого делать, потому что это просто «неправильно» (вид мошенничества), но мне интересно, есть ли какая-то убедительная техническая причина не делать этого.
Адам Ральф

3
@AdamRalph: Нет никаких причин не делать этого. Эти библиотеки реализованы в 100% -ом управляемом коде, поэтому все так же, как и все остальное. Единственное возможное отличие состоит в том, что Microsoft.VisualBasic.dllмодуль должен быть загружен, но время, которое требуется для этого, незначительно. Нет причин обманывать себя проверенными и полезными функциями только потому, что вы решили написать свою программу на C #. (Это относится и к таким вещам My.Application.SplashScreen.)
Коди Грей

3
Вы бы передумали, если бы знали, что это написано на C #? Это было. По той же логике, использование System.Data и PresentationFramework также обманывает, существенные части этого написаны на C ++ / CLI.
Ганс Пассант

3
@AdamRalph: Какие конкретные примеры этого «странного багажа» приходят на ум? Или вы говорите это чисто гипотетически? И да, это могло бы испортить умы некоторых ваших приятелей по C #, которые писали невероятный объем кода, чтобы сделать что-то, что вы можете сделать в одной строке с правильным usingутверждением, но я сомневаюсь, что будет какой-либо серьезный ущерб.
Коди Грей

1
@ Коди Грей: согласен, пример, как вы видите, тривиален. Это дополнительный «шум» кода, вызванный вызовом такого необычного (из C # POV) метода, которого я бы хотел избежать. В хорошо организованной команде такие вещи в любом случае будут обнаружены в обзоре кода, и их можно легко избежать. Кстати, я не пытаюсь атаковать VB6 / VB.NET. Я описал такие методы как «странные» только потому, что из .NET POV нет причин для DateAndTime.Year()существования, учитывая, что у DateTimeнего есть Yearсвойство. Он существует только для того, чтобы VB.NET выглядел больше как VB6. Как бывший программист VB6, я могу это оценить ;-)
Адам Ральф,

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

5
Представь startи endидентичны. Тогда вы получите результат 1. Как это правильно? Почему вы добавляете 1 к результату? Кто голосует против этого ответа: - /?
Пол

Для идентичных дат будет выдан результат 1. Как правило, он будет учитывать все месяцы, включая месяцы начала и окончания.
Чираг

3
не похоже на разницу между двумя предметами для меня. В чем разница между 2 и 2? Это действительно 1? Я бы предположил, что разница равна 0.
Пол


7

Мне просто нужно было что-то простое, чтобы удовлетворить, например, даты трудоустройства, когда вводится только месяц / год, поэтому хотел, чтобы работали разные годы и месяцы. Это то, что я использую, здесь только для пользы

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

.NET Fiddle


4

Вы можете использовать класс DateDiff библиотеки периодов времени для .NET :

// ----------------------------------------------------------------------
public void DateDiffSample()
{
  DateTime date1 = new DateTime( 2009, 11, 8, 7, 13, 59 );
  DateTime date2 = new DateTime( 2011, 3, 20, 19, 55, 28 );
  DateDiff dateDiff = new DateDiff( date1, date2 );

  // differences
  Console.WriteLine( "DateDiff.Months: {0}", dateDiff.Months );
  // > DateDiff.Months: 16

  // elapsed
  Console.WriteLine( "DateDiff.ElapsedMonths: {0}", dateDiff.ElapsedMonths );
  // > DateDiff.ElapsedMonths: 4

  // description
  Console.WriteLine( "DateDiff.GetDescription(6): {0}", dateDiff.GetDescription( 6 ) );
  // > DateDiff.GetDescription(6): 1 Year 4 Months 12 Days 12 Hours 41 Mins 29 Secs
} // DateDiffSample

2

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

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.


2

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

public static int MonthDiff(DateTime d1, DateTime d2){
    int retVal = 0;

    if (d1.Month<d2.Month)
    {
        retVal = (d1.Month + 12) - d2.Month;
        retVal += ((d1.Year - 1) - d2.Year)*12;
    }
    else
    {
        retVal = d1.Month - d2.Month;
        retVal += (d1.Year - d2.Year)*12;
    }
    //// Calculate the number of years represented and multiply by 12
    //// Substract the month number from the total
    //// Substract the difference of the second month and 12 from the total
    //retVal = (d1.Year - d2.Year) * 12;
    //retVal = retVal - d1.Month;
    //retVal = retVal - (12 - d2.Month);

    return retVal;
}

2

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

private double ReturnDiffereceBetweenTwoDatesInMonths(DateTime startDateTime, DateTime endDateTime)
{
    double result = 0;
    double days = 0;
    DateTime currentDateTime = startDateTime;
    while (endDateTime > currentDateTime.AddMonths(1))
    {
        result ++;

        currentDateTime = currentDateTime.AddMonths(1);
    }

    if (endDateTime > currentDateTime)
    {
        days = endDateTime.Subtract(currentDateTime).TotalDays;

    }
    return result + days/endDateTime.GetMonthDays;
}

2

Вот простое решение, которое работает по крайней мере для меня. Это, вероятно, не самый быстрый, потому что он использует классную функцию AddTonth DateTime в цикле:

public static int GetMonthsDiff(DateTime start, DateTime end)
{
    if (start > end)
        return GetMonthsDiff(end, start);

    int months = 0;
    do
    {
        start = start.AddMonths(1);
        if (start > end)
            return months;

        months++;
    }
    while (true);
}

1
Public Class ClassDateOperation
    Private prop_DifferenceInDay As Integer
    Private prop_DifferenceInMonth As Integer
    Private prop_DifferenceInYear As Integer


    Public Function DayMonthYearFromTwoDate(ByVal DateStart As Date, ByVal DateEnd As Date) As ClassDateOperation
        Dim differenceInDay As Integer
        Dim differenceInMonth As Integer
        Dim differenceInYear As Integer
        Dim myDate As Date

        DateEnd = DateEnd.AddDays(1)

        differenceInYear = DateEnd.Year - DateStart.Year

        If DateStart.Month <= DateEnd.Month Then
            differenceInMonth = DateEnd.Month - DateStart.Month
        Else
            differenceInYear -= 1
            differenceInMonth = (12 - DateStart.Month) + DateEnd.Month
        End If


        If DateStart.Day <= DateEnd.Day Then
            differenceInDay = DateEnd.Day - DateStart.Day
        Else

            myDate = CDate("01/" & DateStart.AddMonths(1).Month & "/" & DateStart.Year).AddDays(-1)
            If differenceInMonth <> 0 Then
                differenceInMonth -= 1
            Else
                differenceInMonth = 11
                differenceInYear -= 1
            End If

            differenceInDay = myDate.Day - DateStart.Day + DateEnd.Day

        End If

        prop_DifferenceInDay = differenceInDay
        prop_DifferenceInMonth = differenceInMonth
        prop_DifferenceInYear = differenceInYear

        Return Me
    End Function

    Public ReadOnly Property DifferenceInDay() As Integer
        Get
            Return prop_DifferenceInDay
        End Get
    End Property

    Public ReadOnly Property DifferenceInMonth As Integer
        Get
            Return prop_DifferenceInMonth
        End Get
    End Property

    Public ReadOnly Property DifferenceInYear As Integer
        Get
            Return prop_DifferenceInYear
        End Get
    End Property

End Class

1

Это из моей собственной библиотеки, вернет разницу месяцев между двумя датами.

public static int MonthDiff(DateTime d1, DateTime d2)
{
    int retVal = 0;

    // Calculate the number of years represented and multiply by 12
    // Substract the month number from the total
    // Substract the difference of the second month and 12 from the total
    retVal = (d1.Year - d2.Year) * 12;
    retVal = retVal - d1.Month;
    retVal = retVal - (12 - d2.Month);

    return retVal;
}

1
Это работает? Я продолжаю получать 11 на бумаге для Jan-31-2014иDec-31-2013
Дейв Кузино

1

Вы можете иметь функцию примерно так.

Например, с 2012/12/27 по 2012/12/29 становится 3 дня. Аналогичным образом, с 2012/12/15 по 2013/15/15 становится 2 месяца, потому что до 2013/01/14 это 1 месяц. с 15го месяца начался 2й месяц

Вы можете удалить «=» во втором условии if, если вы не хотите включать в расчет оба дня. т.е. с 2012/12/15 по 2013/15/15 составляет 1 месяц.

public int GetMonths(DateTime startDate, DateTime endDate)
{
    if (startDate > endDate)
    {
        throw new Exception("Start Date is greater than the End Date");
    }

    int months = ((endDate.Year * 12) + endDate.Month) - ((startDate.Year * 12) + startDate.Month);

    if (endDate.Day >= startDate.Day)
    {
        months++;
    }

    return months;
}

1

Вы можете использовать следующее расширение: Код

public static class Ext
{
    #region Public Methods

    public static int GetAge(this DateTime @this)
    {
        var today = DateTime.Today;
        return ((((today.Year - @this.Year) * 100) + (today.Month - @this.Month)) * 100 + today.Day - @this.Day) / 10000;
    }

    public static int DiffMonths(this DateTime @from, DateTime @to)
    {
        return (((((@to.Year - @from.Year) * 12) + (@to.Month - @from.Month)) * 100 + @to.Day - @from.Day) / 100);
    }

    public static int DiffYears(this DateTime @from, DateTime @to)
    {
        return ((((@to.Year - @from.Year) * 100) + (@to.Month - @from.Month)) * 100 + @to.Day - @from.Day) / 10000;
    }

    #endregion Public Methods
}

Реализация !

int Age;
int years;
int Months;
//Replace your own date
var d1 = new DateTime(2000, 10, 22);
var d2 = new DateTime(2003, 10, 20);
//Age
Age = d1.GetAge();
Age = d2.GetAge();
//positive
years = d1.DiffYears(d2);
Months = d1.DiffMonths(d2);
//negative
years = d2.DiffYears(d1);
Months = d2.DiffMonths(d1);
//Or
Months = Ext.DiffMonths(d1, d2);
years = Ext.DiffYears(d1, d2); 

1

Вот гораздо более краткое решение, использующее VB.Net DateDiff только для года, месяца, дня. Вы также можете загрузить библиотеку DateDiff в C #.

дата1 должна быть <= дата2

VB.NET

Dim date1 = Now.AddDays(-2000)
Dim date2 = Now
Dim diffYears = DateDiff(DateInterval.Year, date1, date2) - If(date1.DayOfYear > date2.DayOfYear, 1, 0)
Dim diffMonths = DateDiff(DateInterval.Month, date1, date2) - diffYears * 12 - If(date1.Day > date2.Day, 1, 0)
Dim diffDays = If(date2.Day >= date1.Day, date2.Day - date1.Day, date2.Day + (Date.DaysInMonth(date1.Year, date1.Month) - date1.Day))

C #

DateTime date1 = Now.AddDays(-2000);
DateTime date2 = Now;
int diffYears = DateDiff(DateInterval.Year, date1, date2) - date1.DayOfYear > date2.DayOfYear ? 1 : 0;
int diffMonths = DateDiff(DateInterval.Month, date1, date2) - diffYears * 12 - date1.Day > date2.Day ? 1 : 0;
int diffDays = date2.Day >= date1.Day ? date2.Day - date1.Day : date2.Day + (System.DateTime.DaysInMonth(date1.Year, date1.Month) - date1.Day);

1

Это в ответ на ответ Кирка Уолла. У меня недостаточно очков репутации, чтобы ответить на комментарий ...

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

Вот мой переписать:

public class DateTimeSpan {
    private DateTime _date1;
    private DateTime _date2;
    private int _years;
    private int _months;
    private int _days;
    private int _hours;
    private int _minutes;
    private int _seconds;
    private int _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; } }

    public DateTimeSpan(DateTime date1, DateTime date2) {
        _date1 = (date1 > date2) ? date1 : date2;
        _date2 = (date2 < date1) ? date2 : date1;

        _years = _date1.Year - _date2.Year;
        _months = (_years * 12) + _date1.Month - _date2.Month;
        TimeSpan t = (_date2 - _date1);
        _days = t.Days;
        _hours = t.Hours;
        _minutes = t.Minutes;
        _seconds = t.Seconds;
        _milliseconds = t.Milliseconds;

    }

    public static DateTimeSpan CompareDates(DateTime date1, DateTime date2) {
        return new DateTimeSpan(date1, date2);
    }
}

Использование1, почти то же самое:

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 = new DateTimeSpan(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);
}

Использование2, аналогично:

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

1

В моем случае требуется рассчитать полный месяц от даты начала до дня, предшествующего этому дню в следующем месяце или от начала до конца месяца.


Пример: с 1 января по 31 января 2008 года - полный месяц
Пример 2: с 1 мая по 2 февраля 2008 года - полный месяц

поэтому на основе этого вот мое решение:

public static DateTime GetMonthEnd(DateTime StartDate, int MonthsCount = 1)
{
    return StartDate.AddMonths(MonthsCount).AddDays(-1);
}
public static Tuple<int, int> CalcPeriod(DateTime StartDate, DateTime EndDate)
{
    int MonthsCount = 0;
    Tuple<int, int> Period;
    while (true)
    {
        if (GetMonthEnd(StartDate) > EndDate)
            break;
        else
        {
            MonthsCount += 1;
            StartDate = StartDate.AddMonths(1);
        }
    }
    int RemainingDays = (EndDate - StartDate).Days + 1;
    Period = new Tuple<int, int>(MonthsCount, RemainingDays);
    return Period;
}

Использование:

Tuple<int, int> Period = CalcPeriod(FromDate, ToDate);

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


1
public static int PayableMonthsInDuration(DateTime StartDate, DateTime EndDate)
{
    int sy = StartDate.Year; int sm = StartDate.Month; int count = 0;
    do
    {
        count++;if ((sy == EndDate.Year) && (sm >= EndDate.Month)) { break; }
        sm++;if (sm == 13) { sm = 1; sy++; }
    } while ((EndDate.Year >= sy) || (EndDate.Month >= sm));
    return (count);
}

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


1

Есть 3 случая: тот же год, предыдущий год и другие годы.

Если день месяца не имеет значения ...

public int GetTotalNumberOfMonths(DateTime start, DateTime end)
{
    // work with dates in the right order
    if (start > end)
    {
        var swapper = start;
        start = end;
        end = swapper;
    }

    switch (end.Year - start.Year)
    {
        case 0: // Same year
            return end.Month - start.Month;

        case 1: // last year
            return (12 - start.Month) + end.Month;

        default:
            return 12 * (3 - (end.Year - start.Year)) + (12 - start.Month) + end.Month;
    }
}

1

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

public string getEndDate (DateTime startDate,decimal monthCount)
{
    int y = startDate.Year;
    int m = startDate.Month;

    for (decimal  i = monthCount; i > 1; i--)
    {
        m++;
        if (m == 12)
        { y++;
            m = 1;
        }
    }
    return string.Format("{0}-{1}-{2}", y.ToString(), m.ToString(), startDate.Day.ToString());
}

Пожалуйста, ответьте на английском (против любого придуманного языка ...)
kleopatra

Почему бы просто не сделать startDate.AddMonths (monthCount) .ToShortDateString ()? Это не отвечает на первоначальный вопрос, который был задан в любом случае!
TabbyCool

о, извините @TabbyCool, этот код хорошо работает в моей программе! Правило программистов гласит: сначала работает код, а затем происходит оптимизация! танкс за ваш комментарий :)
Реза Ахлаги

1

Мое понимание общей разницы в месяцах между двумя датами имеет неотъемлемую и дробную часть (дата имеет значение).

Неотъемлемой частью является полная месячная разница.

Дробная часть, для меня, это разница в процентах от дня (до полных дней месяца) между начальным и конечным месяцами.

public static class DateTimeExtensions
{
    public static double TotalMonthsDifference(this DateTime from, DateTime to)
    {
        //Compute full months difference between dates
        var fullMonthsDiff = (to.Year - from.Year)*12 + to.Month - from.Month;

        //Compute difference between the % of day to full days of each month
        var fractionMonthsDiff = ((double)(to.Day-1) / (DateTime.DaysInMonth(to.Year, to.Month)-1)) -
            ((double)(from.Day-1)/ (DateTime.DaysInMonth(from.Year, from.Month)-1));

        return fullMonthsDiff + fractionMonthsDiff;
    }
}

С этим расширением таковы результаты:

2/29/2000 TotalMonthsDifference 2/28/2001 => 12
2/28/2000 TotalMonthsDifference 2/28/2001 => 12.035714285714286
01/01/2000 TotalMonthsDifference 01/16/2000 => 0.5
01/31/2000 TotalMonthsDifference 01/01/2000 => -1.0
01/31/2000 TotalMonthsDifference 02/29/2000 => 1.0
01/31/2000 TotalMonthsDifference 02/28/2000 => 0.9642857142857143
01/31/2001 TotalMonthsDifference 02/28/2001 => 1.0

1

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

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

Например, если у вас дата 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
}

1

Основываясь на превосходной работе DateTimeSpan, проделанной выше, я немного нормализовал код; это, кажется, работает довольно хорошо:

public class DateTimeSpan
{
  private DateTimeSpan() { }

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

  public int Years { get; private set; } = 0;
  public int Months { get; private set; } = 0;
  public int Days { get; private set; } = 0;
  public int Hours { get; private set; } = 0;
  public int Minutes { get; private set; } = 0;
  public int Seconds { get; private set; } = 0;
  public int Milliseconds { get; private set; } = 0;

  public static DateTimeSpan CompareDates(DateTime StartDate, DateTime EndDate)
  {
    if (StartDate.Equals(EndDate)) return new DateTimeSpan();
    DateTimeSpan R = new DateTimeSpan();
    bool Later;
    if (Later = StartDate > EndDate)
    {
      DateTime D = StartDate;
      StartDate = EndDate;
      EndDate = D;
    }

    // Calculate Date Stuff
    for (DateTime D = StartDate.AddYears(1); D < EndDate; D = D.AddYears(1), R.Years++) ;
    if (R.Years > 0) StartDate = StartDate.AddYears(R.Years);
    for (DateTime D = StartDate.AddMonths(1); D < EndDate; D = D.AddMonths(1), R.Months++) ;
    if (R.Months > 0) StartDate = StartDate.AddMonths(R.Months);
    for (DateTime D = StartDate.AddDays(1); D < EndDate; D = D.AddDays(1), R.Days++) ;
    if (R.Days > 0) StartDate = StartDate.AddDays(R.Days);

    // Calculate Time Stuff
    TimeSpan T1 = EndDate - StartDate;
    R.Hours = T1.Hours;
    R.Minutes = T1.Minutes;
    R.Seconds = T1.Seconds;
    R.Milliseconds = T1.Milliseconds;

    // Return answer. Negate values if the Start Date was later than the End Date
    if (Later)
      return new DateTimeSpan(-R.Years, -R.Months, -R.Days, -R.Hours, -R.Minutes, -R.Seconds, -R.Milliseconds);
    return R;
  }
}

При сравнении с CompareDates(x, y)где x={01/02/2019 00:00:00}и y={01/05/2020 00:00:00}затем Monthsдает мне2
Bassie

1

Эта простая статическая функция вычисляет долю месяцев между двумя датами, например

  • 1.1. до 31.1. = 1,0
  • 1.4. до 15,4. = 0,5
  • 16,4. до 30,4. = 0,5
  • 1.3. до 1.4. = 1 + 1/30

Функция предполагает, что первая дата меньше второй даты. Чтобы справиться с отрицательными временными интервалами, можно легко изменить функцию, введя знак и переменную swap в начале.

public static double GetDeltaMonths(DateTime t0, DateTime t1)
{
     DateTime t = t0;
     double months = 0;
     while(t<=t1)
     {
         int daysInMonth = DateTime.DaysInMonth(t.Year, t.Month);
         DateTime endOfMonth = new DateTime(t.Year, t.Month, daysInMonth);
         int cutDay = endOfMonth <= t1 ? daysInMonth : t1.Day;
         months += (cutDay - t.Day + 1) / (double) daysInMonth;
         t = new DateTime(t.Year, t.Month, 1).AddMonths(1);
     }
     return Math.Round(months,2);
 }

0

Чтобы иметь возможность рассчитать разницу между двумя датами в месяцах, это совершенно логичная вещь, которая необходима во многих бизнес-приложениях. Несколько кодеров, которые предоставили комментарии, такие как - какая разница в месяцах между «май 1,2010» и «16,2010 июня», какая разница в месяцах между 31 декабря 2010 года и 1 января 2011 года? - не смогли понять самые основы бизнес-приложений.

Вот ответ на 2 вышеупомянутых комментария - Количество месяцев между 1 мая 2010 года и 16 июня 2010 года составляет 1 месяц, количество месяцев между 31 декабря 2010 года и 1 января 2011 года равно 0. Это было бы очень глупо вычислять их как 1,5 месяца и 1 секунду, как предложили кодеры выше.

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

Проблема в том, что такая функция не включена в C # или VB.NET в этом отношении. Datediff учитывает только годы или компонент месяца, поэтому фактически бесполезен.

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

Вы жили в краткосрочной аренде с 18 февраля по 23 августа. Сколько месяцев вы там пробыли? Ответ прост - 6 месяцев

У вас есть банковский счет, на котором проценты начисляются и выплачиваются в конце каждого месяца. Вы вносите деньги 10 июня и снимаете их 29 октября (в том же году). Сколько месяцев вы интересуетесь? Очень простой ответ - 4 месяца (опять же лишние дни не имеют значения)

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


5
Это одна из причин, почему бухгалтерский учет не математика. В бухгалтерском учете результат зависит от того, как вы его рассчитываете. Я знаю ваши точки зрения и знаю «общий бизнес-взгляд» на это, но это объяснение явно неверно. Между 2012.11.30 и 2012.12.01 есть либо 0, либо 1/30, либо 1/31, либо 1 или 2 месяца, в зависимости от того, что вы просили . Были ли даты исключительными или включающими? Вы спрашивали, сколько месяцев прошло, коснулось или прошло? Вы хотели округлить, округлить или точно?
Кетцалькоатль

3
Теперь объясните это деловому парню или бухгалтеру, и они озадаченно посмотрят на вас. Для них всегда "настолько очевидно, что они, конечно, имели в виду X, Y и Z, как ты мог думать иначе?" Теперь найдите несколько бизнесменов и постарайтесь, чтобы они договорились о теме. Бухгалтеры с большей вероятностью согласятся, потому что в какой-то момент они будут использовать математические расчеты, чтобы проверить, с какими вариантами они могут случайно подвести итоги за один и тот же период дважды, и т. Д. Даже ваши примеры расчетов являются спорными и зависят от региона или явно недействительны, как они предполагают дополнительные бизнес-правила, такие как игнорирование дополнительных дней.
Кетцалькоатль

2
-1 Вы предполагаете, что все программное обеспечение является «бизнес-приложением». Назначение рассматриваемого кода не упоминается. Вы также предполагаете, что все «бизнес-приложения» имеют одинаковые правила, что определенно не соответствует действительности.
Джесси Уэбб

0

Расширенная структура Киркс с ToString (формат) и продолжительностью (длинные мс)

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

0
  var dt1 = (DateTime.Now.Year * 12) + DateTime.Now.Month;
  var dt2 = (DateTime.Now.AddMonths(-13).Year * 12) + DateTime.Now.AddMonths(-13).Month;
  Console.WriteLine(dt1);
  Console.WriteLine(dt2);
  Console.WriteLine((dt1 - dt2));
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.