Rubyで過去日なら経過日数、未来なら何日後が表示されるアプリを作ってみた(1)

日付関連のロジックを勉強したかったのでちょっと作ってみました

##仕様要件
・入力した日付の正当性をチェックをする
・過去日なら経過日数を表示する
・未来日なら何日後なのか表示させる

##実際に書いてみた
```ruby:day_count.rb
require "date"

# 日付の正当性確認
def date_valid?(str)
  !! Date.parse(str) rescue false
end

# 今日の日付取得、表示
today = Date.today
weekend = %w(日 月 火 水 木 金 土)
puts "本日は、#{today.year}/#{today.month}/#{today.day}(#{weekend[today.wday]})です"

input_year = nil
puts "\n指定日時を設定します"

# 指定年を入力
loop_flg = 0
while loop_flg == 0 do
  puts "\n指定年を4桁の整数で入れてください。未入力の場合は今年になります"
  # 入力文字列を変数に代入
  input_year = gets.to_i
  if input_year == 0
    # 未入力の場合は、今月を代入してループ解除
    input_year = today.year.to_i
    loop_flg = 1
  elsif (input_year < 1000  || input_year > 9999)
    # 入力値が4桁以外の場合のルート
    puts "入力した値は4桁の整数ではありません"
  else
    # 正しく入力されたのでループ解除
    loop_flg = 1
  end
end

# 指定月を入力
loop_flg = 0
while loop_flg == 0 do
  puts "\n指定月を1～12以下の整数で入れてください。未入力の場合は今月になります"
  # 入力文字列を変数に代入
  input_month = gets.to_i

  if input_month == 0
    # 未入力の場合は、今月を代入してループ解除
    input_month = today.month.to_i
    loop_flg = 1
  elsif  (input_month < 1  || input_month > 12)
    # 入力値が1〜12以外の場合のルート
    puts "入力した値は1から12のいずれかではありません"
  else
    # 正しく入力されたのでループ解除
    loop_flg = 1
  end
end

# 指定日を入力
d_len = nil
loop_flg = 0
while loop_flg == 0 do
  puts "\n指定日を指定年月に合わせた整数で入れてください。"
  # 入力文字列を変数に代入
  input_day = gets.to_i

  if input_day == 0
    # 未入力の場合は、今月を代入してループ解除
    input_day = today.day.to_i

    loop_flg = 1
  else
    # 実在する日付か判定する
    input_date = "#{input_year}/#{input_month}/#{input_day}"
    result = date_valid?(input_date)
    if result == false
      puts "入力した値は日付として正しくありません"
    else
      # 正しく入力されたのでループ解除
      loop_flg = 1
    end
  end
end

# 入力値を日付フォーマットで代入
input_date = Date.new(input_year, input_month, input_day)
puts "\n入力した指定日時は、#{input_date} になります\n"

# 時系列判定
if input_date == today
  puts "今日です\n"
elsif input_date < today
  # 指定した日付からの経過日数
  puts "\n・過去日"
  puts "指定日：#{input_date}"
  sumdate1 = today - input_date
  puts "指定日から今日まで、#{sumdate1.to_i.to_s.gsub(/(\d)(?=\d{3}+$)/, '\\1,')}日経過しています"

  # 10,000日後の日付
  sumday2 = 10000
  sumdate2 = input_date + sumday2
  puts "指定日から#{sumday2.to_i.to_s.gsub(/(\d)(?=\d{3}+$)/, '\\1,')}日後は、#{sumdate2.year}/#{sumdate2.month}/#{sumdate2.day}(#{weekend[sumdate2.wday]})になります"
else
  # 指定した日付までの残日数
  puts "\n・未来日"
  puts "指定日：#{input_date}"
  sumdate3 = input_date - today
  puts "指定日は本日から#{sumdate3.to_i.to_s.gsub(/(\d)(?=\d{3}+$)/, '\\1,')}日後です"
end

exit
```

##ソースコードで使ったもの

###日付(Date)クラス

Dateクラスは組み込みクラスとはなっていない為、Dateクラスを使うにはまずプログラムファイルの中で次の一文を記述する必要がある。

```ruby:ソースコード
require "date"
```

