11

Objective

I am trying to calculate the distance in weeks since a given date without jumping through hoops. I'd prefer to do it in plain Ruby, but ActiveSupport is certainly an acceptable alternative.

My Code

I wrote the following, which seems to work but looks like the long way around to me.

require 'date'

DAYS_IN_WEEK = 7.0 

def weeks_since date_string
  date  = Date.parse date_string
  days  = Date.today - date
  weeks = days / DAYS_IN_WEEK
  weeks.round 2
end

weeks_since '2015-06-15'
#=> 32.57

ActiveSupport's #weeks_since takes a number of weeks as its argument, so it doesn't fit this use case. Ruby's Date class doesn't seem to have anything relevant, either.

Alternatives?

Is there a better built-in solution or well-known algorithm for calculating the number of weeks separating a pair of dates? I'm not trying to code-golf this, as readability trumps brevity, but simply to learn whether Ruby natively supports the type of date arithmetic I've coded by hand.

Todd A. Jacobs
  • 81,402
  • 15
  • 141
  • 199
  • 2
    Weeks do not start on the same day across the world. Which definition are you taking? Or, by week, do you actually mean days/7? For example based on Monday-starting week, Jan 30, 2016 and Jan 31, 2016 belong to the same week, but based on Sunday-starting week, they belong to different weeks. – sawa Jan 29 '16 at 17:29
  • Using times it would be `(t2 - t1) / 604800`. Which is `(60 * 60 * 24 * 7)` = 1 week in seconds. otherwise your solution will also do a similar thing. – engineersmnky Jan 29 '16 at 17:50
  • 2
    @sawa I think he is defining week in a much purer sense of 7 contiguous days. – engineersmnky Jan 29 '16 at 17:52
  • @engineersmnky You're correct. I'm defining weeks as blocks of 7 contiguous days, rather than "work weeks" or "calendar weeks." – Todd A. Jacobs Jan 29 '16 at 17:57
  • You could say, "distance in meters" or "time in weeks", but not "distance in weeks". :-) – Cary Swoveland Jan 29 '16 at 18:34
  • if you just mean 7-day increments, as the code you've got does, I don't see anything wrong with the code you've got or any reason to improve it. – jrochkind Jan 29 '16 at 18:56

3 Answers3

15
require 'date'

str = '2015-06-15'

Date.parse(str).step(Date.today, 7).count                  # => 33
Date.parse(str).upto(Date.today).count.fdiv(7).round(2)    # => 32.71
sbs
  • 4,102
  • 5
  • 40
  • 54
  • Okay, I see [Date#step](http://ruby-doc.org/stdlib-2.3.0/libdoc/date/rdoc/Date.html#method-i-step) defined, but even after reading the docs, I'm not sure why your example works. – Todd A. Jacobs Jan 29 '16 at 22:13
  • `Date#step` accepts an other `Date` object, and a number. It will return an `Enumerator` object, consist of the days difference between those two dates. `Date#upto(some_date)` is equivalent as `Date#step(some_date, 1)`. – sbs Jan 30 '16 at 01:17
  • It is helpful to point out that the second argument pertains to the _number of days per step_. This is not well documented [as of Ruby 2.4.1](https://ruby-doc.org/stdlib-2.4.1/libdoc/date/rdoc/Date.html#method-i-step). – Todd Aug 27 '17 at 20:03
  • 1
    To be more precise, the first one should be `Date.parse(str).step(Date.today, 7).count - 1` so that `(Date.current - 7.days).step(Date.current, 7).count - 1 == 1` – estani Jun 08 '21 at 16:54
8

Might be easier to convert the dates to time and then divide the time difference by a week. You can round it however you want or ceil.

def weeks_since(date_string)
  time_in_past = Date.parse(date_string).to_time
  now = Time.now
  time_difference = now - time_in_past
  (time_difference / 1.week).round(2)
end
Marcus Hoile
  • 201
  • 2
  • 4
7

in_weeks (Rails 6.1+)

Rails 6.1 introduces new ActiveSupport::Duration conversion methods like in_seconds, in_minutes, in_hours, in_days, in_weeks, in_months, and in_years.

As a result, now, your problem can be solved as:

date_1 = Time.parse('2020-10-18 00:00:00 UTC')
date_2 = Time.parse('2020-08-13 03:35:38 UTC') 

(date_2 - date_1).seconds.in_weeks.to_i.abs
# => 9

Here is a link to the corresponding PR.

Marian13
  • 7,740
  • 2
  • 47
  • 51