Help us understand the problem. What is going on with this article?

[Ruby入門] 14. 日付と時刻を扱う(全パターン網羅)

More than 3 years have passed since last update.

>> 連載の目次は こちら!

今回は、Rubyで日付と時刻を扱う方法を整理してみる。
Timeクラス、Dateクラス、DateTimeクラス、
さらに Active Support による拡張機能について整理する。

全パターン盛り込んだら、かなり大きなトピックになってしまった...

■ Time クラス

UNIXタイムで日時を扱うクラス。これだけでもかなり強力

● 現在時刻でTimeオブジェクトを生成

now1 = Time.new; p now1  # 2017-04-25 21:01:27 +0900
now2 = Time.now; p now2  # 2017-04-25 21:01:27 +0900

● 特定の日時でTimeオブジェクトを生成

※日付文字列からTimeに変換する方法については、後述の 文字列からTimeオブジェクトに変換 を参照

# 方法1
t1 = Time.local(2017, 5, 1, 9, 33, 45, 0); p t1
   # 2017-05-01 09:33:45 +0900
   # 形式は Time.local(year, mon = 1, day = 1, hour = 0, min = 0, sec = 0, usec = 0)
   # year以外は省略可能。usecはマイクロ秒。

# 方法2
t2 = Time.local(12, 44, 23, 22, 5, 2017, 0, 0, false, "JST"); p t2
   # 2017-05-22 23:44:12 +0900
   # 形式は Time.local(sec, min, hour, mday, mon, year, wday, yday, isdst, zone)
   # 引数は省略できない! しかも wday, yday, zone は無視されるらしい。
   # 誰が使うのこの形式?

● いろんな切り口でTimeオブジェクトから値を取り出す

t = Time.new
p [
  t.year,  # 2017   年
  t.month, # 4      月。mon でも同じ。
  t.day,   # 25     日。mday でも同じ。
  t.hour,  # 21     時
  t.min,   # 11     分
  t.sec,   # 25     秒
  t.wday,  # 2      曜日番号。0(日曜日) 〜 6(土曜日)
  t.zone   # JST    タイムゾーン
]

上記の他に、夏時間があるか?(isdst) とか 1月1日からの通算日(yday) とかあるけど、あんまり使わないだろうな。

● 曜日を確認する

t = Time.new;
p [
  t.sunday?,
  t.monday?,
  t.tuesday?,
  t.wednesday?,
  t.thursday?,
  t.friday?,
  t.saturday?
]

● 文字列からTimeオブジェクトに変換

require "time"
t = Time.parse("2017/04/25 19:23:55"); p t  # 2017-04-25 19:23:55 +0900
t = Time.parse("2017/4/25 19"); p t         # 2017-04-25 19:00:00 +0900
t = Time.parse("2017/6/1 7:5"); p t         # 2017-06-01 07:05:00 +0900
t = Time.parse("2017-8-1 1:2:3"); p t       # 2017-08-01 01:02:03 +0900
t = Time.parse("20170907 122456"); p t      # 2017-09-07 12:24:56 +0900
t = Time.parse("20170907123456"); p t       # 2017-09-07 12:34:56 +0900
t = Time.parse("2017-9/12 12:34:56"); p t   # 2017-09-12 12:34:56 +0900
Time.parse("xoxoxo$$") rescue p $!         # #<ArgumentError: no time information in "xoxoxo$$">

かなり柔軟にパースしてくれるので便利!

さらに、日本語表記の日付文字列みたいに、変則的な文字列であった場合、strptime を使って、書式を明示してパースすることができます。

@riocampos さんから教えていただきました。ありがとうざいます!
参考: 日本語表記の日時をTimeオブジェクトに変換

require "time"

input_str = "2017年 5月 5日 10:25"
t = Time.strptime(input_str, "%Y年 %m月%d日 %H:%M")
p t  # 2017-05-05 10:25:00 +0900

input_str = "2017年05月05日 10:25"
t = Time.strptime(input_str, "%Y年 %m月%d日 %H:%M")
p t  # 2017-05-05 10:25:00 +0900

