Edited at

その月のカレンダーっぽいハッシュを返すコード(Ruby)

More than 1 year has passed since last update.


きっかけ

カレンダーのライブラリはいっぱい凄いがのあるけどカスタマイズしづらい....。 JSじゃなくてサーバーサイドである程度つくってしまえばカスタマイズしやすいのだろうか....?


実装方針(アルゴリズム的なもの)


  1. ある日付aを受け取る

  2. ある日付aが属する月の一日, 末日を取得する

  3. 初日の曜日(x)を取得する

  4. 月曜日(cwday=1)~からx曜日まで繰り返し dayが空のhashを入れる(hash①)

  5. 一日から末日までの hashの配列をつくる。dayには値を入れる(hash②)

  6. 末日から日曜日(cwday=7)まで繰り返し, dayが空のhashを入れる(hash③)

  7. hash①②③を結合して、each_slice(7)をし、週単位の配列をつくる。


サンプルコード


calendar.rb

require "date"

class Calendar
def initialize(date)
@datetime = date
assign_first_date_and_last_date_of_the_month
end

attr_reader :datetime, :first_date, :last_date

def generate
calendar = []

# 月曜から一日までのカレンダー紙面上における空白の日付を生成
(first_date.cwday - 1).times do
calendar << { date: "" }
end

# 一日から末日までの日付を作成。
1.upto(last_date.day) do |n|
calendar << { date: n }
end

# 末日から日曜までのカレンダー紙面上における空白の日付を生成
(7 - last_date.cwday).times do |n|
calendar << { date: "" }
end

calendar.each_slice(7).to_a
end

private
def assign_first_date_and_last_date_of_the_month
year, month = datetime.year, datetime.month
@first_date = Date.new(year, month, 1)
@last_date = Date.new(year, month, -1)
end
end



実行結果

こういう値を返します

- 週ごとにhashでまとまってる

- 空白のhashをつくってカレンダー上の空白を表現(つまりフロントでは

とかをeachで描画するだけでOK!)

# 2019年2月のカレンダー

[{:date=>""}, {:date=>""}, {:date=>""}, {:date=>""}, {:date=>1}, {:date=>2}, {:date=>3}]
[{:date=>4}, {:date=>5}, {:date=>6}, {:date=>7}, {:date=>8}, {:date=>9}, {:date=>10}]
[{:date=>11}, {:date=>12}, {:date=>13}, {:date=>14}, {:date=>15}, {:date=>16}, {:date=>17}]
[{:date=>18}, {:date=>19}, {:date=>20}, {:date=>21}, {:date=>22}, {:date=>23}, {:date=>24}]
[{:date=>25}, {:date=>26}, {:date=>27}, {:date=>28}, {:date=>""}, {:date=>""}, {:date=>""}]


もっとクールに書く

@QUANON さん にもっとクールなコードを書いていただいたので転載。


samp.rb

require 'date'

class Calendar
attr_reader :date

def initialize(date)
@date = date
end

def to_a
[
*Array.new(first_date.wday),
*1..last_date.day,
*Array.new(6 - last_date.wday)
]
.map { |date| { date: date || '' } }
.each_slice(7)
.to_a
end

private

def first_date
Date.new(date.year, date.month, 1)
end

def last_date
Date.new(date.year, date.month, -1)
end
end



まとめ

これでサーバー側の値もいれることができそう...な気がする。

あとはJSONにして、ReactなりVueなりでよしなにやれば....勝てる...!


懸念

実サービスで活用するコードに変化させた時(例えば何かをDBからfetchしてくる)に

果たしてさくさく動いてくれるのかまだ検証しておらず。

単体でbenchmark取った結果は大して重くならなそうでしたが。


参考文献


Enumerable#each_slice

https://qiita.com/QUANON/items/749f4a2a79dafdaff57f


Time#days_in_month

https://apidock.com/rails/ActiveSupport/CoreExtensions/Time/Calculations/ClassMethods/days_in_month


active_support

https://qiita.com/seri_k/items/4818af527bd0e94cc860