Dateクラスのオブジェクトを作成するには「new」メソッドを使います。
引数は「new」メソッドの最後の引数と同じくグレゴリオ暦をつかい始めた日をあらわすユリウス日を指定します。
引数は順に年、月、日となっています。
月は1から12、日は1から31の範囲で指定します。

```ruby:ソースコード
# 指定した日付を代入する
input_date = Date.new(2019, 11, 5)
```

Dateクラスのオブジェクトを作成するには「today」メソッドを使います。

```ruby:ソースコード
# 今日の日付を代入する
today = Date.today
```

Dateクラスのオブジェクトから年や月など個々の日付要素を取り出すことができ、それぞれ別のメソッドが用意されています。

```ruby:ソースコード
today = Date.today # => 2019/11/5
puts "今年は、#{today.year}年です"
puts "今月は、#{today.month}月です"
puts "今日は、#{today.day}日です"
```

```ruby:出力結果
今年は、2019年です
今月は、11月です
今日は、5日です
```

曜日を取り出す際には`wday`で指定しますが、日本語表記にする際は配列を用います。

```ruby:ソースコード
weekend = %w(日 月 火 水 木 金 土)
puts "本日は、#{weekend[today.wday]}曜日です"
```

```ruby:出力結果
本日は、火曜日です
```

似たようなクラスの使い分けとしては。

| クラス名 | 扱う内容 |特徴 |
|:-----------|:------------|:------------|
| Timeクラス | 日付と時刻 | ・クラスの指定が不要<br />・現在時刻取得：now メソッド |
| Dateクラス | 日付のみ| ・クラスの指定（date）が必要<br />・今日の日付取得：today メソッド<br />・Timeオブジェクトに変換：to_time メソッド |
| DateTimeクラス | 日付と時刻  | ・クラスの指定（date）が必要<br />・Dateクラスのサブクラス<br />・今日の日付取得：now メソッド<br />・Timeオブジェクトに変換：to_time メソッド |

###while文
使い方は前回と同様なので省略。

```ruby:ソースコード
loop_flg = 0
while loop_flg == 0 do
...
end
```

while文の前にループフラグを指定し、正しいルートのみループフラグを解除するようにしている。

###if文
指定年については、未入力が0となり、今年指定月の場合は今月）を代入する。
他に正しい場合が1000〜9999なので（指定月の場合は1〜12）、この中の値かどうか判定させる。

```ruby:ソースコード
#if 条件A or 条件B
#指定年
if (input_year < 1000 || input_year > 9999)
...
end
#指定月
if  (input_month < 1  || input_month > 12)
...
end
```

###日付の正当性確認をメソッド化する
同じ処理を何回もしているわけではないのでメソッド化する意味ないかもなと思いながら作った

```ruby:ソースコード
# 日付の正当性確認
def date_valid?(str)
  !! Date.parse(str) rescue false
end
...
input_date = "#{input_year}/#{input_month}/#{input_day}"
result = date_valid?(input_date) # => true or false
```

ざっくりとした処理の流れ
(1)`input_date`にYYYY/MM/DDの形式（文字列）で代入する
(2)`date_valid?`メソッドを呼び出す。引数は`input_date`
(3)parseメソッドでDateオブジェクト（文字列の引数を日付）に変換する
(4)`!! object`で変換する文字列が正当性を評価する。falseなら`rescue`の処理に従う（※解説は下段）
(5)正当な値ならtrue、そうではない場合はfalseが返ってくる

※メソッドの処理の評価について
`! object`が評価後、objectをtrueかfalseに変換する。
期待通りなら`!`なのでfalseを返すが、`!!`なのでtrueを返す。
期待されていない場合は、`!`ならtrueを返し、`!!`ならfalseを返す。
まとめると、`!! object`が期待通りならtrue、そうではないならfalseを返す。

###数値が3桁以上はカンマ表記にする
`1000`などの値は、`1,000`という表記に変換するようにする
あんまり必要ではないかもしれないが、付加機能としておく
変換するには、gsubメソッドで値を置換することになる

```ruby:ソースコード
i = 10000
puts "#{i.to_i.to_s.gsub(/(\d)(?=\d{3}+$)/, '\\1,')}"
```

```ruby:出力結果
10,000
```

##完成して
もう少しスマートにやっていきたいですね。
ドンドン、コメント募集しています。