「_5月_5日」も「05月05日」も、同じ書式文字列でパースできているところがポイントですね。

● Timeオブジェクトから文字列に変換

t = Time.new
str = t.strftime("%Y-%m-%d %H:%M:%S")
puts str  # 2017-04-25 21:45:06

よく使いそうな書式は以下。

%Y  年4桁
%m  月2桁
%d  日2桁
%H  時2桁
%M  分2桁
%S  秒2桁
%X  時:分:秒 (%H:%M:%S と同じ)
%w  曜日番号。0(日曜日) 〜 6(土曜日)

● UNIXタイムの経過秒数 ⇔ TIMEオブジェクト の相互変換

# UNIXタイムの経過秒数 → TIMEオブジェクトへ
sec = 1493125226
t = Time.at(sec); p t  # 2017-04-25 22:00:26 +0900

# TIMEオブジェクト → UNIXタイムの経過秒数へ
t = Time.new
time = t.to_i; p time  # 1493125226  tv_secメソッドでも同じ

● Timeオブジェクトから配列に変換

t = Time.new
arr = t.to_a
p arr  # [11, 54, 21, 25, 4, 2017, 2, 115, false, "JST"]

返される配列は、
[秒 分 時 日 月 年 曜日 年内通算日 夏時間? タイムゾーン]

● Timeオブジェクトで加減算

t = Time.new; p t              # 2017-04-25 22:24:09 +0900
t2 = t + (60*60*24*3);   p t2  # 2017-04-28 22:24:09 +0900
t3 = t + (60*60*24*365); p t3  # 2018-04-25 22:24:09 +0900

秒で加減算される

● Timeオブジェクトの大小比較

require "time"

t1 = Time.parse("2017/04/25 19:23:55");
t2 = Time.parse("2017/05/25 19:23:55");

p (t2 > t1)  # true

■ Date クラス

  • 日付を扱うクラス
  • 「require "date"」する必要がある
  • Active Support を入れなくてもそこそこ便利なメソッドがある

● Dateオブジェクトの生成

require "date"

# 本日の日付で生成
d1 = Date.today; p d1
   # #<Date: 2017-04-26 ((2457870j,0s,0n),+0s,2299161j)>

# 指定の日付で生成
d2 = Date.new(2017, 4, 30); p d2
   # #<Date: 2017-04-30 ((2457874j,0s,0n),+0s,2299161j)>
  • newの引数は、year以外は省略可
  • todayの第1引数、およびnewの第4引数 というのがあるが、グレゴリオがどーのユリウスがどーのとかファンタジックな引数なので気にしない

● 月末日を取得したい

require "date"

day = Date.new(2020, 2 , -1); p day
   # #<Date: 2020-02-29 ((2458909j,0s,0n),+0s,2299161j)>

newの第3引数に負の値をわたすと末日から数える。-1が末日になる。

● いろんな切り口でDateオブジェクトから値を取り出す

d = Date.today;
p [
  d.year,  # 2017   年
  d.month, # 4      月。mon でも同じ。
  d.day,   # 26     日。mday でも同じ。
  d.wday,  # 3      曜日番号。0(日曜日) 〜 6(土曜日)
]

● 曜日を確認する

d = Date.today;
p [
  d.sunday?,
  d.monday?,
  d.tuesday?,
  d.wednesday?,
  d.thursday?,
  d.friday?,
  d.saturday?
]

● 文字列からDateオブジェクトに変換

require "date"

d = Date.parse("2017/04/25"); p d   # #<Date: 2017-04-25 ((2457869j,0s,0n),+0s,2299161j)>
d = Date.parse("2017/5/5"); p d     # #<Date: 2017-05-05 ((2457879j,0s,0n),+0s,2299161j)>
d = Date.parse("2017-8-1"); p d     # #<Date: 2017-08-01 ((2457967j,0s,0n),+0s,2299161j)>
d = Date.parse("20170907"); p d     # #<Date: 2017-09-07 ((2458004j,0s,0n),+0s,2299161j)>
d = Date.parse("2017-9/12"); p d    # #<Date: 2017-09-12 ((2458009j,0s,0n),+0s,2299161j)>
Date.parse("xoxoxo$$") rescue p $! # #<ArgumentError: invalid date>

