10

I have a daterange (from, to) that i want loop through an different intervals (daily, weekly, monthly, ...)

How can i loop through this dateranges?

Update

Thanks for your answers, i came up with the following:

interval = 'week' # month, year
start = from
while start < to
  stop  = start.send("end_of_#{interval}")
  if stop > to
    stop = to
  end
  logger.debug "Interval from #{start.inspect} to #{stop.inspect}"
  start = stop.send("beginning_of_#{interval}")
  start += 1.send(interval)
end

This will loop through a date range with intervals week, month or year and respects the beginning and end of the given interval.

Since i did not mention this in my question i choosed the answer that pushed me into the right direction.

6 Answers 6

7

Loop until the from date plus 1.day, 1.week, or 1.month is greater than the to date?

 > from = Time.now
 => 2012-05-12 09:21:24 -0400 
 > to = Time.now + 1.month + 2.week + 3.day
 => 2012-06-29 09:21:34 -0400 
 > tmp = from
 => 2012-05-12 09:21:24 -0400 
 > begin
?>   tmp += 1.week
?>   puts tmp
?> end while tmp <= to
2012-05-19 09:21:24 -0400
2012-05-26 09:21:24 -0400
2012-06-02 09:21:24 -0400
2012-06-09 09:21:24 -0400
2012-06-16 09:21:24 -0400
2012-06-23 09:21:24 -0400
2012-06-30 09:21:24 -0400
 => nil 
3
  • [link]stackoverflow.com/questions/501253/… Xenofex answer more friendly in use
    – Sector
    Commented May 12, 2012 at 13:54
  • @Sector That's the same thing, wrapped in a method. Commented May 12, 2012 at 14:06
  • if meet the end of month like 2014-06-30 , it is unable to plus day to July 1st , is there any solution?
    – peterlawn
    Commented Jul 4, 2014 at 1:25
7

In Ruby 1.9, I added my own method on Range for stepping through time ranges:

class Range
  def time_step(step, &block)
    return enum_for(:time_step, step) unless block_given?

    start_time, end_time = first, last
    begin
      yield(start_time)
    end while (start_time += step) <= end_time
  end
end

Then, you can call this like, e.g. (My example uses a Rails specific method: 15.minutes):

irb(main):001:0> (1.hour.ago..Time.current).time_step(15.minutes) { |time| puts time }
2012-07-01 21:07:48 -0400
2012-07-01 21:22:48 -0400
2012-07-01 21:37:48 -0400
2012-07-01 21:52:48 -0400
2012-07-01 22:07:48 -0400
=> nil

irb(main):002:0> (1.hour.ago..Time.current).time_step(15.minutes).map { |time| time.to_s(:short) }
=> ["01 Jul 21:10", "01 Jul 21:25", "01 Jul 21:40", "01 Jul 21:55", "01 Jul 22:10"]

Notice that this method uses the Ruby 1.9 convention where enumeration methods return an enumerator if no block is given, which allows you to string enumerators together.

UPDATE

I've added the Range#time_step method to my personal core_extensions "gem". If you'd like to utilize this in your Rails project, just add the following to your Gemfile:

gem 'core_extensions', github: 'pdobb/core_extensions'
1
4

The succ method is deprecated in 1.9 range. Wanting to do the same thing by week, I came to this solution :

  def by_week(start_date, number_of_weeks)
    number_of_weeks.times.inject([]) { |memo, w| memo << start_date + w.weeks }
  end

This return an array of week in the interval. Easily adaptable for months.

1

Adding a very simple solution to a very old question, one can use a simple numeric range to generate dates. For example, to get an array of dates between 2 and 7 days/weeks/months/years ago you can do this:

(2..7).map {|d| d.days.ago }
0

You have the step method on the Range object. http://ruby-doc.org/core-1.9.3/Range.html#method-i-step

5
  • Can you provide an example? You can't iterate from time, so it's not just a matter of stepping via 1.week etc. Commented May 12, 2012 at 13:27
  • You can do some basic stuff here: (Date.current - 5.months .. Date.current).step(7){#code}, (Date.current - 5.months .. Date.current).step(1){#code} Commented May 12, 2012 at 13:53
  • I think you can iterate from time but it will be very slow as it will create an item in the range for each second Commented May 12, 2012 at 13:54
  • Not in 1.9 you can't, AFAICT, ultrahigh I didn't do it by a plain int. Commented May 12, 2012 at 14:08
  • Yup ... from 1.9 you can't do ranges on Time. Range works by calling the method succ on the start value of the range and in 1.9 the succ method on time was deprecated... Commented May 12, 2012 at 19:49
0

Think about timestamps, they are plain integers and you use basic arithmetic on them. My solution is creating the array with the help of timestamps.

start_date = Time.now.to_datetime
end_date = Time.now.to_datetime + 1.year
interval = 10.years.in_days.to_i.days.in_seconds
# To make the first date as start date
epoch = start_date.to_i - interval

(start_date..end_date)
  .select {|time| (time.to_i - epoch) % interval == 0 }
  .each { |time| puts time }

Not the answer you're looking for? Browse other questions tagged or ask your own question.