Как рассчитать фактическую разницу месяцев (календарный год, а не приближение) между двумя данными датами в C #? - PullRequest
7 голосов
/ 20 июля 2010

Пример: учитывая две даты ниже, финиш всегда больше или равен старту

start = 2001 Jan 01

finish = 2002 март 15

Итак, с 2001 января 01 до конца 2002 февраля

месяцев = 12 + 2 = 14

На март 2002 года

15/30 = 0,5

, поэтому общая сумма составляет 14,5 месяцев.

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

Обновление: вывод должен быть точное (не приближение), например: если начало 2001 г. 01 января и окончание 16 апреля 2001 г., выходные данные должны быть 1 + 1 + 1 = 3 (для января, февраля и марта) и 16/31 = 0,516месяц, итого всего 3,516.

Другой пример: если я начну 5 июля 2001 года и закончу 10 июля 2002 года, то выходной должен быть за 11 месяцев до конца июня 2002 года, и (31-5) / 31 = 0,839 и 10/31 = 0,323 месяца, поэтому общее количество составляет 11 + 0,839 + 0,323 = 12,162.

Я расширил код Джоша Стодола и Hightechrider код:

public static decimal GetMonthsInRange(this IDateRange thisDateRange)
{
    var start = thisDateRange.Start;
    var finish = thisDateRange.Finish;

    var monthsApart = Math.Abs(12*(start.Year - finish.Year) + start.Month - finish.Month) - 1;

    decimal daysInStartMonth = DateTime.DaysInMonth(start.Year, start.Month);
    decimal daysInFinishMonth = DateTime.DaysInMonth(finish.Year, finish.Month);

    var daysApartInStartMonth = (daysInStartMonth - start.Day + 1)/daysInStartMonth;
    var daysApartInFinishMonth = finish.Day/daysInFinishMonth;

    return monthsApart + daysApartInStartMonth + daysApartInFinishMonth;
}

Ответы [ 11 ]

9 голосов
/ 20 июля 2010

Я дал int ответ раньше, а затем понял, что вы просили более точный ответ.Я устал, поэтому я удалил и пошел спать.Так много для этого, я не мог заснуть!По какой-то причине этот вопрос действительно беспокоил меня, и мне пришлось его решать.Итак, поехали ...

static void Main(string[] args)
{
    decimal diff;

    diff = monthDifference(new DateTime(2001, 1, 1), new DateTime(2002, 3, 15));
    Console.WriteLine(diff.ToString("n2")); //14.45

    diff = monthDifference(new DateTime(2001, 1, 1), new DateTime(2001, 4, 16));
    Console.WriteLine(diff.ToString("n2")); //3.50

    diff = monthDifference(new DateTime(2001, 7, 5), new DateTime(2002, 7, 10));
    Console.WriteLine(diff.ToString("n2")); //12.16

    Console.Read();
}

static decimal monthDifference(DateTime d1, DateTime d2)
{
    if (d1 > d2)
    {
        DateTime hold = d1;
        d1 = d2;
        d2 = hold;
    }

    int monthsApart = Math.Abs(12 * (d1.Year-d2.Year) + d1.Month - d2.Month) - 1;
    decimal daysInMonth1 = DateTime.DaysInMonth(d1.Year, d1.Month);
    decimal daysInMonth2 = DateTime.DaysInMonth(d2.Year, d2.Month);

    decimal dayPercentage = ((daysInMonth1 - d1.Day) / daysInMonth1)
                          + (d2.Day / daysInMonth2);
    return monthsApart + dayPercentage;
}

Теперь у меня будут сладкие сны.Спокойной ночи:)

2 голосов
/ 20 июля 2010

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

var startofd1 = d1.AddDays(-d1.Day + 1);
var startOfNextMonthAfterd1 = startofd1.AddMonths(1);      // back to start of month and then to next month
int daysInFirstMonth = (startOfNextMonthAfterd1 - startofd1).Days;
double fraction1 = (double)(daysInFirstMonth - (d1.Day - 1)) / daysInFirstMonth;     // fractional part of first month remaining

var startofd2 = d2.AddDays(-d2.Day + 1);
var startOfNextMonthAfterd2 = startofd2.AddMonths(1);      // back to start of month and then to next month
int daysInFinalMonth = (startOfNextMonthAfterd2 - startofd2).Days;
double fraction2 = (double)(d2.Day - 1) / daysInFinalMonth;     // fractional part of last month

// now find whole months in between
int monthsInBetween = (startofd2.Year - startOfNextMonthAfterd1.Year) * 12 + (startofd2.Month - startOfNextMonthAfterd1.Month);

return monthsInBetween + fraction1 + fraction2;

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

Хотя циклы для вычисления даты и времени всегда плохая идея: см. http://www.zuneboards.com/forums/zune-news/38143-cause-zune-30-leapyear-problem-isolated.html

1 голос
/ 20 июля 2010

