calculating the difference in months between two dates
Asked Answered
S

27

149

In C#/.NET TimeSpan has TotalDays, TotalMinutes, etc. but I can't figure out a formula for total months difference. Variable days per month and leap years keep throwing me off. How can I get TotalMonths?

Edit Sorry for not being more clear: I know I can't actually get this from TimeSpan but I thought using TotalDays and TotalMinutes would be a good example to express what I was looking for ... except I'm trying to get Total Months.

Example: Dec 25, 2009 - Oct 6, 2009 = 2 TotalMonths. Oct 6th to Nov 5th equals 0 months. On Nov 6th, 1 month. On Dec 6th, 2 months

Systole answered 6/10, 2009 at 14:36 Comment(7)
What are you expecting for Dec 25, 2009 - Oct 6, 2009?Chitter
How do you define TimeSpan in months?Agatha
@Agatha - Without dates you could define a month as 30 days and be pretty accurate.Supererogate
It was merged with this question by a mod for some reason.Boron
Actually, u need to read my post here, which answers this question & provides a coded solution, #1916858 ignore the trolls (brianary) & pay attention to my conversation via comments with supercat. The months that r at the begining & end of a timespan we r calling "Orphaned Months", & the question comes down to how to define these orphaned months in terms of days - once u have determined that (& how u want to define it), the rest is just code (which is included). My def. is based on what I think my users will expectPathogenic
what is the result of total months between 20-01-2021 and 20-03-2021? I guess it should be 2.0xx, but if you considered Feb as 30 days, you'll get a different answer "1.8xx or something" I've answered this in another question check it out if you like https://mcmap.net/q/82037/-month-difference-between-2-datesDoering
Check this: codingfusion.com/Post/…Bebel
E
258

You won't be able to get that from a TimeSpan, because a "month" is a variable unit of measure. You'll have to calculate it yourself, and you'll have to figure out how exactly you want it to work.

For example, should dates like July 5, 2009 and August 4, 2009 yield one month or zero months difference? If you say it should yield one, then what about July 31, 2009 and August 1, 2009? Is that a month? Is it simply the difference of the Month values for the dates, or is it more related to an actual span of time? The logic for determining all of these rules is non-trivial, so you'll have to determine your own and implement the appropriate algorithm.

If all you want is simply a difference in the months--completely disregarding the date values--then you can use this:

public static int MonthDifference(this DateTime lValue, DateTime rValue)
{
    return (lValue.Month - rValue.Month) + 12 * (lValue.Year - rValue.Year);
}

Note that this returns a relative difference, meaning that if rValue is greater than lValue, then the return value will be negative. If you want an absolute difference, you can use this:

public static int MonthDifference(this DateTime lValue, DateTime rValue)
{
    return Math.Abs((lValue.Month - rValue.Month) + 12 * (lValue.Year - rValue.Year));
}
Entanglement answered 6/10, 2009 at 14:39 Comment(8)
@Systole this just an approximation, if you want to know the true .Month and .Years - I've just posted an answer for that which you can read. Although, as far as approximations so, this is a good approximation (props to Adam Robinson) however you should keep in mind that if you use any of these approximations you are just unintentionally lying to your users.Pathogenic
@Erx_VB.NExT.Coder: Thanks for the props, but while your answer states that none of the answers take the fact that a month is a variable unit of measure into account, it seems that most of them do; they just don't use your particular approximation. Case in point, the very first sentence in my answer indicates that it's variable. Any answer, yours included, is an approximation, simply because it is not a precise answer. Your "2 months" result can mean different things for different inputs, so it is an approximation.Entanglement
mine is not an approximation though, if today is the 14th of March, then the two pervious months are calculated based on the fact that jan had 31 days in it, and feb hasd 29 days in it. now, youre correct in that my method is not the definition of a "general" month, & yours is! However, mine only applies if you are reporting things like "This comment was posted x months and y days AGO", the "AGO" part makes the difference, because its referring to the previous x months, those previous x months need to be calculated based on how many days were present in those x months! link....Pathogenic
does that make sense? so if you are referring to particular, known months, then my method is 100% accurate and you're would be an approximation, however, if you are referring to a month in general, you're approximation would be a better idea, and mine would just be a bad idea (it isn't made for that and there would be no point in using it). Here is the link to my article describing the problem and providing a solution: #1916858Pathogenic
Very simple solution. Like thisPurify
In one hand given solution is simple, in another hand it does not solve initial question's requirements "Oct 6th to Nov 5th equals 0 months". For some reason author still selected this as an answer :) The trick is that correct answer is also simple(and based on your answer): var dayOfMonth = int.Parse(startTime.ToString("dd")); DateTime correctedStart = startTime.AddDays(-(dayOfMonth-1)); DateTime correctedEnd = endTime.AddDays(-(dayOfMonth - 1)); var monthsDiff = (correctedEnd.Month - correctedStart.Month) + 12 * (correctedEnd.Year - correctedStart.Year);Dusen
This seems to be the same logic used by the Sql Server DateDiff(month, ...) function. It also has the advantage of being extremely concise and easy to explain and understand. I would explain it as follows... how many pages in the calendar would you have to turn to get from one date to the other?Jalousie
what is the result of total months between 20-01-2021 and 20-03-2021? I guess it should be 2.0xx, but if you considered Feb as 30 days, you'll get a different answer "1.8xx or something" I've answered this in another question check it out if you like https://mcmap.net/q/82037/-month-difference-between-2-datesDoering
M
64

(I realize this is an old question, but...)

This is relatively painful to do in pure .NET. I'd recommend my own Noda Time library, which is particularly designed for things like this:

LocalDate start = new LocalDate(2009, 10, 6);
LocalDate end = new LocalDate(2009, 12, 25);
Period period = Period.Between(start, end);
int months = period.Months;

(There are other options, e.g. if you only want a count of months even across years, you'd use Period period = Period.Between(start, end, PeriodUnits.Months);)

Monmouth answered 12/8, 2014 at 12:24 Comment(5)
I downloaded your library and I copied the code which you wrote above, but I am receiving compile time error. Error 1 Operator '-' cannot be applied to operands of type 'NodaTime.LocalDate' and 'NodaTime.LocalDate'. I know this post from 5 years, did any thing changed from that time , which make the this code not working?Guacharo
@HakamFostok: Sorry - it will work when 2.0 is released, but until then you need to use Period.Between. Have edited the code so it'll work with NodaTime 1.3.1.Monmouth
thanks a lot the NodaTime library did exactly what I want to do. I wanted to calculated not just the months between two dates but the remaining days also, and this what NodaTime has done exactly, thanks again.Guacharo
@JonSkeet That library of yours is truly black magic. Dates bite me all the time. That code snippet saved me a massive amount of time.Thermogenesis
Thank you very much for your brilliant library, a must-have lifesaver for sure! 🍻Marindamarinduque
P
28

Maybe you don't want to know about month fractions; What about this code?


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"


Pod answered 6/10, 2009 at 15:4 Comment(1)
I don't understand the * 100. Should it be * 12?Hellas
K
16

I've written a very simple extension method on DateTime and DateTimeOffset to do this. I wanted it to work exactly like a TotalMonths property on TimeSpan would work: i.e. return the count of complete months between two dates, ignoring any partial months. Because it's based on DateTime.AddMonths() it respects different month lengths and returns what a human would understand as a period of months.

(Unfortunately you can't implement it as an extension method on TimeSpan because that doesn't retain knowledge of the actual dates used, and for months they're important.)

The code and tests are both available on GitHub. The code is very simple:

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

And it passes all these unit test cases:

// 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)));
Kittle answered 5/8, 2014 at 10:8 Comment(2)
Rustic, but best solution. Copies and pasted. Thank youAldred
Thank you, great solution this work charm and fulfil my requirement.Protactinium
B
9

You will have to define what you mean by TotalMonths to start with.
A simple definition puts a month at 30.4 days (365.25 / 12).

Beyond that, any definition including fractions seems useless, and the more common integer value (whole months between dates) also depends on non-standard business rules.

Binky answered 6/10, 2009 at 14:41 Comment(0)
T
8

You need to work it out yourself off the datetimes. How you deal with the stub days at the end will depend on what you want to use it for.

One method would be to count month and then correct for days at the end. Something like:

   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;
Titanomachy answered 6/10, 2009 at 14:55 Comment(1)
Nice code, although, 1 bug: instead: (as 28 February + 1 month == 28 March) :-) //decimal daysInEndMonth = (end - end.AddMonths(1)).Days; I suggest: decimal daysInEndMonth = DateTime.DaysInMonth(end.Year, end.Month) * -1;Structure
N
4

There are not a lot of clear answers on this because you are always assuming things.

This solution calculates between two dates the months between assuming you want to save the day of month for comparison, (meaning that the day of the month is considered in the calculation)

Example, if you have a date of 30 Jan 2012, 29 Feb 2012 will not be a month but 01 March 2013 will.

It's been tested pretty thoroughly, probably will clean it up later as we use it, and takes in two dates instead of a Timespan, which is probably better. Hope this helps out anyone else.

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
}
Notum answered 7/1, 2014 at 18:55 Comment(0)
W
3

I would do it like this:

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;
}
Whomsoever answered 6/10, 2009 at 14:45 Comment(5)
That's certainly one algoritm, but it could be vastly simplified to return (dtOther.Month - dtThis.Month) + 12 * (dtOther.Year - dtThis.Year);Entanglement
Two problems: You are starting from 2 Dates, not a TimeSpan. Second, you calculate between the 1st of both months, that is a very questionable definition. Although it could be right sometimes.Binky
@Henk: Yes, of course that's not always right, that's why I said that this is how I would do it, not how anybody should do it. The OP didn't specify how the result should be calculated. @Adam: Wow, I thought way too complicated yet again...that happens all too often to me. Thanks for the comment, you are obviously right, your version is much better. I will use this from now on.Whomsoever
@Adam: why don't you submit this as an actual answer?! This is the most compact so far. Very slick.Systole
@Dinah: I didn't want to assume that's what you actually wanted. If it is, I have edited my previous answer to include this approach.Entanglement
T
3

