Rubyの小ネタです。
お題
2020年4月1日から2021年3月1日まで、1年分の「xxxx年xx月1日」のDateオブジェクトを配列として取得するコードを書いてください。
イメージとしてはこんな感じです。
# 見やすいように文字列の配列にしていますが、本当はDateオブジェクトの配列を取得するのがゴールです
[
"2020-04-01",
"2020-05-01",
"2020-06-01",
"2020-07-01",
"2020-08-01",
"2020-09-01",
"2020-10-01",
"2020-11-01",
"2020-12-01",
"2021-01-01",
"2021-02-01",
"2021-03-01"
]
解答例
こんな感じで書けます。
require 'date'
start_date = Date.parse '2020/04/01'
end_date = Date.parse '2021/03/01'
(start_date..end_date).select{|d| d.day == 1}
#=> [#<Date: 2020-04-01 ((2458941j,0s,0n),+0s,2299161j)>, #<Date: 2020-05-01 ((2458971j,0s,0n),+0s,2299161j)>, (中略), #<Date: 2021-03-01 ((2459275j,0s,0n),+0s,2299161j)>]
RailsであればDate.parse
の代わりにto_date
が使えます。
# Railsの場合
start_date = '2020/04/01'.to_date
end_date = '2021/03/01'.to_date
(start_date..end_date).select{|d| d.day == 1}
#=> [Wed, 01 Apr 2020, Fri, 01 May 2020, (中略), Mon, 01 Mar 2021]
むりやり1行で書くとこんな感じ。(可読性がイマイチなのであまりお勧めしませんが)
# Railsの場合
Range.new(*%w[2020/04/01 2021/03/01].map(&:to_date)).select{|d| d.day == 1}
#=> [Wed, 01 Apr 2020, Fri, 01 May 2020, (中略), Mon, 01 Mar 2021]
いったん335日分のDateオブジェクトの配列を作ってから、毎月1日のDateオブジェクトだけをselectするので若干処理効率が悪いところはありますが、何十年、何百年という期間を対象にしないのであれば、たぶん大きな問題にはならないんじゃないかなー、と思っています。
以上、Rubyの小ネタでした!
おまけ
Railsのnext_month
メソッドとRuby 2.7で導入されたEnumerator.produce
を使って、無駄なDateオブジェクトを作らない処理を考えてみました。これもなかなか良さそうです。
# Rails + Ruby 2.7
start_date = '2020/04/01'.to_date
end_date = '2021/03/01'.to_date
Enumerator.produce(start_date, &:next_month).take_while{|d| d <= end_date}
#=> [Wed, 01 Apr 2020, Fri, 01 May 2020, (中略), Mon, 01 Mar 2021]
素のRubyでも>> 1
を使えば1ヶ月後のDateオブジェクトが取得できるので、Ruby 2.7単体でこういう書き方もできます。
# Ruby 2.7
require 'date'
start_date = Date.parse '2020/04/01'
end_date = Date.parse '2021/03/01'
Enumerator.produce(start_date){|d| d >> 1}.take_while{|d| d <= end_date}
#=> [#<Date: 2020-04-01 ((2458941j,0s,0n),+0s,2299161j)>, #<Date: 2020-05-01 ((2458971j,0s,0n),+0s,2299161j)>, (中略), #<Date: 2021-03-01 ((2459275j,0s,0n),+0s,2299161j)>]