※ strptime による、書式を明示したパース方法に関しては、前述の 文字列からTimeオブジェクトに変換 を参照

● Dateオブジェクトから文字列に変換

require "date"

day = Date.today
str = day.strftime("%Y-%m-%d %H:%M:%S")
puts str
# 2017-04-26 00:00:00
# Dateは日付情報までしか保持していないので、時刻部分は0で表示される。

よく使いそうな書式は以下。Timeといっしょ。

%Y  年4桁
%m  月2桁
%d  日2桁
%H  時2桁
%M  分2桁
%S  秒2桁
%X  時:分:秒 (%H:%M:%S と同じ)
%w  曜日番号。0(日曜日) 〜 6(土曜日)

● 日付として正しいかチェックする

require "date"

p Date.valid_date?(2017, 12, 31)  # true
p Date.valid_date?(2017, 12, 32)  # false

d1 = Date.parse("20171001") rescue false; p d1  # #<Date: 2017-10-01 ((2458028j,0s,0n),+0s,2299161j)>
d2 = Date.parse("xoxoxo$$") rescue false; p d2  # false

● n日前・n日後 / n月前・n月後 / n年前・n年後

require "date"
day = Date.today; p day  # #<Date: 2017-04-26 ((2457870j,0s,0n),+0s,2299161j)>

# n日前・n日後
p day.prev_day(5)     # #<Date: 2017-04-21 ((2457865j,0s,0n),+0s,2299161j)>
p day.next_day(10)    # #<Date: 2017-05-06 ((2457880j,0s,0n),+0s,2299161j)>

# n月前・n月後
p day.prev_month(5)   # #<Date: 2016-11-26 ((2457719j,0s,0n),+0s,2299161j)>
p day.next_month(10)  # #<Date: 2018-02-26 ((2458176j,0s,0n),+0s,2299161j)>

# n年前・n年後
p day.prev_year(5)    # #<Date: 2012-04-26 ((2456044j,0s,0n),+0s,2299161j)>
p day.next_year(10)   # #<Date: 2027-04-26 ((2461522j,0s,0n),+0s,2299161j)>

● Timeオブジェクトに変換する

require "date"

day = Date.today; 
t = day.to_time; p t  # 2017-04-26 00:00:00 +0900

● Dateオブジェクトの大小比較

require "date"

d1 = Date.parse("2017/5/5");
d2 = Date.parse("2017/6/6");

p (d2 > d1)  # true

■ DateTime クラス

  • 日付と時刻を扱うクラス
  • Dateのサブクラスなので、Dateのメソッドが使える。時刻の情報まで保持しているので、戻り値が詳細になる。
  • 「require "date"」する必要がある
  • Timeクラスがあるから、こちらはあまり使わないかもしれないが、DateとTimeの良いとこ取りな良さもあるかな。

● 現在時刻でDateTimeオブジェクトを生成

require "date"
d = DateTime.now; p d
   # #<DateTime: 2017-04-26T22:12:41+09:00 ((2457870j,47561s,244634000n),+32400s,2299161j)>

● 特定の日時でDateTimeオブジェクトを生成

require "date"
d = DateTime.new(2017, 4, 26, 22, 03, 45, 0.375); p d
   # #<DateTime: 2017-04-26T22:03:45+09:00 ((2457870j,47025s,0n),+32400s,2299161j)>

最後の引数は、協定世界時との時差(オフセット)を指定する。
日本の場合は、時差9時間なので、9時間/24時間 = 0.375 を指定する。

● いろんな切り口でDateTimeオブジェクトから値を取り出す

require "date"
dt = DateTime.now;

