【Ruby】日付・時刻操作まとめ：require "date"とrequire "time"の使い分けと使い方

## はじめに
Rubyで日付や時刻を扱うには、標準ライブラリの Date、DateTime、Time クラスを活用します。これらは require "date" や require "time" で使えるようになります。本記事では、それぞれのクラスが何を扱うか、どのようなメソッドで操作できるかを網羅的に解説します。

## 1. Time クラス（組み込み + require 'time'）
### 1. 何を扱う？
- 日付 + 時刻 + タイムゾーン情報
- 秒・分・時間まで含む「現在時刻」や「特定の時刻」を表す

### 2. 主な使い方
```ruby 
p t = Time.now # 現在の時刻  例: 2025-04-06 21:46:31.430407 +0900
p t - 3600     # １時間前    例: 2025-04-06 20:46:31.430407 +0900
p t + 60       # 60秒後     例: 2025-04-06 21:47:31.430407 +0900

p t.year # 2025
p t.month # 4
p t.day # 6
p t.strftime("%Y-%m-%d") # "2025-04-06"
```

###  3. require 'time' で拡張される機能
- Time.parse(string) で文字列をパースできるようになる。「文字列をパースする」とは、文字列（String）を意味のあるデータ構造に変換することを指します。
- Time.parse は人間がよく使う形式のいろんな書き方に対応しています：


```ruby
require "time"
p Time.parse("2025-04-06 10:00:00")  # => Time オブジェクト 例: 2025-04-06 10:00:00 +0900
p Time.parse("06 Apr 2025 10:00:00") # => Time オブジェクト 例: 2025-04-06 10:00:00 +0900
p Time.parse("2025/04/06 10:00")     # => Time オブジェクト 例: 2025-04-06 10:00:00 +0900

# 特定フォーマットの文字列を Time に変換
p Time.iso8601("2025-04-06T10:00:00+09:00")      # => ISO 8601 形式  2025-04-06 10:00:00 +0900
p Time.rfc2822("Sun, 6 Apr 2025 10:00:00 +0900") # => RFC 2822 形式  2025-04-06 10:00:00 +0900
p Time.httpdate("Sun, 06 Apr 2025 01:00:00 GMT") # => HTTP 日付形式  2025-04-06 01:00:00 UTC

# Time オブジェクトをフォーマットに合わせた文字列に変換
p now = Time.now # => 2025-04-06 21:58:52.383212 +0900
p now.iso8601    # => "2025-04-06T21:58:52+09:00"
p now.rfc2822    # => "Sun, 06 Apr 2025 21:58:52 +0900"
p now.httpdate   # => "Sun, 06 Apr 2025 12:58:52 GMT"
```
**補足1: ISO形式（ISO 8601形式）とは？** 
- API通信（JSONの日付フィールド）や DBとのデータやり取りなどでよく使う。
- 国際標準の「日付と時刻の書き方」
- 基本の形（日時）`YYYY-MM-DDThh:mm:ss±hh:mm
` `2025-04-06T10:00:00+09:00`
- 2025-04-06 → 日付（年-月-日）
- T → 時間の区切り文字（Timeの「T」）
- 10:00:00 → 時刻（時:分:秒）
- +09:00 → タイムゾーン（日本時間はUTC+9）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3490027/cf8f52fb-28e2-4911-9137-24800a87d946.png)

**補足2: UTCとは？** 
- 協定世界時（Coordinated Universal Time）のこと。
- 地球上での「基準となる時間」で、全世界共通の“ゼロ基準の時間”です。 
- 地球基準の時計＝UTC。時間のズレをなくすための世界共通の時間。経度0度（イギリス・グリニッジ）を基準。「GMT（グリニッジ標準時）」に近い（ほぼ同じと考えてOK）
- 日本時間（JST）は UTCより9時間進んでいる ので：
    ```
    UTC:  2025-04-06T01:00:00Z
    JST:  2025-04-06T10:00:00+09:00
    ```
    - Z は「Zuluタイム」の略（＝UTCのこと）
    - +09:00 は UTC からの時差を表してる


## 2. Date クラス（require 'date'）
### 1. 何を扱う?
- **日付のみ**（時刻なし）
- 年・月・日を操作するのに向いている