The accepted answer works perfectly when you want full months.

I needed partial months. This is the solution I came up with for partial months:

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

I also needed a year difference with the same need for partial years. Here is the solution I came up with:

    /// <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);
    }
Trengganu answered 26/6, 2018 at 13:44 Comment(1)
You had logic bug in your YearDifference function when lValue.Month < rValue.Month - I've fixed that now, you may want to review...Urbannal
A
2

Old question I know, but might help someone. I've used @Adam accepted answer above, but then checked if the difference is 1 or -1 then check to see if it is a full calendar month's difference. So 21/07/55 and 20/08/55 would not be a full month, but 21/07/55 and 21/07/55 would be.

/// <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);
}
Allegorize answered 1/9, 2011 at 13:20 Comment(0)
D
2
case IntervalType.Month:
    returnValue = start.AddMonths(-end.Month).Month.ToString();
    break;
case IntervalType.Year:
    returnValue = (start.Year - end.Year).ToString();
    break;
Dah answered 7/11, 2012 at 19:23 Comment(2)
A description to go with the code would be beneficial to other readers as well.Denial
yeah please add some commentary.Thearchy
P
1

The problem with months is that it isn't really a simple measure - they aren't constant size. You would need to define your rules for what you want to include, and work from there. For example 1 Jan to 1 Feb - you could argue 2 months are involved there, or you could say that is one month. Then what about "1 Jan 20:00" to "1 Feb 00:00" - that isn't quite an entire full month. Is that 0? 1? what about the other way around (1 Jan 00:00 to 1 Feb 20:00)... 1? 2?

First define the rules, then you'll have to code it yourself, I'm afraid...

Parkins answered 17/3, 2011 at 12:36 Comment(0)
C
1

If you want to have a result 1 between 28th Feb and 1st March:

DateTime date1, date2;
int monthSpan = (date2.Year - date1.Year) * 12 + date2.Month - date1.Month
Canoe answered 17/3, 2011 at 12:37 Comment(1)
This seems to be the same logic used by the Sql Server DateDiff(month, ...) function. It also has the advantage of being extremely concise and easy to explain and understand. I would explain it as follows... how many pages in the calendar would you have to turn to get from one date to the other?Jalousie
K
1

This library calculates the difference of months, considering all parts of 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
Klaus answered 7/5, 2011 at 21:10 Comment(0)
P
1

