レシピ1.7 タイムゾーンの変換
問題
タイムオブジェクトを変更して、他のタイムゾーンで同じ瞬間を表したい。
解決
最も一般的なタイムゾーン変換は、ローカルタイムからUCTへの変換とUTCからローカルタイムへの変換である。これらの変換はTimeでもDateTimeでも簡単に行える。
Time#gmtimeのメソッドは、Timeオブジェクトを直接変更して、UCTに変換する。Time#localtimeメソッドは逆方向への変換を行う。
now = Time.now # => Sat Mar 18 20:15:58 EST 2006 now = now.gmtime # => Sun Mar 19 01:15:58 UTC 2006 now = now.localtme # => Sat Mar 18 20:15:58 EST 2006
DateTime.new_offsetメソッドは、DateTimeオブジェクトをあるタイムゾーンから別のタイムゾーンに変換する。このメソッドには、変換先のタイムゾーンのUTCからのオフセットを渡さなければならない。ローカルタイムをUTCに変換するには、0を渡す。DateTimeオブジェクトは不変なので、このメソッドによって、タイムゾーンのオフセット以外は返還前のDateTimeオブジェクトと全く同じオブジェクトが新たに生成される。
UTCのDateTimeオブジェクトをローカルタイムに変換するには、DateTimeオブジェクトでoffsetを呼び出すことだ。オフセットは通常、分母が24の有理数となる。
local = DateTime.now utc = local.new_offset local.offset # => Rational(-5, 24) local_from_utc = utc.new_offset(local.offset) local_from_utc.to_s # => "2006-03-18T20:15:58-0500" local == local_from_utc # => true
解説
Time.at、Time.local、Time.mktime、Time.new、およびTime.nowで生成されるTimeオブジェクトは、現在のシステムのタイムゾーンに基づいて生成される。Time.gmとTime.utcで生成されるTimeオブジェクトは任意のタイムゾーンを表せるが、ローカルタイムやUTC以外のTimeオブジェクトでタイムゾーンを使用するのは難しい。
# ローカル(東部標準)時間を太平洋標準時間に変換する eastern = DateTime.now eastern.to_s # => "2006-03-18T20:15:58-0500" pacific_offset = Rational(7, 24) pacific = eastern.new_offset(pacific_offset) pacific.to_s # => "2006-03-18T18:15:58-0700"
Datetime#new_offsetを使用すれば、任意のタイムゾーンオフセット間での変換が可能である。このため、タイムゾーン変換では、DateTimeオブジェクトをし雄牛、必要に応じてTimeオブジェクトに再変換するのが最も簡単である。しかし、DateTimeオブジェクトはタイムゾーンを数値のUTCオフセットとしてのみ理解する。タイムゾーンが「WET」、「Zulu」、「Asia/Taskent」と呼ばれていることしか分からない場合、日付と時刻をUTCに変換するにはどうすればよいだろうか。
UNIXシステムでは、現在のプロセスの「システム」タイムゾーンを一時的に変更することが出来る。Timeクラスに使用されているCライブラリは、膨大な数のタイムゾーンを認識する(利用可能なタイムゾーンを調べたい場合、この「aoneinfo」データベースは通常、/usr/shere/zoneinfo/にある。)環境変数TZを適切な値に設定し、コンピュータが他のタイムゾーンン井あるかのようにTimeクラスを動作させれば、この知識を引き出すことができる。このトリックを利用して、CライブラリがサポートしているタイムゾーンにTimeオブジェクトを変換する方法は、次のようになる。
class Time def convert_zone(to_zone) original_zone = ENV["TZ"] utc_time = dup.gmtime ENV["TZ"] = to_zone to_zone_time = utc_time.localtime ENV["TZ"] = original_zone return to_zone_time end end
ローカルタイム(東部標準)から世界各地のタイムゾーンへの変換を行ってみよう。
t = Time.at(1000000000) # => Sat Sep 08 21:46:40 EDT 2001 t.convert_zone("US/Pacific") # => Sat Sep 08 18:46:40 PDT 2001 time.convert_zone("US/Alaska") # => Sun Sep 08 17:46:40 AKDT 2001 t.convert_zone("UTC") # => Sun Sep 09 01:46:40 UTC 2001 t.convert_zone("Turkey") # = Sun Sep 09 04:46:40 EEST 2001
インドなどの一部のタイムゾーンではほとんどのタイムゾーンに対して30分のオフセットを使用することに注意しよう。
t.convert_zone("Asia/Calucutta") # => Sun Sep 09 07:16:40 IST 2001
Timeオブジェクトを生成する前にTZ環境変数を設定すると、任意のタイムゾーンの時刻を表すことが出来る。次のコードは、「実際」にどのタイムゾーンを使用するかに関わらず、ラゴス時間をシンガポール時間に変換する。
ENV["TZ"] = "Africa/Lagos" t = Time.at(1000000000) # => Sun Sep 09 02:46:40 WAT 2001 ENV["TZ"] = nil t.convert_zone("Singapore") # => Sun Sep 09 09:46:40 SGT 2001 # 前の時間と同じことを検証する t.convert_zone("US/Eastern") # => Sat Sep 08 21:46:40 EDT 2001
TZ環境変数はプロセスに対してグローバルなので、複数のスレッドで同時にタイムゾーンを変換しようとすると、問題が発生する。
参照
・レシピ1.8、1.9
・「zoneinfo」に含まれている情報(http://www.twinsun.com/tz/tz-link.htm)