11

How do I calculate the difference of two dates in months? Also, incase it makes a difference, I am working with Date objects, not DateTime. Also, some rounding options might be nice so I can control if I want to round up or down on partial months.

Thanks!

P Shved
  • 96,026
  • 17
  • 121
  • 165
brettish
  • 2,628
  • 3
  • 17
  • 22
  • I like this one, but it sounds a bit tedious to code. ._. – omninonsense May 04 '11 at 18:02
  • DateTime and Date are somewhat interchangeable using the `DateTime#to_date` and `Date#to_datetime` methods. – tadman May 04 '11 at 18:07
  • Perhaps, [a similar question about years](http://stackoverflow.com/questions/1904097/how-to-calculate-how-many-years-passed-since-a-given-date-in-ruby) might help you? – P Shved May 04 '11 at 19:09

8 Answers8

11

Subtracting one Date or DateTime from another will yield the number of days as a fraction, but this can be evaluated as a Float or Fixnum as required.

For instance:

(Date.today - Date.today.advance(:months => -3)).to_f
# => 89.0

There were 89.0 days between today and the same calendar date three months ago. If you work this using 30-day months, or 30.4375 as they are on average, you end up with 2.92 months elapsed between then and now, or rounded up to the nearest integer, 3.

If you want to compute the precise number of calendar months, that is trickier, but can be done.

tadman
  • 208,517
  • 23
  • 234
  • 262
  • calendar months would be the preference... but if it is too tricky I will just use the divide by 30 (or 30.44) method. – brettish May 04 '11 at 18:31
  • you know what, after thinking about it a little more and how I would go about attempting to implement calendar months, I decided it just wasn't worth it. I will use your approach. – brettish May 04 '11 at 18:49
5

Something like this is more readable than figuring out seconds, and will give you the actual calendar difference:

# Calculate differnce between two dates in months
# Produces b - a
def month_difference(a, b)
    difference = 0.0
    if a.year != b.year
        difference += 12 * (b.year - a.year)
    end
    difference + b.month - a.month
end

If you need to work out the difference based on days as well, you can just follow the pattern

Glenjamin
  • 7,150
  • 6
  • 25
  • 26
  • Wow, thanks! That is a useful little chunk of code! It looks like you could shorten things by replacing the first 4 lines of the method with `difference = 12 * (b.year - a.year).abs` (with the .abs so it doesn't matter what order the dates are specified in) – brettish May 04 '11 at 20:49
  • @brettish I'd keep the abs on the outside of the function, as otherwise the function will be discarding the direction information. I also think you'd have to do the abs at the end to make sure the month difference added to the year difference is correct when a.year > b.year but a.month < b.month. Your best bet is to write out a bunch of known results as a test suite for the function to make sure it behaves as you'd expect. – Glenjamin May 05 '11 at 08:41
  • 1
    There's a cleaner version of this [here](http://stackoverflow.com/questions/9428605/find-number-of-months-between-two-dates-in-ruby-on-rails): (date2.year * 12 + date2.month) - (date1.year * 12 + date1.month) – Jo P Feb 01 '13 at 17:55
5

We needed something along these lines, but inclusive of partial months. So 1/31 to 2/1 would still yield 2 months. Might help!

def self.month_count(range)
  12 * (range.end.year - range.begin.year) + range.end.month - range.begin.month
end
adamrneary
  • 645
  • 1
  • 7
  • 10
4

This should give an o.k. approximation:

Date1 - Date2 = difference_in_days
(difference_in_days/30).round = difference_in_months
DanS
  • 17,550
  • 9
  • 53
  • 47
1

This answer is late to the party, builds on previous answers, and could probably be written more concisely, however, it does give the calendar difference between two dates taking days into account.

def difference_in_months(start_date, today)
  date_to_months(today) - date_to_months(start_date) + adjustment_for_days(start_date, today)
end

def date_to_months(date)
  date.year * 12 + date.month
end

def adjustment_for_days(earlier_date, later_date)
  if later_date.day == earlier_date.day
    0
  elsif later_date.day > earlier_date.day
    1
  else
   -1
  end
end
Jason
  • 321
  • 3
  • 3
0

how about this practice?

current_date = start_date

while current_date < end_date
  # something  
  current_date = current_date.next_month
end
ajahongir
  • 469
  • 5
  • 11
0

I needed the exact number of months (including decimals) between two dates and wrote the following method for it.

def months_difference(period_start, period_end)
  period_end = period_end + 1.day
  months = (period_end.year - period_start.year) * 12 + period_end.month - period_start.month - (period_end.day >= period_start.day ? 0 : 1)
  remains = period_end - (period_start + months.month)

  (months + remains/period_end.end_of_month.day).to_f.round(2)
end

If comparing let's say September 26th to September 26th (same day) I calculate it as 1 day. If you don't need that you can remove the first line in the method: period_end = period_end + 1.day

It passes the following specs:

expect(months_difference(Date.new(2017, 8, 1), Date.new(2017, 8, 31))).to eq 1.0
expect(months_difference(Date.new(2017, 8, 1), Date.new(2017, 8, 30))).to eq 0.97
expect(months_difference(Date.new(2017, 8, 1), Date.new(2017, 10, 31))).to eq 3.0
# Overlapping february (28 days) still counts Feb as a full month
expect(months_difference(Date.new(2017, 1, 1), Date.new(2017, 3, 31))).to eq 3.0
expect(months_difference(Date.new(2017, 2, 10), Date.new(2017, 3, 9))).to eq 1.0
# Leap year
expect(months_difference(Date.new(2016, 2, 1), Date.new(2016, 2, 29))).to eq 1.0

It relies on Rails' ActiveSupport.

mtrolle
  • 2,234
  • 20
  • 19
0

There is a rails helper for this functionality:

http://api.rubyonrails.org/classes/ActionView/Helpers/DateHelper.html#method-i-distance_of_time_in_words

Kyle Boon
  • 5,213
  • 6
  • 39
  • 50
  • Thanks for the heads up... I am looking for a number so I can do some calculations with it, but this would definitely be useful in other places! – brettish May 04 '11 at 18:35