Iterating Over Dates

Problem

Given a point in time, you want to get somewhere else.

Solution

All of Ruby's time objects can be used in ranges as though they were numbers. Date and DateTime objects iterate in increments of one day, and Time objects iterate in increments of one second:

```	require 'date'
(Date.new(1776, 7, 2)..Date.new(1776, 7, 4)).each { |x| puts x }
# 1776-07-02
# 1776-07-03
# 1776-07-04

span = DateTime.new(1776, 7, 2, 1, 30, 15)..DateTime.new(1776, 7, 4, 7, 0, 0)
span.each { |x| puts x }
# 1776-07-02T01:30:15Z
# 1776-07-03T01:30:15Z
# 1776-07-04T01:30:15Z

(Time.at(100)..Time.at(102)).each { |x| puts x }
# Wed Dec 31 19:01:40 EST 1969
# Wed Dec 31 19:01:41 EST 1969
# Wed Dec 31 19:01:42 EST 1969
```

Ruby's Date class defines step and upto, the same convenient iterator methods used by numbers:

```	the_first = Date.new(2004, 1, 1)
the_fifth = Date.new(2004, 1, 5)

the_first.upto(the_fifth) { |x| puts x }
# 2004-01-01
# 2004-01-02
# 2004-01-03
# 2004-01-04
# 2004-01-05
```

Discussion

Ruby date objects are stored internally as numbers, and a range of those objects is treated like a range of numbers. For Date and DateTime objects, the internal representation is the Julian day: iterating over a range of those objects adds one day at a time. For Time objects, the internal representation is the number of seconds since the Unix epoch: iterating over a range of Time objects adds one second at a time.

Time doesn't define the step and upto method, but it's simple to add them:

```	class Time
def step(other_time, increment)
raise ArgumentError, "step can't be 0" if increment == 0
increasing = self < other_time
if (increasing && increment < 0) || (!increasing && increment > 0)
yield self
return
end
d = self
begin
yield d
d += increment
end while (increasing ? d <= other_time : d >= other_time)
end

def upto(other_time)
step(other_time, 1) { |x| yield x }
end
end

the_first = Time.local(2004, 1, 1)
the_second = Time.local(2004, 1, 2)
the_first.step(the_second, 60 * 60 * 6) { |x| puts x }
# Thu Jan 01 00:00:00 EST 2004
# Thu Jan 01 06:00:00 EST 2004
# Thu Jan 01 12:00:00 EST 2004
# Thu Jan 01 18:00:00 EST 2004
# Fri Jan 02 00:00:00 EST 2004

the_first.upto(the_first) { |x| puts x }
# Thu Jan 01 00:00:00 EST 2004
```

• Recipe 2.15, "Generating a Sequence of Numbers"

Ruby Cookbook (Cookbooks (OReilly))
ISBN: 0596523696
EAN: 2147483647
Year: N/A
Pages: 399