### 2. 主な使い方
```ruby 
require "date"

# 基本的な生成と取得
p Date.today                 # => 今日の日付 #<Date: 2025-04-06 ((2460772j,0s,0n),+0s,2299161j)>
p Date.new(2025, 4, 1)       # => 2025-04-01 #<Date: 2025-04-01 ((2460767j,0s,0n),+0s,2299161j)>
p Date.parse("2025-04-06")   # => 文字列から変換 #<Date: 2025-04-06 ((2460772j,0s,0n),+0s,2299161j)>

puts "----------------------------"

# 日付の演算
d = Date.new(2025, 4, 6)

p d + 1            # => 翌日（2025-04-07） #<Date: 2025-04-07 ((2460773j,0s,0n),+0s,2299161j)>
p d - 7            # => 1週間前（2025-03-30） #<Date: 2025-03-30 ((2460765j,0s,0n),+0s,2299161j)>
p d.next_day(3)    # => 3日後（2025-04-09） #<Date: 2025-04-09 ((2460775j,0s,0n),+0s,2299161j)>
p d.prev_month     # => 前の月（2025-03-06） #<Date: 2025-03-06 ((2460741j,0s,0n),+0s,2299161j)>
p d.next_month(2)  # => 2ヶ月後（2025-06-06） #<Date: 2025-06-06 ((2460833j,0s,0n),+0s,2299161j)>

puts "----------------------------"

# 曜日チェック
p d.wday        # => 0（日曜日）※0〜6（Sun〜Sat）
p d.sunday?     # => true
p d.monday?     # => false

puts "----------------------------"

# フォーマット変換（文字列
p d.strftime("%Y-%m-%d")      # => "2025-04-06"
p d.strftime("%B %d, %Y")     # => "April 06, 2025"
p d.strftime("%Y年%m月%d日")  # => "2025年04月06日"

puts "----------------------------"

# 文字列からの変換（フォーマット指定あり）
puts Date.strptime("06/04/2025", "%d/%m/%Y") # => 2025-04-06
puts Date.strptime("20250406", "%Y%m%d")     # => 2025-04-06

puts "----------------------------"

# 日付の比較
puts a = Date.new(2025, 4, 6) # 2025-04-06
puts b = Date.new(2025, 4, 1) # 2025-04-01

p a > b   # true
p a == b  # false

puts "----------------------------"

# 範囲を使ったループ
from_date = Date.new(2025, 4, 1)
to_date = Date.new(2025, 4, 5)

(from_date..to_date).each do |date|
  puts date.strftime("%Y-%m-%d")
end

# 出力:
# 2025-04-01
# 2025-04-02
# 2025-04-03
# 2025-04-04
# 2025-04-05

puts "----------------------------"

# 月末・月初

d = Date.new(2025, 4, 6)

puts Date.new(d.year, d.month, 1)    # => 2025-04-01
puts Date.new(d.year, d.month, -1)   # => 2025-04-30
```

## 3. DateTime クラス（require 'date'）
### 1. 何を扱う？
- 日付 + 時刻（でも Time より機能が少ない）
- タイムゾーン操作など一部の場面では Time より使いやすいことも

### 2. 主な使い方
```ruby 
require "date"

p dt = DateTime.now # 現在の時刻 #<DateTime: 2025-04-06T22:28:32+09:00 ((2460772j,48512s,198739000n),+32400s,2299161j)>

puts "----------------------------"

# 時刻の各要素を取得
puts dt = DateTime.new(2025, 4, 6, 15, 30, 45) # => 2025-04-06T15:30:45+00:00

puts dt.year    # => 2025
puts dt.month   # => 4
puts dt.day     # => 6
puts dt.hour    # => 15
puts dt.minute  # => 30
puts dt.second  # => 45

puts "----------------------------"

# 日付や時刻の加減算（演算）
puts dt + 1  # 翌日 => 2025-04-07T15:30:45+00:00
puts dt - 1  # 前日 => 2025-04-05T15:30:45+00:00

puts "----------------------------"

# フォーマット文字列の活用
puts dt.strftime("%Y/%m/%d %H:%M:%S")  # => "2025/04/06 15:30:00"
puts dt.strftime("%A, %d %B %Y")       # => "Sunday, 06 April 2025"
puts dt.strftime("%Y年%-m月%-d日 %H時%M分") # => "2025年4月6日 15時30分"

puts "----------------------------"

# 文字列からの変換
puts DateTime.parse("2025-04-06T15:30:00+09:00")  # 2025-04-06T15:30:00+09:00
puts DateTime.strptime("06/04/2025 15:30", "%d/%m/%Y %H:%M")  # 2025-04-06T15:30:00+00:00

puts "----------------------------"

# タイムゾーンの扱い（オフセット指定）
puts dt = DateTime.new(2025, 4, 6, 15, 30, 0, "+09:00")
puts dt.zone      # => "+09:00"
puts dt.offset    # => 3/8 (9/24)

# UTC変換
puts dt.new_offset(0)  # => UTC時刻（+00:00）に変換 2025-04-06T06:30:00+00:00

puts "----------------------------"

dt1 = DateTime.parse("2025-04-06T10:00:00")
dt2 = DateTime.parse("2025-04-07T10:00:00")

p dt1 < dt2  # => true
p (dt1..dt2).cover?(DateTime.parse("2025-04-06T12:00:00"))  # => true

puts "----------------------------"

# 日付・時刻だけ取り出したいとき

puts dt = DateTime.now # => 2025-04-06T22:37:28+09:00

puts dt.to_date    # => 2025-04-06
puts dt.to_time    # => 2025-04-06 22:37:28 +0900
```

