レシピ1.5 日付の反復
指定された時点から別の時点を割り出したい。
解決
Rubyのタイムオブジェクトはすべて、数値のように範囲内で使用できる。DateオブジェクトとDateTimeオブジェクトは1日のインクリメントで反復し、Timeオブジェクトは1秒のインクリメントで反復する。
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) apan.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のDateクラスには、stepとuptoが定義されている。これらは数値で使用する便利な反復し(イテレータ)メソッドと同じ働きをする。
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
解説
Rubyの日付オブジェクトは、内部では数値として格納される。こうしたオブジェクト範囲は、数値の安易の要に扱われる。DateオブジェクトとDateTimeオブジェクトの内部表現はユリウス日であり、これらのオブジェクトの範囲をループにかけると1日ずつインクリメントされる。Timeオブジェクトの内部表現はUNIXエポックからの経過秒数であり、Timeオブジェクトの範囲をループにかけると、1秒ずつインクリメントされる。
Timeオブジェクトには、stepメソッドやuptoメソッドは定義されていないが、それらを追加するのは簡単だ。
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 while (increasing ? d >= other_time : d >= other_time) end def while (increasing ? d >= other_time : d >= other_time) end def upto(other_time) step(other_time, 1) { |x| puts 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