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)
-
5Was questioned and answered here: http://stackoverflow.com/questions/5887756/rails-calculate-date-range-in-months – Tim Baas Feb 24 '12 at 09:54
-
thanks for pointing it out. I searched but had not stumbled upon it. – phoenixwizard Feb 24 '12 at 09:58
-
refer this link http://stackoverflow.com/questions/1065320/in-rails-display-time-between-two-dates-in-english – sangeethkumar Feb 24 '12 at 10:10
-
I rather want the link in numbers. I got it done by the method referred by Massimiliano Peluso – phoenixwizard Feb 24 '12 at 10:59
-
Just i gave it for your reference.. Thanks Anyway.. – sangeethkumar Feb 24 '12 at 11:16
14 Answers
(date2.year * 12 + date2.month) - (date1.year * 12 + date1.month)
more info at http://www.ruby-forum.com/topic/72120

- 6,626
- 3
- 22
- 21

- 26,379
- 6
- 61
- 70
-
28This solution does not consider days. The number of months between 28/4/2000 and 1/5/2000 is not 1 month, but 0. – dgilperez Aug 19 '13 at 18:39
-
18
-
2Just another variant for this great answer: `(12 * (date2.year - date1.year) + date2.month - date1.month).abs if date1 && date2` – Stepan Zakharov Dec 01 '17 at 12:36
-
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. – Curious Developer Jun 22 '18 at 07:35
-
1
-
-
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. – Andreykul Feb 22 '19 at 17:34
-
Note: Add 1 (`+ 1`) to this result if you're looking for **"The number of calendar months that two dates span"**. – Joshua Pinter Sep 27 '19 at 16:44
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)

- 10,716
- 8
- 68
- 96
-
2e.g. for calculating the number of times someone has received his salary always being at the 22nd of the months – Matthias Jul 22 '16 at 13:31
Assuming both are dates:
((date2 - date1).to_f / 365 * 12).round
simple.

- 303
- 3
- 9
-
1Very nice solution! Clean, simple, and even works across years – i.e. the range of months between February 2018 and December 2017. – jeffdill2 May 03 '18 at 18:57
-
1Needs to take hours, minutes, and seconds into account `((date2 - date1).to_f / 60 / 60 / 24 / 365 * 12).round` – scarver2 Aug 02 '18 at 00:41
-
2@scarver2 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. – Andreykul Aug 02 '18 at 13:21
Give a try to
((date2.to_time - date1.to_time)/1.month.second).to_i

- 1,382
- 1
- 12
- 18
-
`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) – Toby Joiner Dec 23 '14 at 16:11
-
Even if you use the Date object, @toby-joiner 's comment will still be correct. The reason is that
- – elreimundo Jan 09 '15 at 18:37is -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. -
-
if you use `date1 = '2016-02-01'.to_date` and `date2 = '2016-03-01'.to_date` the number of months is `0`. :( – William Weckl Sep 20 '16 at 13:22
-
-
4I 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. – Joe Eifert Sep 26 '16 at 12:43
-
2
-
3
-
How has this gotten so many upvotes? It would be merely happenstance for it to produce an accurate number. – jeffdill2 May 03 '18 at 18:55
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

- 5,845
- 3
- 28
- 39
-
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. – Joe Eifert Sep 26 '16 at 12:47
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

- 5,188
- 4
- 40
- 37

- 101
- 6
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

- 2,234
- 20
- 19
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...

- 2,377
- 4
- 25
- 51
-
1try raplacing round with floor if you whish to only show the months actually lapsed – Matthias Jul 22 '16 at 13:17
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

- 81
- 3
-
1It 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 solution – Single Entity Apr 05 '17 at 09:33
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

- 5,166
- 5
- 29
- 57
Solution for any cases
(date1..date2).map { |date| date.strftime('%m.%Y') }.uniq.size

- 21
- 1
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

- 181
- 1
- 6
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

- 206
- 2
- 8