Find Number of Months Between Two Dates in Ruby on Rails

Find number of months between two Dates in Ruby on Rails

(date2.year * 12 + date2.month) - (date1.year * 12 + date1.month)

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

Finding the months between two dates in rails

I'm not sure what's exactly your desired outcome but, given start date and end date as Date objects, you can perform

(start_date..end_date).to_a.group_by(&:month).values

and at the end what you get is a three element array, and each element contains an array with all the dates in that range for a month

Calculate how many n.months between two dates

Thanks for @Cary's and @Lam's answer.

Here is my answer to find the n month.

# try to find the minimum n month between start_date and target_date
def calc_diff(start_date, target_date)
months_diff = (target_date.year * 12 + target_date.month) - (start_date.year * 12 + start_date.month)

## need to check the end of month because some special case
## start date: 2020-01-31 ; end date 2020-06-30
## the minimum n month must be 5
## another special case of Feb must consider (test case 15)

if start_date.day > target_date.day && !((start_date == start_date.end_of_month || target_date.month == 2) && (target_date == target_date.end_of_month))
months_diff = months_diff - 1
end
puts months_diff # it will show the minimum whole n month

# the target_date will between inside
# (start_date + months_diff.months) <= target_date < (start_date + (months_diff + 1).months)
(start_date + months_diff.months)..(start_date + (months_diff + 1).months)
end

The Test Cases:

## test case 1
## 6/15 - 7/15 => n = 5
calc_diff(Date.parse('2020-01-15'), Date.parse('2020-06-19'))

## test case 2
## 7/15 - 8/15 => n = 6
calc_diff(Date.parse('2020-01-15'), Date.parse('2020-07-15'))

## test case 3
## 5/15 - 6/15 => n = 4
calc_diff(Date.parse('2020-01-15'), Date.parse('2020-06-01'))

## test case 4 (special case)
## 6/30 - 7/31 => n = 5
calc_diff(Date.parse('2020-01-31'), Date.parse('2020-06-30'))

## test case 5
## 7/30 - 8/30 => n = 4
calc_diff(Date.parse('2020-04-30'), Date.parse('2020-07-31'))

## test case 6
## 6/30 - 7/30 => n = 2
calc_diff(Date.parse('2020-04-30'), Date.parse('2020-06-30'))

## test case 7
## 5/31 - 6/30 => n = 4
calc_diff(Date.parse('2020-01-31'), Date.parse('2020-05-31'))

## test case 8
## 2/29 - 3/31 => n = 1
calc_diff(Date.parse('2020-01-31'), Date.parse('2020-02-29'))

## test case 9
## 6/29 - 7/29 => n = 4
calc_diff(Date.parse('2020-02-29'), Date.parse('2020-06-30'))

## test case 10
## 7/29 - 8/29 => n = 5
calc_diff(Date.parse('2020-02-29'), Date.parse('2020-07-31'))

## test case 11
## 1/31 - 2/29 => n = 0
calc_diff(Date.parse('2020-01-31'), Date.parse('2020-02-28'))

## test case 12
## 2/29 - 3/31 => n = 1
calc_diff(Date.parse('2020-01-31'), Date.parse('2020-03-01'))

## test case 13
## 1/17 - 2/17 => n = 0
calc_diff(Date.parse('2020-01-17'), Date.parse('2020-01-17'))

## test case 14
## 1/17 - 2/17 => n = 0
calc_diff(Date.parse('2020-01-17'), Date.parse('2020-01-18'))

## test case 15 (special case)
## 1/30 - 2/29 => n = 1
calc_diff(Date.parse('2019-12-30'), Date.parse('2020-02-28'))

## test case 16
## 2/29 - 3/30 => n = 2
calc_diff(Date.parse('2019-12-30'), Date.parse('2020-02-29'))

Get month names between two dates

I'd go with:

d1 = Date.parse('jan 1 2011')
d2 = Date.parse('dec 31 2012')

(d1..d2).map{ |m| m.strftime('%Y%m') }.uniq.map{ |m| Date::ABBR_MONTHNAMES[ Date.strptime(m, '%Y%m').mon ] }
=> ["Jan",
"Feb",
"Mar",
"Apr",
"May",
"Jun",
"Jul",
"Aug",
"Sep",
"Oct",
"Nov",
"Dec",
"Jan",
"Feb",
"Mar",
"Apr",
"May",
"Jun",
"Jul",
"Aug",
"Sep",
"Oct",
"Nov",
"Dec"]

or:

(d1..d2).map{ |m| m.strftime('%Y%m') }.uniq.map{ |m| Date::ABBR_MONTHNAMES[ m[/\d\d$/ ].to_i ] }

which is probably a little faster.

The problem is the year boundary. You have to track years and months, not just the months, otherwise you'll remove all the duplicated month indexes when using uniq to remove the days. I went with the YYYYMM format, to get the right granularity.


require 'benchmark'
require 'date'

d1 = Date.parse('jan 1 2011')
d2 = Date.parse('dec 31 2012')

