Ruby の Time.parse で文字列を Time に変換するときのエラーチェック
Ruby では、文字列から Time オブジェクト への変換には、time ライブラリ によって拡張される Time.parse
を使う。
require "time"
Time.parse("2007/6/21 19:23")
# => Thu Jun 21 19:23:00 +0900 2007
Time.parse
は内部的に ParseDate.parsedate
を利用しているので、さまざまなフォーマットをサポートしており、それなりに便利だ。逆に、サポートされていないフォーマットの文字列を変換するときは、事前に正規表現で変換するなどの泥臭い作業になる。
Time.parse("Sat Aug 28 21:45:09 1999")
# => Sat Aug 28 21:45:09 +0900 1999
Time.parse("08/28/1999")
# => Sat Aug 28 00:00:00 +0900 1999
他にも問題がある。
変換対象の文字列が日付形式ではなかった場合は、当然 Time
に変換できないわけだが、そういうときでもエラーを検知する仕組みがないのだ。
Time.parse("Happy Hacking!")
# => Thu Jun 21 19:33:12 +0900 2007
このように、明らかに日付ではない文字列を渡しても、例外が発生するでもなく、Time
オブジェクトが返ってくる。
Time.parse のマニュアルによると、
Time.parse(date, now=Time.now)
与えられた時刻に上位の要素がなかったり壊れていた場合、now の該当要素が使われます。
とのことで、解析に失敗した場合は「時刻の要素がない」が判断され、引数 now
(この場合、デフォルトの Time.now
)がそのまま返ってきているようだ。
では、この引数 now
に nil
を渡せば、失敗時に nil
が返ってくるだろうか?
Time.parse("Happy Hacking!", nil)
# => Thu Jan 01 00:00:00 +0900 1970
違った。
実は Time.parse
は、それぞれの時刻要素のデフォルトとして 1970-01-01 00:00:00:00
を指定している。また、この値は Time
の起算時間(Unix epoch
)に見えるが、結果は地方時間で返ってくるので必ずしもそうではない(日本の場合、1970-01-01 00:00:00 +0900
)。
これだと困るので、解析に失敗した場合は nil
を返す Time.parse
を書いてみた。
require "parsedate"
require "time"
# The Unix epoch is the time 00:00:00 UTC on January 1, 1970
UNIX_EPOCH_TIME = Time.at(0)
# Strict version of +Time.parse+, returns +nil+ when parsing is failed.
def strict_parsetime(string)
# +Time.parse+ returns localtime "1970/01/01 00:00:00" when parsing is failed.
# So, ugly, I check whether returned value is UNIX epoch.
time = Time.parse(string, UNIX_EPOCH_TIME) rescue nil
if UNIX_EPOCH_TIME == time then
# Previous +Time.parse+ possibly failed.
time = nil unless (ParseDate.parsedate(string)[0] rescue nil)
end
time
end
実装としては、TIme.parse
の第二引数に特定の日時を指定し、結果がこの値なら再度 ParseDate.parsedate
して本当にエラーなのかチェックしている。
ちゃんとした方法がありそうなんだけどなー。