How can i loop through a daterange with different intervals?
Asked Answered
C

6

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.

Cadmium answered 12/5, 2012 at 13:6 Comment(0)
O
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 
Ozalid answered 12/5, 2012 at 13:18 Comment(3)
[link]#501753 Xenofex answer more friendly in useCockalorum
@Cockalorum That's the same thing, wrapped in a method.Ozalid
if meet the end of month like 2014-06-30 , it is unable to plus day to July 1st , is there any solution?Biddable
E
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'
Expansible answered 2/7, 2012 at 2:42 Comment(1)
Nice, I should've found this answer sooner it would've saved me time! Wrote a very similar implementation for adding ActiveSupport::Durations to Range#step https://mcmap.net/q/452295/-ruby-create-range-of-dates/…Chromatin
R
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.

Riddance answered 22/3, 2013 at 10:18 Comment(0)
R
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 }
Respectable answered 13/11, 2023 at 13:19 Comment(0)
D
0

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

Doctor answered 12/5, 2012 at 13:24 Comment(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.Ozalid
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}Doctor
I think you can iterate from time but it will be very slow as it will create an item in the range for each secondDoctor
Not in 1.9 you can't, AFAICT, ultrahigh I didn't do it by a plain int.Ozalid
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...Doctor
W
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 }
Waistband answered 27/1, 2023 at 10:51 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.