Find number of months between two Dates in Ruby on Rails
Asked Answered
R

14

112

I have two Ruby on Rails DateTime objects. How to find the number of months between them? (Keeping in mind they might belong to different years)

Rutaceous answered 24/2, 2012 at 9:48 Comment(5)
Was questioned and answered here: #5888256Hundredpercenter
thanks for pointing it out. I searched but had not stumbled upon it.Rutaceous
refer this link #1065820Molarity
I rather want the link in numbers. I got it done by the method referred by Massimiliano PelusoRutaceous
Just i gave it for your reference.. Thanks Anyway..Molarity
F
201
(date2.year * 12 + date2.month) - (date1.year * 12 + date1.month)

more info at http://www.ruby-forum.com/topic/72120

Freightage answered 24/2, 2012 at 9:53 Comment(7)
This solution does not consider days. The number of months between 28/4/2000 and 1/5/2000 is not 1 month, but 0.Tabloid
I need the one without considering days so this works for me. +1Stapes
Just another variant for this great answer: (12 * (date2.year - date1.year) + date2.month - date1.month).abs if date1 && date2Salep
Its showing incorrect # of months if we try to fetch months between 2 years i.e. 2017-05-20 to 2018-05-20. The output shows 1 month.Crimpy
@CuriousDeveloper It is showing correct months i.e 12Housebound
I have only one problem with this method, it completely ignores the day of the month, for example how many months are there between Feb 1st and March 30th of the same year? This would respond with 1, while it is almost 2 months.Rosauraroscius
Note: Add 1 (+ 1) to this result if you're looking for "The number of calendar months that two dates span".Asymptomatic
T
48

A more accurate answer would consider days in the distance.

For example, if you consider that the month-distance from 28/4/2000 and 1/5/2000 is 0 rather than 1, then you can use:

(date2.year - date1.year) * 12 + date2.month - date1.month - (date2.day >= date1.day ? 0 : 1)
Tabloid answered 19/8, 2013 at 18:47 Comment(1)
e.g. for calculating the number of times someone has received his salary always being at the 22nd of the monthsSynchroflash
C
15

Give a try to

