LoginSignup
1
0

More than 5 years have passed since last update.

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

Last updated at Posted at 2018-01-23

きっかけ

カレンダーのライブラリはいっぱい凄いがのあるけどカスタマイズしづらい....。 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

Time#days_in_month

active_support

1
0
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0