Below is actually the most accurate way you can do it, since the definition of "1 Month" changes depending on which month it is, and non of the other answers take this into account! If you want more information about the issue which is not built into the framework, you can read this post: A Real Timespan Object With .Years & .Months (however, reading that post isn't necessary to understand and use the function below, it works 100%, without the inherent inaccuracies of the approximation others love to use - and feel free to replace the .ReverseIt function with the built-in .Reverse function you may have on your framework (it's just here for completeness).

Please note that you can get any number of dates/times accuracy, seconds & minutes, or seconds, minutes and days, anywhere up to years (which would contain 6 parts/segments). If you specify top two and it's over a year old, it will return "1 year and 3 months ago" and won't return the rest because you've requested two segments. if it's only a few hours old, then it will only return "2 hours and 1 minute ago". Of course, same rules apply if you specify 1, 2, 3, 4, 5 or 6 segmets (maxes out at 6 because seconds, minutes, hours, days, months, years only make 6 types). It will also correct grammer issues like "minutes" vs "minute" depending on if it's 1 minute or more, same for all types, and the "string" generated will always be grammatically correct.

Here are some examples for use: 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" (won't include hours, minutes and seconds as the top 3 time categories are returned), if however, the date was a newer date, such as something a few days ago, specifying the same segments (3) will return "4 days, 1 hour and 13 minutes ago" instead, so it takes everything into account!

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", but, be reminded that it will NEVER RETURN something like this "0 years, 0 months, 0 days, 3 hours, 2 minutes and 13 seconds ago" as it understands there is no date data in the top 3 segments and ignores them, even if you specify 6 segments, so don't worry :). Of course, if there is a segment with 0 in it, it will take that into account when forming the string, and will display as "3 days and 4 seconds ago" and ignoring the "0 hours" part! Enjoy and please comment if you like.

 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

Of course, you will need a "ReplaceLast" function, which takes a source string, and an argument specifying what needs to be replaced, and another arg specifying what you want to replace it with, and it only replaces the last occurance of that string... i've included my one if you don't have one or dont want to implement it, so here it is, it will work "as is" with no modification needed. I know the reverseit function is no longer needed (exists in .net) but the ReplaceLast and the ReverseIt func are carried over from the pre-.net days, so please excuse how dated it may look (still works 100% tho, been using em for over ten years, can guarante they are bug free)... :). cheers.

<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 
Pathogenic answered 15/2, 2012 at 5:16 Comment(0)
V
0

If you want the exact number, you can't from just the Timespan, since you need to know which months you're dealing, and whether you're dealing with a leap year, like you said.

Either go for an approximate number, or do some fidgetting with the original DateTimes

Valerlan answered 6/10, 2009 at 14:44 Comment(0)
H
0

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

If you can get the time converted from a Gregorian Date into Julian day number, you can just create an operator to do comparisons of the zulian day number, which can be type double to get months, days, seconds, etc. Check out the above link for an algorithm for converting from Gregorian to Julian.

Hadj answered 6/10, 2009 at 15:3 Comment(0)
M
0

There is no built in way to do this accurately in idiomatic-c#. There are some workarounds, such as this CodeProject example that people have coded though.

Multiplication answered 17/3, 2011 at 12:37 Comment(0)
M
0

If you're dealing with months and years you need something that knows how many days each month has and which years are leap years.

Enter the Gregorian Calendar (and other culture-specific Calendar implementations).

While Calendar doesn't provide methods to directly calculate the difference between two points in time, it does have methods such as

DateTime AddWeeks(DateTime time, int weeks)
DateTime AddMonths(DateTime time, int months)
DateTime AddYears(DateTime time, int years)
Malkamalkah answered 4/6, 2011 at 8:26 Comment(0)
N
0
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;
Norbert answered 25/7, 2013 at 9:24 Comment(0)
S
0

The method returns a list that contains 3 element first is year, second is month and end element is day:

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;
        }
    }
Stiffen answered 1/6, 2015 at 10:35 Comment(0)
B
0

Here is my contribution to get difference in Months that I've found to be accurate:

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

Usage:

Int32 months = DateTime.Now.DiffMonths( DateTime.Now.AddYears( 5 ) );

