通常の日付のループ処理
RubyのDateクラスは先頭の日付と末尾の日付を繰り返し処理出来る仕組みがあります。
head = Date.new(2016, 1)
tail = Date.new(2016, 12)
# 1日ずつループ
(head..tail).each { |date| p date.strftime('%Y%m%d') }
# => '20160101', '20160102', '20160103', ...
# n日の間隔でループ
head.step(tail, 5).each { |date| p date.strftime('%Y%m%d') }
# => '20160101', '20160106', '20160111', ...
これはこれで、とても便利な機能なのですが、ループのstepの幅は日単位でしか指定することが出来ません。
年・月・週単位でループさせる
Enumerator
インスタンスを生成することで、任意の間隔でループさせる処理を書くことが出来るようになります。
e = Enumerator.new do |yielder|
head = Date.new 2016, 1
tail = Date.new 2016, 12
while head <= tail
yielder << head
head = head.next_month # ここをhead.next_weekやhead.next_yearに変えることで年毎や週毎のループに出来る
end
end
e.each { |date| p date.strftime('%Y%m%d') }
# => '20160101', '20160201', '20160301', ...
ほんの少し解説
yielder
に対して<< arg
という形でメソッドを呼び出すとarg
がeach
のブロック引数に渡されます。
上記のサンプルコードでは、yielder << head
となっているので、head
(whileループの中でカウントアップされている日付)がeach
のブロック引数に渡されていることになります。
便利に使う
下記のようなrefinementを利用して、Dateクラスを拡張すると便利です。
(active_support
を使うこと前提のコードです)
require 'date'
require 'active_support/all'
module DateExtender
refine Date do
def step_by_unit(tail, unit = :day, step = 1)
head = self.dup
Enumerator.new do |yielder|
while head <= tail
yielder << head
head += step.send(unit)
end
end
end
end
end
using DateExtender
# week by
head = Date.new(2016, 1)
tail = Date.new(2016, 1).end_of_month
head.step_by_unit(tail, :week).each { |date| p date.strftime('%Y%m%d') }
# => '20160101', '20160108', '20160115', ...
# month by
head = Date.new(2016, 1)
tail = Date.new(2016, 4)
head.step_by_unit(tail, :month).each { |date| p date.strftime('%Y%m%d') }
# => '20160101', '20160201', '20160301', ...
# year by
head = Date.new(2016)
tail = Date.new(2020)
head.step_by_unit(tail, :year).each { |date| p date.strftime('%Y%m%d') }
# => '20160101', '20170101', '20180101', ...