## 4. クラスの使い分けまとめ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3490027/2f1a0bfc-c1e4-43c2-aba2-e99555e477bb.png)

## 5. 実践問題
### Q1. 今日が週の何日目かを数値と文字列で出力せよ
```ruby
# 出力例: 0 (Sunday)
# 出力例: Sunday
```

**回答**
```ruby 
require "date"

today = Date.today
puts today.wday         # => 0〜6（0は日曜）
puts today.strftime("%A") # => "Sunday"など英語の曜日
```

### Q2. 指定された年月に含まれる土日の日数を求めよ
```
# 入力: 2025年4月
# 出力: 土日が何日あるか
```

**回答**
```ruby 
require "date"

def weekend_count(year, month)
  first_day = Date.new(year, month, 1)
  last_day = Date.new(year, month, -1)

  (first_day..last_day).count { |d| d.saturday? || d.sunday? }
end

puts weekend_count(2025, 4)  # => 8
```

### Q3. 2つの日付の間にある平日・休日の数を数えよ
```
# 入力: Date型2つ
# 出力: 平日と休日の数（祝日は無視）
```

**回答**
```ruby 
require "date"

def count_weekdays_and_weekends(from, to)
  weekdays = 0
  weekends = 0

  (from..to).each do |date|
    if date.saturday? || date.sunday?
      weekends += 1
    else
      weekdays += 1
    end
  end

  { weekdays: weekdays, weekends: weekends }
end

start_date = Date.parse("2025-04-01")
end_date = Date.parse("2025-04-10")

result = count_weekdays_and_weekends(start_date, end_date)
puts result
# => {:weekdays=>8, :weekends=>2}
```

### Q4. 日付の配列から、最も未来に近い日を求めよ
```
# 入力: Date配列
# 出力: 今日より未来のうち、最も近い日付
```

**回答**
```ruby 
require "date"

def nearest_future_date(dates)
  today = Date.today
  future_dates = dates.select { |d| d > today }
  future_dates.min
end

dates = [
  Date.new(2025, 4, 10),
  Date.new(2025, 4, 5),
  Date.new(2025, 4, 8)
]

puts nearest_future_date(dates)
# 今日が2025-04-06 なら => 2025-04-08
```

### Q5. 「yyyy-mm-dd」形式の文字列配列から、昇順でソートせよ
```ruby
# 入力: ["2025-04-10", "2023-01-01", "2024-12-31"]
# 出力: ["2023-01-01", "2024-12-31", "2025-04-10"]
```

**回答**
```ruby 
require "date"

def sort_date_strings(array)
  array.map { |s| Date.parse(s) }
      .sort
      .map(&:to_s)
end

dates = ["2025-04-10", "2023-01-01", "2024-12-31"]
puts sort_date_strings(dates)
# => ["2023-01-01", "2024-12-31", "2025-04-10"]
```


## おわりに
Rubyは Time と Date をしっかり使い分けることで、複雑な日付処理も簡潔に書くことができます。標準ライブラリながら強力な機能を活かして、実務でも使えるコードをどんどん書いていきましょう！

また、株式会社シンシアでは、実務未経験のエンジニアの方や学生エンジニアインターンを採用し一緒に働いています。
※ シンシアにおける働き方の様子はこちら

https://www.wantedly.com/companies/xincere-inc/stories

弊社には年間100人程度の実務未経験の方に応募いただき、技術面接を実施しております。
この記事が少しでも学びになったという方は、ぜひ wantedly のストーリーもご覧いただけるととても嬉しいです！