2
votes

I am attempting to calculate the elapsed time in months between two dates in Ruby in tenths of a month. So, for example, the time from January 1, 2015 to February 14, 2015 would be 1.5 months.

I found this:

((date2.to_time - date1.to_time)/1.month.second).round(1)

Which is great but this assumes a 30 day month (1.month==30.days). But obviously not all months are 30 days. I am looking for a formula that would account for the different lengths of months.

2
What are you using that defines 1.month?Reinstate Monica -- notmaynard
What is "time in months between two dates"? How is it different from "time between two dates"?sawa
I don't think that what you're trying to do has a trivial solution.Adrian
By which month do you measure the length of a month? For example, January 1, 2015 to February 14, 2015 spans between January, 2015 (which is 31 days) and February, 2015 (which is 28 days); by which month?sawa
@sawa I'm not OP, but that date range is 100% of the days in january and 50% of the days in february, so I think that's why the answer is 1 + 0.5 = 1.5.Adrian

2 Answers

2
votes

If I understood well, you are looking for something like this:

require 'date'

def last_day_of_month(date)
  (((date - date.day + 1) >> 1) - 1).day
end

def diff_in_months(date1, date2, precision=1)
  if date2 < date1
    return -diff_in_months(date2, date1, precision)
  end

  days_month_1 = last_day_of_month(date1)
  ratio1 = (days_month_1 - date1.day + 1) / days_month_1.to_f

  days_month_2 = last_day_of_month(date2)
  ratio2 = date2.day / days_month_2.to_f

  months_diff = (date2.year - date1.year) * 12 +
                date2.month - date1.month - 1

  (ratio1 + ratio2 + months_diff).round(precision)
end

date1 = Date.new(2015,  1,  1)
date2 = Date.new(2015,  2, 14)
date3 = Date.new(2015,  3, 14)
date4 = Date.new(2015,  4,  1)
date5 = Date.new(2016,  1,  1)
date6 = Date.new(2016,  2, 14)

diff_in_months(date1, date2)     # 1.5
diff_in_months(date2, date1, 2)  # -1.5
diff_in_months(date1, date3, 2)  # 2.45
diff_in_months(date4, date4, 2)  # 0.03 (1 day in 30)
diff_in_months(date5, date6, 2)  # 1.48 (Feb'16 has 29 days)
1
votes

My solution, with some monkey patching of Date class

require 'date'

class Date
  COMMON_YEAR_DAYS_IN_MONTH = [nil, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]

  def days_in_month
   return 29 if month == 2 && Date.gregorian_leap?(year)
   COMMON_YEAR_DAYS_IN_MONTH[month]
  end

  def elapsed_month
    ((day.to_f/days_in_month.to_f))
  end

  def remaining_month
    (1.0 - elapsed_month)
  end

  def diff_percent(d)
    from, to = self > d ? [d, self] : [self, d]
    to.elapsed_month + from.remaining_month + (to.month - from.month) - 1
  end

end

from = Date.new(2015, 1, 1)
to = Date.new(2015, 8, 12)

p to.diff_percent(from).round(1) # Prints 7.4
p from.diff_percent(to).round(1) # Prints 7.4