>> 連載の目次は こちら!
今回は、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