レシピ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