Edited at

ついにRoRの日付の闇を払った

More than 3 years have passed since last update.


時間を表現する型


日付(2001年1月1日)だけを扱う型

「2001年1月1日」のように日付だけを表現する型を、

PostgreSQLはDATE型として扱える。

RoRはDateクラスとして扱える。


時間(9:05)だけを扱う型

「9時5分」のように時間だけを表現できる型を、

PostgreSQLはTIME型として扱える。

RoRはクラスとして存在しない。(日付をどうしても付けないといけない)


モデルの定義とRoRのクラス

:dateで定義したものはDateクラス

:timeで定義したものはTimeクラス

:datetimeで定義したものはActiveSupport::TimeWithZoneクラス

(以下ActiveSupport::TimeWithZoneクラスTimeWithZoneクラスと呼ぶ)


RoRで基本的に使うクラス

モデルの定義とRoR型の関係より主にRubyソース内で使うこととなるRoRの型は次の3つ。

Dateクラス, Timeクラス, TimeWithZoneクラス

(この中に含まれないDateTimeクラスは基本的に使わない)


TimeクラスとTimeWithZoneクラスで迷ったら

TimeクラスTimeWithZoneクラスで迷ったらTimeWithZoneクラスを使っておく。


日付を表した文字列


ISOで規定された表現

ISOでタイムゾーンと時間を同時に表現した書式が規定されているので、これを使う。

2001-01-01T09:05:00+09:00 (UTC+9 日本)

2001-01-01T09:05:00Z(UTC+0 イギリス)


2001-01-01 09:05の表記

2001-01-01 09:05

(このようにタイムゾーンがないものをここでは no timezoneと呼ぶこととする)


文字列からクラスへの変換


文字列 to Dateクラス

'2000-01-01'.to_date


文字列 to Timeクラス

'2001-01-01T09:05:00+09:00'.to_time


文字列 to TimeWithZoneクラス

Time.zone.parse('2001-01-01T09:05:00+09:00')


クラスから年、月、日、時間、分、曜日を取り出す

ここではあえて

config.time_zone = 'Pacific Time (US & Canada)'

を設定した状態での動きを記載する。


Timeクラス

0> hoge = '2001-01-01T09:05:00+09:00'.to_time

=> 2001-01-01 09:05:00 +0900

0> hoge.year

=> 2001

0> hoge.month

=> 1

0> hoge.day

=> 1

0> hoge.hour

=> 9

0> hoge.min

=> 5

0> hoge.wday

=> 1

コンフィグで日本でないタイムゾーンを設定しているのに日本の時間でGETしている...


TimeWithZoneクラス

0> foo = Time.zone.parse('2001-01-01T09:05:00+09:00')

=> Sun, 31 Dec 2000 16:05:00 PST -08:00

0> foo.year

=> 2000

0> foo.month

=> 12

0> foo.day

=> 31

0> foo.min

=> 5

0> foo.wday

=> 0

(あれ? wdayでうまくとれない気がしたが気のせいか...)

コンフィグで設定した時間で取得(GET)している。


UTCとして取り出したい


Timeクラス

0> hoge = '2001-01-01T09:05:00+09:00'.to_time

=> 2001-01-01 09:05:00 +0900

0> hoge.utc

=> 2001-01-01 00:05:00 UTC

0> hoge.utc.class

=> Time

0> hoge.utc.hour

=> 0

.utcでアクセスすれば、UTCの時間としてGETできる。


TimeWithZoneクラス

0> foo = Time.zone.parse('2001-01-01T09:05:00+09:00')

=> Sun, 31 Dec 2000 16:05:00 PST -08:00

0> foo.utc

=> 2001-01-01 00:05:00 UTC

0> foo.utc.class

=> Time

.utcでアクセスすれば、UTCの時間としてGETできる。

このときクラスはTimeクラスになっている。


クラス to 文字列


Timeクラス

0> hoge.iso8601

=> "2001-01-01T09:05:00+09:00"

なんで日本以外のタイムゾーン設定してるのに、+9:00で出るんだろう...

0> hoge.utc.iso8601

=> "2001-01-01T00:05:00Z"


TimeWithZoneクラス

0> foo.iso8601

=> "2000-12-31T16:05:00-08:00"

0> foo.utc.iso8601

=> "2001-01-01T00:05:00Z"


ハマらないために

+9:00がついた日本時間で文字列が取得されたり、日本時間で.hourが取得されたりで、ハマるので、全部UTCとして扱ったほうがよい。

'2015-09-06T01:00:00Z'.to_time.utc

0> hoge = '2015-09-06T01:00:00Z'.to_time.utc

=> 2015-09-06 01:00:00 UTC

0> hoge.iso8601
=> "2015-09-06T01:00:00Z"


クラス to 他クラス


TimeWithZone to Time

0> foo = Time.zone.parse('2001-01-01T09:05:00+09:00')

=> Sun, 31 Dec 2000 16:05:00 PST -08:00

foo.utc // UTCなTimeクラスへ


TimeWithZone to Date

0> foo = Time.zone.parse('2001-01-01T09:05:00+09:00')

=> Mon, 01 Jan 2001 09:05:00 JST +09:00

0> Date.new(foo.utc.year, foo.utc.month, foo.utc.day)
=> Mon, 01 Jan 2001

今回TimeやTimeWithZoneを生成する文字列に+9:00のものを使ったが、設計段階で、文字列もすべて00Zで表現できるようにしておいた方が、混乱を避けれる。


saveするときの挙動

DBへの保存のタイムゾーンは、

config.active_record.default_timezoneで設定。

デフォルトはUTC(イギリス)。

ということで、デフォルトで使った場合。

self.bar = Time.zone.parse('2001-01-01T09:05:00+09:00')

self.bar:datetimeで定義したもの。とした場合、DB内では、

Screen Shot 2015-09-08 at 08.33.28.png

このように表現される。