Ruby でカレンダーを出力してみた

特定の年と月を指定して、カレンダーを出力するプログラムを書いてみました。

標準添付ライブラリの Date クラスを使ってはつまらないので、自力で計算しました。
calender.rb

month_table = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]

#うるう年か?
is_uruu = ->(year) {
  (year % 4 == 0 && year % 100 != 0) || year % 400 == 0
}

#西暦1年1月1日から何日目か(すべてグレゴリオ暦で計算)
days = ->(year, month, day) {
  uruu = ->(y) {
    y / 4 - y / 100 + y / 400
  }
  month_days = ->{
    month_table[0, month].inject(&:+) + (is_uruu.(year) && month > 2 ? 1 : 0)
  }
  y1 = year - 1
  y1 * 365 + uruu.(y1) + month_days.() + day - 1
}

#曜日の計算
week_number = ->(year, month, day) {
  (days.(year, month, day) + 1) % 7
}

#カレンダーの出力
Calender = ->(year, month) {
  gen = ->(from, to) {
    (from..to).map {|i| sprintf("%2d  ", i)}.join
  }
  putout = ->(i) {
    last = month_table[month]
    last += 1 if is_uruu.(year) && month == 2
    while i + 6 <= last
      puts gen.(i, i + 6)
      i += 7
    end
    st = gen.(i, last)
    puts st unless st.empty?
  }
  puts "#{year}/#{month}".center(27)
  puts "sun mon tue wed thu fri sat"
  w = week_number.(year, month, 1)
  puts "    " * w + gen.(1, 7 - w)
  putout.(8 - w)
}

実行例。

$ pry
[1] pry(main)> require "./calender"
=> true
[2] pry(main)> Calender[2018, 3]
          2018/3           
sun mon tue wed thu fri sat
                 1   2   3  
 4   5   6   7   8   9  10  
11  12  13  14  15  16  17  
18  19  20  21  22  23  24  
25  26  27  28  29  30  31  
=> nil
[3] pry(main)> Calender[1989, 1]
          1989/1           
sun mon tue wed thu fri sat
 1   2   3   4   5   6   7  
 8   9  10  11  12  13  14  
15  16  17  18  19  20  21  
22  23  24  25  26  27  28  
29  30  31  
=> nil
[4] pry(main)> Calender[1968, 7]
          1968/7           
sun mon tue wed thu fri sat
     1   2   3   4   5   6  
 7   8   9  10  11  12  13  
14  15  16  17  18  19  20  
21  22  23  24  25  26  27  
28  29  30  31  
=> nil
[5] pry(main)> Calender[2018, 2]
          2018/2           
sun mon tue wed thu fri sat
                 1   2   3  
 4   5   6   7   8   9  10  
11  12  13  14  15  16  17  
18  19  20  21  22  23  24  
25  26  27  28  
=> nil

 
なお、days.() 関数は西暦1年1月1日から何日目かを計算しますが、すべてグレゴリオ暦で計算しています。Ruby の Date クラスでは、それ以前のユリウス暦の期間を補正しているらしいので、こことはちがった値を出力します。なので厳密にいえば、このカレンダーはグレゴリオ暦に切り替わって以降の分しか正しい出力ではないことになります。なお、未来のカレンダーとしては正しいものを出力する筈です。

week_number.(year, month, day) はその日の曜日を出力します。日曜日は 0、月曜日は 1, ..土曜日は 6 という具合です。ソースの最後に

if __FILE__ == $0
  week_table = %W(日 月 火 水 木 金 土)
  puts week_table[week_number.(*ARGV.map(&:to_i))] + "曜日"
end

を加えると、

$ ruby calender.rb 2018 3 27
火曜日

という風にその日の曜日を出力することができます。


こんなのもどうぞ。
obelisk.hatenablog.com