【Ruby】カレンダー作成問題を解く

　毎日アルゴリズム問題を解こうと思い、今回はカレンダー作成問題を解いてみました。同じプログラミング初心者向けに、答えを導くまでの細かいプロセスも紹介したいと思います。

[Ruby初心者向けのプログラミング問題を集めてみた（全10問）の問題を解いてみた(前編)](https://qiita.com/sackey_24/items/8fc236bb054aff6b74c8)
[アウトプットのネタに困ったらこれ!?](https://blog.jnito.com/entry/2019/05/03/121235)

　問題に関しては、こちらの記事を参考にしました。自分の勉強のためなので、まずは自分で解答を考えました。ただし、上記の記事に目を通してしまったため、上記の記事の解法に引っ張られた部分もあります。

　問題は、次のような形式でカレンダーを表示させることです。

```erb
     April 2013
Su Mo Tu We Th Fr Sa
    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
```

1. 西暦と月の名称を表示させる
2. 曜日を出力させる
2. 横並びと改行を試してみる
2. 月ごとに最終日を変える
2. 初日の日付をズラす
2. 全体の日付をズラす

　以上の流れで説明していきます！

##1. 西暦と月の名称を表示させる

　ひとまず[DateクラスのAPI](https://docs.ruby-lang.org/ja/latest/class/Date.html)を参考にすれば、西暦と月の名称、曜日までは多少強引なやり方でも表示させることができそうです。


```ruby
require "date"

puts "#{Date.today.strftime('%B')} #{Date.today.strftime('%Y')}"

```
　とりあえずこのようなやり方で、西暦と月の名称を表示させることができました。


```
July 2021
```


##2. 曜日を出力させる
　汎用性がなく、この問題を解くことだけに特化したコードの書き方をするなら、これで曜日の出力が可能でした。

```ruby
require "date"

puts "#{Date.today.strftime('%B')} #{Date.today.strftime('%Y')}"
puts "Su Mo Tu We Th Fr Sa"
```

　出力結果はこの通りです。

```
July2021
Su Mo Tu We Th Fr Sa
```


##3. 横並びと改行を試してみる
　ここからが少し分からないところでした。

- 「どのように日付を横並びに表示させるか」
- 「どのようにして改行させるか」
- 「どのようにして月ごとの最終日を変えるか」
- 「どのようにして曜日に日付を合わせるか」
- 「どのようにして月ごとに日付をズラすことができるか」

　プログラミング初心者にとってはこの辺の課題を一気に整理することはできませんでした。そのため1つ1つ解決していくことにしました。まずは横並びと改行の課題を解消します。

```ruby
require "date"

puts "#{Date.today.strftime('%B')} #{Date.today.strftime('%Y')}"
puts "Su Mo Tu We Th Fr Sa"

count = 1
while count <= 31
  print count
  count += 1
end
```

調べたところ、「puts」ではなく「print」を使うと横並びに表示されるようでした。

```
July 2021
Su Mo Tu We Th Fr Sa
12345678910111213141516171819202122232425262728293031
```

```ruby
require "date"

puts "#{Date.today.strftime('%B')} #{Date.today.strftime('%Y')}"
puts "Su Mo Tu We Th Fr Sa"

count = 1
while count <= 31
  print count
  if count % 7 == 0
    puts "\n"
  end
  count += 1
end
```

　そして「\n」を使えば改行ができます。

```
July 2021
Su Mo Tu We Th Fr Sa
1234567
891011121314
15161718192021
22232425262728
293031
```

　さらに日付が1桁か2桁かによって、日付の間隔を変えるようにすれば、それっぽいカレンダーは完成しました。

```ruby
require "date"

puts "#{Date.today.strftime('%B')} #{Date.today.strftime('%Y')}"
puts "Su Mo Tu We Th Fr Sa"

count = 1
while count <= 31
  if count <= 9
    print " #{count} "
  else
    print "#{count} "
  end

  if count % 7 == 0
    puts "\n"
  end
  count += 1
end
```

```
July 2021
Su Mo Tu We Th Fr Sa
 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
```
　ここまでは、とりあえず横並びと改行の問題の解決方法を知っただけです。実際2021年7月は木曜日から始まりますし、月ごとに最終日を変える必要があります。
　
##4. 月ごとに最終日を変える
　月ごとに最終日を変えるということに関しては、DateクラスのAPIを参考にすればなんとか解消できそうです。

```ruby
require "date"

puts "#{Date.today.strftime('%B')} #{Date.today.strftime('%Y')}"
this_year = Date.today.year
this_month = Date.today.month
last_day = Date.new(this_year, this_month,-1)

puts "Su Mo Tu We Th Fr Sa"
(1..last_day.day).each{ |day|
  if day <= 9
    print " #{day} "
  else
    print "#{day} "
  end

  if day % 7 == 0
    puts "\n"
  end
}
```
　このようにして月ごとに最終日を変えることが可能となりました。


##5. 初日の日付をズラす
　ここからのやり方が1番よくわかりませんでした。瞬時に効率的なやり方は浮かばなかったため、とりあえず強引に解いてみようと思います。

```ruby
require "date"

puts "#{Date.today.strftime('%B')} #{Date.today.strftime('%Y')}"
this_year = Date.today.year
this_month = Date.today.month
first_day = Date.new(this_year, this_month,1)
last_day = Date.new(this_year, this_month,-1)
wday_first = first_day.wday
blank = "   " * wday_first

puts "Su Mo Tu We Th Fr Sa"
print blank
(first_day.day..last_day.day).each{ |day|
  if day <= 9
    print " #{day} "
  else
    print "#{day} "
  end

  if day % 7 == 0
    puts "\n"
  end
}
```

　月の初日である1日が、何曜日であるかによって日付がズレていくため、日曜日であれば「0」、月曜日であれば「1」が出力される「wday」の大きさによって、空欄の大きさを変えることができそうです。

```ruby
#省略
first_day = Date.new(this_year, this_month,1)
#省略
wday_first = first_day.wday
blank = "   " * wday_first
#省略
print blank
#省略
```
　これらの記述によって以下のような出力結果が得られました。

```
July 2021
Su Mo Tu We Th Fr Sa
             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 
```

##6. 全体の日付をズラす
　上記のズレを修正するために、下記の部分のコードを編集しました。

```ruby
  if (wday_first + day) % 7 == 0
    puts "\n"
  end
```
　また西暦と月の名称を中央揃えにしておきました。

```ruby
year_month =  "#{Date.today.strftime('%B')} #{Date.today.strftime('%Y')}"
puts year_month.center(20)
```

　これで、まだ抜け漏れはあるかもしれませんが、求めたい結果を出すことはできました。

```
     July 2021      
Su Mo Tu We Th Fr Sa
             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 
```

　最終的なコードがこちらです。コメントをいただき、桁数に応じて空欄を変える記述をシンプルにしています。

```ruby
require "date"

year_month =  "#{Date.today.strftime('%B')} #{Date.today.strftime('%Y')}"
puts year_month.center(20)
this_year = Date.today.year
this_month = Date.today.month
first_day = Date.new(this_year, this_month,1)
last_day = Date.new(this_year, this_month,-1)
wday_first = first_day.wday
blank = "   " * wday_first

puts "Su Mo Tu We Th Fr Sa"
print blank
(first_day.day..last_day.day).each{ |day|
  printf("%2d ", day)

  if (wday_first + day) % 7 == 0
    puts "\n"
  end
}
```

　以上です！プログラミング初心者で見落としどころもあるかと思いますので、ご指摘いただけるとありがたいです！