n = 100
Benchmark.bm(8) do |x|
x.report('strptime') { n.times { (d1..d2).map{ |m| m.strftime('%Y%m') }.uniq.map{ |m| Date::ABBR_MONTHNAMES[ Date.strptime(m, '%Y%m').mon ] } } }
x.report('regex') { n.times { (d1..d2).map{ |m| m.strftime('%Y%m') }.uniq.map{ |m| Date::ABBR_MONTHNAMES[ m[/\\d\\d$/ ].to_i ] } } }
end

user system total real
strptime 3.060000 0.020000 3.080000 ( 3.076614)
regex 2.820000 0.010000 2.830000 ( 2.829366)

EDIT:

Let's make it even more interesting.

I had some code smell that kept bugging me. I didn't like using Date.strftime and Date.strptime, so I took another run at the problem: Here are two more solutions that are running a lot faster, along with the benchmarks:

require 'benchmark'
require 'date'

def regex_months_between(d1, d2)
d1, d2 = [d1, d2].map{ |d| Date.parse(d) }.minmax

(d1..d2).map{ |m| m.strftime('%Y%m') }.uniq.map{ |m| Date::ABBR_MONTHNAMES[ m[/\d\d$/ ].to_i ] }
end

def months_between1(d1, d2)
d1, d2 = [d1, d2].map{ |d| Date.parse(d) }.minmax

months = (d2.mon - d1.mon) + (d2.year - d1.year) * 12
month_names = []
months.times{ |m|
month_names << Date::ABBR_MONTHNAMES[(d1 >> m).mon]
}
month_names << Date::ABBR_MONTHNAMES[d2.mon]
month_names
end

def months_between2(d1, d2)
d1, d2 = [d1, d2].map{ |d| Date.parse(d) }.minmax

months = (d2.mon - d1.mon) + (d2.year - d1.year) * 12
(d1.mon ... (d1.mon + months)).each_with_object(Date::ABBR_MONTHNAMES[d1.mon, 1]) { |month_offset, month_names_array|
month_names_array << Date::ABBR_MONTHNAMES[(d1 >> month_offset).mon]
}
end

puts regex_months_between('jan 1 2011', 'dec 31 2012').join(', ')
puts months_between1('jan 1 2011', 'dec 31 2012').join(', ')
puts months_between2('jan 1 2011', 'dec 31 2012').join(', ')

n = 100
Benchmark.bm(3) do |b|
b.report('rmb') { n.times { regex_months_between('jan 1 2011', 'dec 31 2012') } }
b.report('mb1') { n.times { months_between1('jan 1 2011', 'dec 31 2012') } }
b.report('mb2') { n.times { months_between2('jan 1 2011', 'dec 31 2012') } }
end

With output looking like:

Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec, Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec
Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec, Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec
Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec, Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec
user system total real
rmb 2.810000 0.010000 2.820000 ( 2.820732)
mb1 0.060000 0.000000 0.060000 ( 0.057763)
mb2 0.060000 0.000000 0.060000 ( 0.057112)

Interesting. "rmb" is now running way behind. Pulling it from the tests and bumping up the loops 100x:

n = 10_000
Benchmark.bm(3) do |b|
b.report('mb1') { n.times { months_between1('jan 1 2011', 'dec 31 2012') } }
b.report('mb2') { n.times { months_between2('jan 1 2011', 'dec 31 2012') } }
end

Which gives:

        user     system      total        real
mb1 5.570000 0.060000 5.630000 ( 5.615789)
mb2 5.570000 0.040000 5.610000 ( 5.611323)

It's basically a tie between the two new ways of getting the months. Being anal, I'd go with mb2 because it'd be a little bit faster if I was doing this millions of times, but your mileage might vary.

Calculate days between two dates [always keeping months of max 30 days]

Similar to @CarySwoveland's answer but uses dot product:

require 'matrix'

def ndays str
Vector[*str.split('/').map(&:to_i)].dot [1,30,360]
end

> ndays('20/12/2020') - ndays('05/04/2020') + 1
=> 256

Add +1 since it seems like you want the number of days, inclusive.

Is it possible to create a list of months between two dates in Rails

Just put what you want inside a range loop and use the Date::MONTHNAMES array like so

(date.year..laterdate.year).each do |y|
mo_start = (date.year == y) ? date.month : 1
mo_end = (laterdate.year == y) ? laterdate.month : 12

(mo_start..mo_end).each do |m|
puts Date::MONTHNAMES[m]
end
end

get no of months, years between two dates in ruby

I'd calculate the difference in months (be aware that we ignore day differences here) and then calculate the number of years by dividing that number by 12:

##
# Calculates the difference in years and month between two dates
# Returns an array [year, month]
def date_diff(date1,date2)
month = (date2.year * 12 + date2.month) - (date1.year * 12 + date1.month)
month.divmod(12)
end

date_diff date1, date4 #=> [0, 4]
date_diff date1, date2 #=> [0, 0]
date_diff date1, date3 #=> [0, 1]
date_diff date1, date5 #=> [1, 3]


Related Topics



Leave a reply



Submit