Только что улучшил ответ Джоша

    static decimal monthDifference(DateTime d1, DateTime d2)
    {
        if (d1 > d2)
        {
            DateTime hold = d1;
            d1 = d2;
            d2 = hold;
        }

        decimal monthsApart = Math.Abs((12 * (d1.Year - d2.Year)) + d2.Month - d1.Month - 1);


        decimal daysinStartingMonth = DateTime.DaysInMonth(d1.Year, d1.Month);
        monthsApart = monthsApart + (1-((d1.Day - 1) / daysinStartingMonth));

        //  Replace (d1.Day - 1) with d1.Day incase you DONT want to have both inclusive difference.



        decimal daysinEndingMonth = DateTime.DaysInMonth(d2.Year, d2.Month);
        monthsApart = monthsApart + (d2.Day / daysinEndingMonth);


        return monthsApart;
    } 
1 голос
/ 20 июля 2010

В зависимости от того, как именно вы хотите, чтобы ваша логика работала, это, по крайней мере, даст вам достойное приближение:

// 365 days per year + 1 day per leap year = 1461 days every 4 years
// But years divisible by 100 are not leap years
// So 1461 days every 4 years - 1 day per 100th year = 36524 days every 100 years
// 12 months per year = 1200 months every 100 years
const double DaysPerMonth = 36524.0 / 1200.0;

double GetMonthsDifference(DateTime start, DateTime finish)
{
    double days = (finish - start).TotalDays;
    return days / DaysPerMonth;
}
1 голос
/ 20 июля 2010

Один из способов сделать это состоит в том, что вы увидите вокруг немного:

private static int monthDifference(DateTime startDate, DateTime endDate)
{
    int monthsApart = 12 * (startDate.Year - endDate.Year) + startDate.Month - endDate.Month;
    return Math.Abs(monthsApart);
}

Однако вам нужны "неполные месяцы", которые это не дает. Но какой смысл сравнивать яблоки (январь / март / май / июль / август / октябрь / декабрь) с апельсинами (апрель / июнь / сентябрь / ноябрь) или даже бананами, которые иногда являются кокосовыми (февраль)?

Альтернативой является импорт Microsoft.VisualBasic и выполнение этого:

    DateTime FromDate;
    DateTime ToDate;
    FromDate = DateTime.Parse("2001 Jan 01");
    ToDate = DateTime.Parse("2002 Mar 15");

    string s = DateAndTime.DateDiff (DateInterval.Month, FromDate,ToDate, FirstDayOfWeek.System, FirstWeekOfYear.System ).ToString();

Однако опять же:

Возвращаемое значение для DateInterval.Month рассчитывается чисто из года и месяца аргументов

[Источник]

0 голосов
/ 01 февраля 2018

Приведенный ниже расчет соответствует тому, как налоговый орган Нидерландов рассчитывает месяцы. Это означает, что когда начальный день, например, 22 февраля, 23 марта должно получиться что-то выше 1, а не просто что-то вроде 0,98.

    private decimal GetMonthDiffBetter(DateTime date1, DateTime date2)
    {
        DateTime start = date1 < date2 ? date1 : date2;
        DateTime end = date1 < date2 ? date2 : date1;

        int totalYearMonths = (end.Year - start.Year) * 12;
        int restMonths = end.Month - start.Month;
        int totalMonths = totalYearMonths + restMonths;

        decimal monthPart = (decimal)end.Day / (decimal)start.Day;
        return totalMonths - 1 + monthPart;
    }`
0 голосов
/ 15 января 2016

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

public decimal getMonthDiff(DateTime date1, DateTime date2) {
    // Make parameters agnostic
    var earlyDate = (date1 < date2 ? date1 : date2);
    var laterDate = (date1 > date2 ? date1 : date2);

    // Calculate the change in full months
    decimal months = ((laterDate.Year - earlyDate.Year) * 12) + (laterDate.Month - earlyDate.Month) - 1;

    // Add partial months based on the later date
    if (earlyDate.Day <= laterDate.Day) {
        decimal laterMonthDays = DateTime.DaysInMonth(laterDate.Year, laterDate.Month);
        decimal laterPartialMonth = ((laterDate.Day - earlyDate.Day) / laterMonthDays);
        months += laterPartialMonth + 1;
    } else {
        var laterLastMonth = laterDate.AddMonths(-1);
        decimal laterLastMonthDays = DateTime.DaysInMonth(laterLastMonth.Year, laterLastMonth.Month);
        decimal laterPartialMonth = ((laterLastMonthDays - earlyDate.Day + laterDate.Day) / laterLastMonthDays);
        months += laterPartialMonth;
    }
    return months;
}
0 голосов
/ 08 сентября 2012

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

    [Test]
    public void Calculate_Total_Months_Difference_Between_Two_Dates()
    {
        var startDate = DateTime.Parse( "10/8/1996" );

        var finishDate = DateTime.Parse( "9/8/2012" );  // this should be now:


        int numberOfMonthsBetweenStartAndFinishYears = getNumberOfMonthsBetweenStartAndFinishYears( startDate, finishDate );


        int absMonthsApartMinusOne = getAbsMonthsApartMinusOne( startDate, finishDate, numberOfMonthsBetweenStartAndFinishYears );


        decimal daysLeftToCompleteStartMonthPercentage = getDaysLeftToCompleteInStartMonthPercentage( startDate );


        decimal daysCompletedSoFarInFinishMonthPercentage = getDaysCompletedSoFarInFinishMonthPercentage( finishDate );

        // .77 + .26 = 1.04
        decimal totalDaysDifferenceInStartAndFinishMonthsPercentage = daysLeftToCompleteStartMonthPercentage + daysCompletedSoFarInFinishMonthPercentage;


        // 13 + 1.04 = 14.04 months difference.
        decimal totalMonthsDifference = absMonthsApartMinusOne + totalDaysDifferenceInStartAndFinishMonthsPercentage;

        //return totalMonths;

    }

    private static int getNumberOfMonthsBetweenStartAndFinishYears( DateTime startDate, DateTime finishDate )
    {
        int yearsApart = startDate.Year - finishDate.Year;

        const int INT_TotalMonthsInAYear = 12;

        // 12 * -1 = -12
        int numberOfMonthsBetweenYears = INT_TotalMonthsInAYear * yearsApart;

        return numberOfMonthsBetweenYears;
    }

    private static int getAbsMonthsApartMinusOne( DateTime startDate, DateTime finishDate, int numberOfMonthsBetweenStartAndFinishYears )
    {
        // This may be negative i.e. 7 - 9 = -2
        int numberOfMonthsBetweenStartAndFinishMonths = startDate.Month - finishDate.Month;

        // Absolute Value Of Total Months In Years Plus The Simple Months Difference Which May Be Negative So We Use Abs Function
        int absDiffInMonths = Math.Abs( numberOfMonthsBetweenStartAndFinishYears + numberOfMonthsBetweenStartAndFinishMonths );

        // Subtract one here because we are going to use a perecentage difference based on the number of days left in the start month
        // and adding together the number of days that we've made it so far in the finish month.
        int absMonthsApartMinusOne = absDiffInMonths - 1;

        return absMonthsApartMinusOne;
    }

    /// <summary>
    /// For example for 7/8/2012 there are 24 days left in the month so about .77 percentage of month is left.
    /// </summary>
    private static decimal getDaysLeftToCompleteInStartMonthPercentage( DateTime startDate )
    {
        // startDate = "7/8/2012"

        // 31
        decimal daysInStartMonth = DateTime.DaysInMonth( startDate.Year, startDate.Month );

        // 31 - 8 = 23 
        decimal totalDaysInStartMonthMinusStartDay = daysInStartMonth - startDate.Day;

        // add one to mark the day as being completed. 23 + 1 = 24
        decimal daysLeftInStartMonth = totalDaysInStartMonthMinusStartDay + 1;

        // 24 / 31 = .77 days left to go in the month
        decimal daysLeftToCompleteInStartMonthPercentage = daysLeftInStartMonth / daysInStartMonth;

        return daysLeftToCompleteInStartMonthPercentage;
    }

    /// <summary>
    /// For example if the finish date were 9/8/2012 we've completed 8 days so far or .24 percent of the month
    /// </summary>
    private static decimal getDaysCompletedSoFarInFinishMonthPercentage( DateTime finishDate )
    {
        // for septebmer = 30 days in month.
        decimal daysInFinishMonth = DateTime.DaysInMonth( finishDate.Year, finishDate.Month );

        // 8 days divided by 30 = .26 days completed so far in finish month.
        decimal daysCompletedSoFarInFinishMonthPercentage = finishDate.Day / daysInFinishMonth;

        return daysCompletedSoFarInFinishMonthPercentage;
    }
0 голосов
/ 20 июля 2010
    private Double GetTotalMonths(DateTime future, DateTime past)
    {
        Double totalMonths = 0.0;

        while ((future - past).TotalDays > 28 )
        {
            past = past.AddMonths(1);
            totalMonths += 1;
        }

        var daysInCurrent = DateTime.DaysInMonth(future.Year, future.Month);
        var remaining = future.Day - past.Day;

        totalMonths += ((Double)remaining / (Double)daysInCurrent);
        return totalMonths;
    }
0 голосов
/ 20 июля 2010

каркас как объект TimeSpan, который является результатом вычитания двух дат.

вычитание уже рассматривает различные варианты февраля (28/29 дней в месяц), так что, на мой взгляд, это лучшая практика после того, как вы его получили, вы можете отформатировать его так, как вам больше нравится

        DateTime dates1 = new DateTime(2010, 1, 1);
        DateTime dates2 = new DateTime(2010, 3, 15);
        var span = dates1.Subtract(dates2);
        span.ToString("your format here");
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...