((date2.to_time - date1.to_time)/1.month.second).to_i
Columelliform answered 21/12, 2014 at 13:20 Comment(9)
irb>Time.at("2014-10-01".to_time - "2014-12-01".to_time).month => 10 (I give up on formatting... can't figure it out)Lucchesi
Even if you use the Date object, @toby-joiner 's comment will still be correct. The reason is that <October> - <December> is -2 months, but Time.at(-2months).month gives the month equivalent of -2 (i.e. -2 % 12 #=> 10). Your suggested method will work if the two months follow one another and are less than a year apart, but it will fail if the two months are in reverse order (e.g. October - December) or if the two months are more than a year apart.Lunate
Time differences gives you a timestamp correspondingColumelliform
if you use date1 = '2016-02-01'.to_date and date2 = '2016-03-01'.to_date the number of months is 0. :(Huck
Using round(1).to_i is more accurate.Huck
I like the idea behind it, but it is incorrect if the datespan is becoming really long. In my example of Date.new(2014, 10, 31), Date.new(1997, 4, 30) I received 213 instead of 210 months. The reason for that is that 1.month.seconds is the number of seconds in 30 days and not the average seconds in a month. Downvoting so that others stay bug-free.Newfashioned
this wont work if you choose a month that isn't 30 daysAblate
this method is not very accurate .Selfdeceit
How has this gotten so many upvotes? It would be merely happenstance for it to produce an accurate number.Catchword
R
15

Assuming both are dates: ((date2 - date1).to_f / 365 * 12).round simple.

Rosauraroscius answered 1/5, 2018 at 16:58 Comment(3)
Very nice solution! Clean, simple, and even works across years – i.e. the range of months between February 2018 and December 2017.Catchword
Needs to take hours, minutes, and seconds into account ((date2 - date1).to_f / 60 / 60 / 24 / 365 * 12).roundEmalee
@Emalee The number you get from your calculation is way off. All we are doing is taking days and converting them to months, it is not super accurate because it assumes 30.4167 days per month, but it very close and then it rounds anyways.Rosauraroscius
R
11

You can rephrase the question as "how many 1st days is there between the beginnings of months of the dates", and then use functional-style data transformations:

(date1.beginning_of_month...date2.beginning_of_month).select { |date| date.day == 1 }.size
Rihana answered 28/9, 2015 at 13:24 Comment(1)
And to get back to the original question, you could sum the days (like you are doing with the first) and take the average of the sums.Newfashioned
J
4
start_date = Date.today
end_date   = Date.today+90
months = (end_date.month+end_date.year*12) - (start_date.month+start_date.year*12)

//months = 3
Jailhouse answered 22/12, 2016 at 8:42 Comment(0)
K
3

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
Kapor answered 26/9, 2017 at 14:8 Comment(1)
btw it relies on Rails' ActiveSupportKapor
D
2

Another solution that I found (built off a solution posted here already) is for if you want the result to include fractions of a month. For example the distance is 1.2 months.

((date2.to_time - date1.to_time)/1.month.second).round(1) #Tenth of a month Ex: 1.2
((date2.to_time - date1.to_time)/1.month.second).round(2) #Hundreth, ex: 1.23 months
etc...
Disruptive answered 11/8, 2015 at 17:3 Comment(1)
try raplacing round with floor if you whish to only show the months actually lapsedSynchroflash
G
2
def difference_in_months(date1, date2)
  month_count = (date2.year == date1.year) ? (date2.month - date1.month) : (12 - date1.month + date2.month)
  month_count = (date2.year == date1.year) ? (month_count + 1) : (((date2.year - date1.year - 1 ) * 12) + (month_count + 1))
  month_count
end
Goral answered 5/4, 2017 at 9:12 Comment(1)
It would be useful if you could elaborate on this? Generally on SO answers include an explanation on what the code does and why it works as a solutionArmour
T
2

Here's a brute forcing variant:

date1 = '2016-01-05'.to_date
date2 = '2017-02-27'.to_date
months = 0

months += 1 while (date2 << (count+1)) >= date1
puts months # => 13

date2 must be always greater than date1

Trucking answered 4/7, 2018 at 21:20 Comment(0)
H
2

Solution for any cases

(date1..date2).map { |date| date.strftime('%m.%Y') }.uniq.size
Heraclitus answered 31/8, 2020 at 15:54 Comment(0)
A
1

How about this? I've found it pretty clean

((date2 - date1) / 1.month).round
Abey answered 14/12, 2020 at 17:38 Comment(0)
B
0

Here is another method. This will help to calculate number of whole months between two dates

def months_difference(date_time_start, date_time_end)
  curr_months = 0
  while (date_time_start + curr_months.months) < date_time_end
    curr_months += 1
  end
  curr_months -= 1 if (date_time_start + curr_months.months) > date_time_end
  curr_months.negative? ? 0 : curr_months
end
Blunt answered 24/7, 2018 at 19:9 Comment(0)
C
0

If you want the real months,then you must consider the days, the next code take in count this.

# get the real years between date, it consider the months and days
def years_between_dates(since_date, until_date)
  years = until_date.year - since_date.year
  if (until_date.month < since_date.month) ||
     (until_date.month == since_date.month && since_date.day > until_date.day)
    years -= 1
  end
  years
end

# Get the months between dates, it consider the days
def difference_between_dates_in_months(since_date, until_date)
  months = (years_between_dates(since_date, until_date) * 12)
  until_month = until_date.month
  since_month = since_date.month

  if until_month > since_month
    months += since_month - until_month
  elsif until_month < since_month
    months += 12 - since_month + until_month
  end

  months -= 1 if(since_date.day > until_date.day)

  months
end
Cohe answered 5/11, 2021 at 15:58 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.