# Dateの属性に加えて、時刻部分を取得できる
p [
  dt.year,  # 2017   年
  dt.month, # 4      月。mon でも同じ。
  dt.day,   # 26     日。mday でも同じ。
  dt.wday,  # 3      曜日番号。0(日曜日) 〜 6(土曜日)
  dt.hour,  # 22     時
  dt.min,   # 18     分
  dt.sec,   # 24     秒
  dt.zone   # +09:00 タイムゾーン ※Timeクラスと戻り値が違うな
]

● 曜日を確認する

Dateのサブクラスなので、Dateとまったく同じ。

● 文字列からDateTimeオブジェクトに変換

require "date"

dt = DateTime.parse("2017/04/25 19:23:55"); p dt  # #<DateTime: 2017-04-25T19:23:55+00:00 ((2457869j,69835s,0n),+0s,2299161j)>
dt = DateTime.parse("2017/4/25 19"); p dt         # #<DateTime: 2017-04-25T19:00:00+00:00 ((2457869j,68400s,0n),+0s,2299161j)>
dt = DateTime.parse("2017/6/1 7:5"); p dt         # #<DateTime: 2017-06-01T07:05:00+00:00 ((2457906j,25500s,0n),+0s,2299161j)>
dt = DateTime.parse("2017-8-1 1:2:3"); p dt       # #<DateTime: 2017-08-01T01:02:03+00:00 ((2457967j,3723s,0n),+0s,2299161j)>
dt = DateTime.parse("20170907 122456"); p dt      # #<DateTime: 2017-09-07T12:24:56+00:00 ((2458004j,44696s,0n),+0s,2299161j)>
dt = DateTime.parse("20170907123456"); p dt       # #<DateTime: 2017-09-07T12:34:56+00:00 ((2458004j,45296s,0n),+0s,2299161j)>
dt = DateTime.parse("2017-9/12 12:34:56"); p dt   # #<DateTime: 2017-09-12T12:34:56+00:00 ((2458009j,45296s,0n),+0s,2299161j)>
DateTime.parse("xoxoxo$$") rescue p $!            # #<ArgumentError: invalid date>

※ strptime による、書式を明示したパース方法に関しては、前述の 文字列からTimeオブジェクトに変換 を参照

● DateTimeオブジェクトから文字列に変換

Dateのサブクラスなので、Dateとまったく同じ。
こちらは時刻の情報まで保持しているので、時刻の %H %M %S とかも0ではなくちゃんと表示される。

require "date"

dt = DateTime.now
str = dt.strftime("%Y-%m-%d %H:%M:%S")
puts str  # 2017-04-26 22:37:25

● Dateオブジェクトの大小比較

Dateのサブクラスなので、Dateとまったく同じ。

■ Active Support で Time, Date, DateTime クラスをパワーアップする

  • 「Active Support」はRubyの組み込みクラスやモジュールなどの機能を拡張してくれるコンポーネント
  • Railsに同梱されているが、単体のGemとしてもインストールできるので試してみる。
  • Time, Date, DateTime に、共通の便利メソッドが追加される。
  • ここでは、例としてTimeクラスで試してみる

● Active Support の導入

連載の第2回のエディタの環境構築のときに、Gem は bundler で入れた。
なので、Active Support の Gem も bundler で入れる

① 確認。

$ gem list activesupport

*** LOCAL GEMS ***

$ 
入ってない

② Gemfile のあるディレクトリへ移動して、Gemfile を開く

cd /Users/miro/
vi Gemfile

③ 以下の1行を追記して保存する

gem "activesupport"

④ んで、インストールを実行

$ bundle install
Fetching gem metadata from https://rubygems.org/..............
〜 中略 〜
Bundle complete! 3 Gemfile dependencies, 11 gems now installed.
〜 中略 〜

⑤ インストールされたか確認する

$ gem list active

*** LOCAL GEMS ***

activesupport (5.0.2)
インストールされた!

「gem list gem名」 は、部分一致で検索してくれる

● Active Support 拡張による便利メソッドを整理する

require 'active_support/time'

#
# ここでは、例としてTimeクラスで試しているが、
# これらのメソッドは Dateクラス, DateTimeクラス でも使える
#  ・唯一 seconds_since_midnight だけは、Dateクラスでは使えなかった。秒数取得メソッドだし)
#  ・その他、クラスによって保持している情報が違うので、戻り値は微妙に違う。
#

