tags:

views:

163

answers:

8

Example: given two dates below, finish is always greater than or equal to start

start = 2001 Jan 01

finish = 2002 Mar 15

So from 2001 Jan 01 to the end of 2002 Feb

months = 12 + 2 = 14

For 2002 March

15/30 = 0.5

so grand total is 14.5 months difference.

It's very easy to work out by hand but how do I code it elegantly? At the moment I have the combination of a lot of if else and while loops to achieve what I want but I believe there are simpler solutions out there.

Update: the output needs to be precise (not approximation) for example: if start 2001 Jan 01 and finish 2001 Apr 16, the output should be 1 + 1 + 1= 3 (for Jan, Feb and Mar) and 16 / 31 = 0.516 month, so the total is 3.516.

Another example would be if I start on 2001 Jul 5 and finish on 2002 Jul 10, the output should be 11 month up to the end of June 2002, and (31-5)/31 = 0.839 and 10/31 = 0.323 months, so the total is 11 + 0.839 + 0.323 = 12.162.

I extended Josh Stodola's code and Hightechrider's code:

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;
}
+1  A: 

One way to do this is that you'll see around quite a bit is:

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

However, you want "partial months" which this doesn't give. But what is the point in comparing apples (January/March/May/July/August/October/December) with oranges (April/June/September/November) or even bananas that are sometimes coconuts (February)?

An alternative is to import Microsoft.VisualBasic and do this:

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

However again:

The return value for DateInterval.Month is calculated purely from the year and month parts of the arguments

[Source]

Graphain
A: 

This should get you where you need to go:

DateTime start = new DateTime(2001, 1, 1);
DateTime finish = new DateTime(2002, 3, 15);
double diff = (finish - start).TotalDays / 30;
APShredder
+1  A: 

Depending on how exactly you want your logic to work, this would at least give you a decent approximation:

// 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;
}
Dan Tao
It is smart mathematically but I don't think it works realistically because it's still assumptions and approximation.
Jeffrey C
Jeffrey is right. He wants the difference between Feb 1 and Mar 1 to be exactly 1 month, but you call it less than 1 month.
Gabe
@Jeffrey: That's why I said "decent approximation" (I posted this answer before you clarified exactly what you wanted your result to be). It would've been a good option if you wanted, e.g., to round the result to the nearest 0.5 months. I would update my suggestion, but it looks like you've got plenty of other ideas now.
Dan Tao
@Gabe: Yeah, you're right. It's just that I posted this answer before that exact requirement had been articulated. It was just an idea to throw the OP's way.
Dan Tao
@Dan Tao, you know sometimes it takes time and people's answers to narrow down the exact requirements. By just going through that "whole process" makes me think through the problem. But thank you anyway and you are correct that if the requirement was to work out the approximate months diff then your solution would have been the best so far.
Jeffrey C
@Jeffrey: Of course! I only got a bit defensive because somebody downvoted me. I am a crybaby.
Dan Tao
@Dan Tao, don't take it personal. L0L
Jeffrey C
A: 

the framework as a TimeSpan object that is a result of subtracting two dates.

the subtraction is already considering the various option of February(28/29 days a month) so in my opinion this is the best practice after you got it you can format it the way you like best

        DateTime dates1 = new DateTime(2010, 1, 1);
        DateTime dates2 = new DateTime(2010, 3, 15);
        var span = dates1.Subtract(dates2);
        span.ToString("your format here");
Itai Zolberg
+5  A: 

I gave an int answer before, and then realized what you asked for a more precise answer. I was tired, so I deleted and went to bed. So much for that, I was unable to fall asleep! For some reason, this question really bugged me, and I had to solve it. So here you go...

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

Now I shall have sweet dreams. Goodnight :)

Josh Stodola
@Josh, thanks for your answer. Initially it seems to be perfect until I ran a serious of tests. I notice you made an assumption that d1 is always a full calendar month. So this won't work for cases like: start 2001 Apr 15 and finish on 2001 Jun 15. Your code returns 2.5 but in fact it's 2 half months and 1 full months which should return 2 months difference.
Jeffrey C
@Josh, I couldn't find any "simple" solution so I went full while loop month by month from the start month to finish month, and work out for each month if it's a full month or partial month, then sum the total. But I don't know if there's any simpler solution in existence. But then this still won't work for case like start 10th of july 2001 and finish 9th of july 2002, this in fact should return 12 months but the code returns 11 point something so then I had to manually treat those cases individually. So painful.
Jeffrey C
@Jeffrey Your formula is fairly unique
Josh Stodola
@Josh, which formula did you mean? The one (dumb one) described in above comment?
Jeffrey C
@Jeffrey I refined the function a little, and now it better matches your test cases :)
Josh Stodola
+2  A: 

What you want is probably something close to this ... which pretty much follows your explanation as to how to calculate it:

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 This has not been tested very well but it shows how to handle problems like this by finding well known dates at the start of months around the problem values and then working off them.

While loops for date time calculations are always a bad idea: see http://www.zuneboards.com/forums/zune-news/38143-cause-zune-30-leapyear-problem-isolated.html

Hightechrider
Would this work if start and finish are within the same month?
Jeffrey C
It appears to on a brief test (monthsInBetween goes to -1) ... find an example where it doesn't work and I'll take a look. Like I said "This has not been tested very well" but it should give you a good start on a non-looping way to calculate it precisely.
Hightechrider
You are correct with the -1 thing. I updated the question to include the correct method (I believe). Now I am not sure whos answer to accept.
Jeffrey C
+1  A: 

Just improved Josh's answer

    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;
    } 
The King
A: 
    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;
    }
Alex Krupnov