You can create another method called DiffYears and apply exactly the same logic as above and AddYears instead of AddMonths in the while loop.

Burnley answered 29/8, 2016 at 9:39 Comment(0)
F
0

Way late to the game but I imagine this may be helpful to someone. The majority of people tend to measure month to month by date excluding the fact that months come in different variations. Using that frame of thought I created a one liner which compares the dates for us. Using the the following process.

  1. Any # of years over 1 when comparing year will be multiplied by 12, there is no case where this can be equal to less than 1 full year.
  2. If the end year is greater we need to evaluate if the current day is greater or equal to the previous day 2A. If the end day is greater or equal we take the current month and then add 12 months subtract the month of the start month 2B. If the end day is less than the start day we perform the the same as above except we add 1 to the start month before subtracting
  3. If the end year is not greater we perform the same as 2A/2B, but without adding the 12 months because we do not need to evaluate around the year.

        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)));
    
Fiver answered 8/6, 2017 at 10:49 Comment(1)
Death by ternary?Parthenogenesis
S
0

My take on this answer also uses an extension method, but it can return a positive or negative result.

public static int MonthsBefore(this DateTime dt1, DateTime dt2)
{
    (DateTime early, DateTime late, bool dt2After) = dt2 > dt1 ? (dt1,dt2,true) : (dt2,dt1,false);
    DateTime tmp; // Save the result so we don't repeat work
    int months = 1;
    while ((tmp = early.AddMonths(1)) <= late)
    {
        early = tmp;
        months++;
    }
    return (months-1)*(dt2After ? 1 : -1);
}

A couple tests:

// Just under 1 month's diff
Assert.AreEqual(0, new DateTime(2014, 1, 1).MonthsBefore(new DateTime(2014, 1, 31)));
// Just over 1 month's diff
Assert.AreEqual(1, new DateTime(2014, 1, 1).MonthsBefore(new DateTime(2014, 2, 2)));    
// Past date returns NEGATIVE
Assert.AreEqual(-6, new DateTime(2012, 1, 1).MonthsBefore(new DateTime(2011, 6, 10)));
Stlaurent answered 26/10, 2019 at 20:54 Comment(0)
A
0

Combing two of the answers above, another extension method is:

public static int ElapsedMonths(this DateTime date1, DateTime date2)
{
    DateTime earlierDate = (date1 > date2) ? date2 : date1;
    DateTime laterDate = (date1 > date2) ? date1 : date2;
    var eMonths = (laterDate.Month - earlierDate.Month) + 12 * (laterDate.Year - earlierDate.Year) - 
                                            ((earlierDate.Day > laterDate.Day) ? 1 : 0);
    return eMonths;
}

Thanks to @AdamRobinson and @MarkWhittaker

Allround answered 4/12, 2019 at 12:17 Comment(0)
V
0

Here's how we did it:

int DifferenceInMonth(DateTime startDate, DateTime endDate) {
    long ToTicks(DateTime date) => new DateTimeOffset(date).Ticks;
    var daysPerMonth = 30.4;
    return (int)Math.Round((ToTicks(endDate) - ToTicks(startDate)) / TimeSpan.TicksPerDay / daysPerMonth);
}
Valiant answered 26/10, 2020 at 21:6 Comment(0)
A
-1

The accepted answer is strongly incorrect:

For these dates: ldate = 2020-08-30 and rdate = 2020-08-01, we have one month, but the accepted answer returns 0.

For these dates: ldate = 2020-08-30 and rdate = 2020-10-01, we have three months, but the accepted answer returns -2.

Here is the correct method (maybe not the unique method, but correct) to calculate the quantity of months between two dates:

  • You don't have to check which date is lower than other.

  • An interval from the first day to the last day of month is counted as a month.

      public static int GetMontsBetween(DateTime date1, DateTime date2)
      {
          int monthCount = 0;
          int direction = date1 < date2 ? 1 : -1;
          date2 = date2.AddDays(direction);
    
          while (date1.Year != date2.Year || date1.Month != date2.Month)
          {
              date1 = date1.AddMonths(direction);
              monthCount++;
          }
    
          return monthCount;
      } 
    
Acculturize answered 27/2, 2021 at 16:34 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.