t = Time.current # 現在時刻
# t = Date.today
# t = DateTime.now

puts '----'

# 日単位
p t.yesterday         # 1日前
p t.tomorrow          # 1日後
p t.ago(3.days)       # n日前
p t.days_ago(3)       # n日前
p 3.days.ago          # n日前
p t.since(3.days)     # n日後
p t.days_since(3)     # n日後
p 3.days.since        # n日後
p t.beginning_of_day  # 今日の開始時間。YYYY-MM-DD 00:00:00
p t.end_of_day        # 今日の最終時間。YYYY-MM-DD 23:59:59

puts '----'

# 月単位
p t.prev_month         # 1ヶ月前
p t.next_month         # 1ヶ月後
p t.ago(3.months)      # nヶ月前
p t.months_ago(3)      # nヶ月前
p 3.months.ago         # nヶ月前
p t.since(3.months)    # nヶ月後
p t.months_since(3)    # nヶ月後
p 3.months.since       # nヶ月後
p t.beginning_of_month # 今月の月初日(時間は 00:00:00 になる)
p t.end_of_month       # 今月の月末日(時間は 23:59:59 になる)

puts '----'

# 週単位
p t.beginning_of_week   # 今週の初めの日。月曜が開始。(時間は 00:00:00 になる)
p t.end_of_week         # 今週の最後の日。月曜が開始。(時間は 23:59:59 になる)
p t.prev_week           # 先週の初めの日。月曜が開始。(時間は 00:00:00 になる)
p t.next_week           # 来週の初めの日。月曜が開始。(時間は 00:00:00 になる)
p t.prev_week(:friday)  # 先週のほにゃ曜日(時間は 00:00:00 になる)
p t.next_week(:friday)  # 来週のほにゃ曜日(時間は 00:00:00 になる)
p t.ago(3.weeks)        # n週間前
p t.weeks_ago(3)        # n週間前
p 3.weeks.ago           # n週間前
p t.since(3.weeks)      # n週間後
p t.weeks_since(3)      # n週間後
p 3.weeks.since         # n週間後

puts '----'

# 年単位
p t.prev_year          # 1年前
p t.next_year          # 1年後
p t.beginning_of_year  # 今年の年初日(時間は 00:00:00 になる)
p t.end_of_year        # 今年の年末日(時間は 23:59:59 になる)
p t.ago(3.years)       # n年前
p t.years_ago(3)       # n年前
p 3.years.ago          # n年前
p t.since(3.years)     # n年後
p t.years_since(3)     # n年後
p 3.years.since        # n年後

puts '----'

# 時・分・秒
# ちなみに hours_ago とか seconds_since とかは無いらしい
p t.ago(3600.seconds)    # n秒前
p 3600.seconds.ago       # n秒前
p t.since(3600.seconds)  # n秒後
p 3600.seconds.since     # n秒後
p t.ago(60.minutes)      # n分前
p 60.minutes.ago         # n分前
p t.since(60.minutes)    # n分後
p 60.minutes.since       # n分後
p t.ago(1.hours)         # n時間前
p 1.hours.ago            # n時間前
p t.since(1.hours)       # n時間後
p 1.hours.since          # n時間後

puts '----'

# かっこいいメソッド
p t.seconds_since_midnight  # 今日の深夜0時からの経過秒数を取得

# 四半期!
p t.beginning_of_quarter  # 今四半期の初日  (時間は 00:00:00 になる)
p t.end_of_quarter        # 今四半期の最終日(時間は 23:59:59 になる)

# 差分を指定して取得( advance )
p t.advance(months: 6, days: -10)  # 6ヶ月後の10日前。yearsでもhoursでもご随意に。

# 特定の日時を指定して取得( change )
p t.change(year:2020, month:12, day:24)  # yearsでもhoursでもご随意に。

puts '----'

# 加減算
p t + 5.days
p t - 1.week
p t - 2.years
prgseek
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした