Ruby / Rails-値を変更せずに、時間のタイムゾーンを変更します


108

と属性fooを持つデータベースにレコードが:start_timeあり:timezoneます。

:start_time- UTC時間である2001-01-01 14:20:00例えば、。:timezone-文字列ですAmerica/New_Yorkたとえば、。

の値を持つ新しいTimeオブジェクトを作成したい:start_timeが、そのタイムゾーンはによって指定されている:timezone。Railsは賢く、UTCからの時間をそのタイムゾーンと一致するように更新:start_timeするため:timezone、をロードしてからに変換したくありません。

現在、

t = foo.start_time
=> 2000-01-01 14:20:00 UTC
t.zone
=> "UTC"
t.in_time_zone("America/New_York")
=> Sat, 01 Jan 2000 09:20:00 EST -05:00

代わりに、私は見たいです

=> Sat, 01 Jan 2000 14:20:00 EST -05:00

すなわち。私はやってみたいです:

t
=> 2000-01-01 14:20:00 UTC
t.zone = "America/New_York"
=> "America/New_York"
t
=> 2000-01-01 14:20:00 EST

1
多分これが役立つでしょう:api.rubyonrails.org/classes/Time.html#method-c-use_zone
MrYoshiji

3
タイムゾーンを正しく使用しているとは思いません。ローカルからUTCとしてデータベースに保存した場合、ローカル時間で解析して相対utcで保存するとどうなるでしょうか。
旅行

1
ええ…私はあなたがこれをする必要がある理由を説明する必要があるかもしれない助けを最もよく受けると思いますか?そもそもなぜデータベースに間違った時間を保存するのでしょうか?
nzifnab 2013年

@MrYoshijiは同意しました。YAGNIのように聞こえるか、私にとって時期尚早の最適化のように思えます。
engineerDave

1
これらのドキュメントが役に立った場合は、StackOverflowは必要ありません。また、夏時間の開始または終了時に壊れないApple-to-Apples比較を強制するためにも、これを行う必要があります。
JosephK 2015年

回答:


72

に沿って何かをしたいように聞こえます

ActiveSupport::TimeZone.new('America/New_York').local_to_utc(t)

これは、このゾーンを(ゾーンを使用して)utcに変換することを示しています。あなたがTime.zone設定した場合、もちろんあなたはすることができます

Time.zone.local_to_utc(t)

これは、tにアタッチされたタイムゾーンを使用しません-変換元のタイムゾーンに対してローカルであると想定しています。

ここで保護する1つのエッジケースは、DST遷移です。指定した現地時間が存在しないか、あいまいである可能性があります。


17
local_to_utcとTime.use_zoneの組み合わせ、私は必要なものである:Time.use_zone(self.timezone) { Time.zone.local_to_utc(t) }.localtime
RWB

DSTの移行に対して何を提案しますか?変換するターゲット時間はD / ST移行後で、Time.nowは変更前です。それはうまくいくでしょうか?
Cyril Duchon-Doris

28

私は同じ問題に直面したばかりであり、これが私がやろうとしていることです:

t = t.asctime.in_time_zone("America/New_York")

これはasctimeドキュメントです


2
それはちょうど私が期待したことをします
Kaz

ありがとうございます。それに基づいて私の答えを簡素化しました。欠点の1つasctimeは、サブセカンドの値がドロップされることです(私の答えはそのままです)。
Henrik N

22

Railsを使用している場合、Eric Walshの回答に沿った別の方法を次に示します。

def set_in_timezone(time, zone)
  Time.use_zone(zone) { time.to_datetime.change(offset: Time.zone.now.strftime("%z")) }
end

1
そして、DateTimeオブジェクトをTimeWithZoneオブジェクトに戻すには.in_time_zone、最後まで追加します。
Brian

@Brian Murphy-Dyeこの関数を使用して夏時間に問題がありました。質問を編集して、DSTで機能するソリューションを提供できますか?多分Time.zone.nowあなたが変更したい時間に最も近いものに置き換えることはうまくいくでしょうか?
Cyril Duchon-Doris

6

変換した後、時間オフセットを時間に追加する必要があります。

これを行う最も簡単な方法は次のとおりです。

t = Foo.start_time.in_time_zone("America/New_York")
t -= t.utc_offset

なぜあなたがこれをしたいのかはわかりませんが、実際にそれらが構築された方法で実際に作業するのが最善です。時間とタイムゾーンを変更する必要がある理由についての背景が役立つと思います。


これでうまくいきました。使用時にオフセット値が設定されていれば、夏時間のシフト中もこれは正しいままです。DateTimeオブジェクトを使用する場合は、「offset.seconds」を加算または減算できます。
JosephK

あなたのRailsのしている外部の場合は、使用することができますTime.in_time_zoneactive_supportの正しい部品を必要とすることによって:require 'active_support/core_ext/time'
jevon

これは、どちらの方向に進むかによって間違った動作をするようであり、常に信頼できるとは限りません。たとえば、今ストックホルムの時間を取り、ロンドン時間に変換する場合、オフセットを加算(減算ではない)すると機能します。しかし、ストックホルムをヘルシンキに変換すると、加算するか減算するかは間違いです。
Henrik N

5

実際には、次のように、変換後にオフセットを減算する必要があると思います。

1.9.3p194 :042 > utc_time = Time.now.utc
=> 2013-05-29 16:37:36 UTC
1.9.3p194 :043 > local_time = utc_time.in_time_zone('America/New_York')
 => Wed, 29 May 2013 12:37:36 EDT -04:00
1.9.3p194 :044 > desired_time = local_time-local_time.utc_offset
 => Wed, 29 May 2013 16:37:36 EDT -04:00 

3

この時間をどこで使用するかによって異なります。

あなたの時間が属性であるとき

時間を属性として使用する場合は、同じdate_time_attribute gemを使用できます。

class Task
  include DateTimeAttribute
  date_time_attribute :due_at
end

task = Task.new
task.due_at_time_zone = 'Moscow'
task.due_at                      # => Mon, 03 Feb 2013 22:00:00 MSK +04:00
task.due_at_time_zone = 'London'
task.due_at                      # => Mon, 03 Feb 2013 22:00:00 GMT +00:00

別の変数を設定すると

同じdate_time_attribute gemを使用します。

my_date_time = DateTimeAttribute::Container.new(Time.zone.now)
my_date_time.date_time           # => 2001-02-03 22:00:00 KRAT +0700
my_date_time.time_zone = 'Moscow'
my_date_time.date_time           # => 2001-02-03 22:00:00 MSK +0400

1
def relative_time_in_time_zone(time, zone)
   DateTime.parse(time.strftime("%d %b %Y %H:%M:%S #{time.in_time_zone(zone).formatted_offset}"))
end

仕事を解決するために思いついた簡単な機能。誰かがこれを行うためのより効率的な方法を持っている場合は、投稿してください!


1
t.change(zone: 'America/New_York')

OPの前提は正しくありません。「Railsは賢く、UTCから時刻を更新してそのタイムゾーンに合わせるため、:start_timeをロードしてから:timezoneに変換したくない」ここで提供されている回答で示されているように、これは必ずしも真実ではありません。


1
これは質問に対する答えを提供しません。批評したり、著者に説明を要求するには、投稿の下にコメントを残してください。- レビューから
Aksen P

これが質問に対する有効な回答である理由、および質問が欠陥のある仮定に基づいている理由を明確にするために私の回答を更新しました。
kkurian

これは何もしないようです。私のテストはそれが何もないことを示しています。
Itay Grudev

@ItayGrudev 何を見るt.zone前に走るとt.change?そして、あなたは何を実行する場合についてはt.zone後にt.change?そして、どのパラメーターにt.change正確に渡していますか?
kkurian

0

私はいくつかのヘルパーメソッドを作成しました。そのうちの1つは、Ruby / Railsの投稿の元の作成者から尋ねられたのと同じことを行うだけです値を変更せずにTimeのタイムゾーンを変更します

また、私が観察したいくつかの特殊性を文書化しました。また、これらのヘルパーには、Railsフレームワークではそのままでは利用できない時間変換中に適用可能な自動夏時間を完全に無視するメソッドが含まれています。

  def utc_offset_of_given_time(time, ignore_dst: false)
    # Correcting the utc_offset below
    utc_offset = time.utc_offset

    if !!ignore_dst && time.dst?
      utc_offset_ignoring_dst = utc_offset - 3600 # 3600 seconds = 1 hour
      utc_offset = utc_offset_ignoring_dst
    end

    utc_offset
  end

  def utc_offset_of_given_time_ignoring_dst(time)
    utc_offset_of_given_time(time, ignore_dst: true)
  end

  def change_offset_in_given_time_to_given_utc_offset(time, utc_offset)
    formatted_utc_offset = ActiveSupport::TimeZone.seconds_to_utc_offset(utc_offset, false)

    # change method accepts :offset option only on DateTime instances.
    # and also offset option works only when given formatted utc_offset
    # like -0500. If giving it number of seconds like -18000 it is not
    # taken into account. This is not mentioned clearly in the documentation
    # , though.
    # Hence the conversion to DateTime instance first using to_datetime.
    datetime_with_changed_offset = time.to_datetime.change(offset: formatted_utc_offset)

    Time.parse(datetime_with_changed_offset.to_s)
  end

  def ignore_dst_in_given_time(time)
    return time unless time.dst?

    utc_offset = time.utc_offset

    if utc_offset < 0
      dst_ignored_time = time - 1.hour
    elsif utc_offset > 0
      dst_ignored_time = time + 1.hour
    end

    utc_offset_ignoring_dst = utc_offset_of_given_time_ignoring_dst(time)

    dst_ignored_time_with_corrected_offset =
      change_offset_in_given_time_to_given_utc_offset(dst_ignored_time, utc_offset_ignoring_dst)

    # A special case for time in timezones observing DST and which are
    # ahead of UTC. For e.g. Tehran city whose timezone is Iran Standard Time
    # and which observes DST and which is UTC +03:30. But when DST is active
    # it becomes UTC +04:30. Thus when a IRDT (Iran Daylight Saving Time)
    # is given to this method say '05-04-2016 4:00pm' then this will convert
    # it to '05-04-2016 5:00pm' and update its offset to +0330 which is incorrect.
    # The updated UTC offset is correct but the hour should retain as 4.
    if utc_offset > 0
      dst_ignored_time_with_corrected_offset -= 1.hour
    end

    dst_ignored_time_with_corrected_offset
  end

上記のメソッドをクラスまたはモジュールでラップした後、railsコンソールまたはrubyスクリプトで試すことができる例:

dd1 = '05-04-2016 4:00pm'
dd2 = '07-11-2016 4:00pm'

utc_zone = ActiveSupport::TimeZone['UTC']
est_zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)']
tehran_zone = ActiveSupport::TimeZone['Tehran']

utc_dd1 = utc_zone.parse(dd1)
est_dd1 = est_zone.parse(dd1)
tehran_dd1 = tehran_zone.parse(dd1)

utc_dd1.dst?
est_dd1.dst?
tehran_dd1.dst?

ignore_dst = true
utc_to_est_time = utc_dd1.in_time_zone(est_zone.name)
if utc_to_est_time.dst? && !!ignore_dst
  utc_to_est_time = ignore_dst_in_given_time(utc_to_est_time)
end

puts utc_to_est_time

お役に立てれば。


0

現在の回答よりもうまく機能した別のバージョンを次に示します。

now = Time.now
# => 2020-04-15 12:07:10 +0200
now.strftime("%F %T.%N").in_time_zone("Europe/London")
# => Wed, 15 Apr 2020 12:07:10 BST +01:00

「%N」を使用してナノ秒を超えて運びます。別の精度が必要な場合は、このstrftimeリファレンスをご覧ください


-1

私もTimeZonesと格闘するのにかなりの時間を費やし、Ruby 1.9.3をいじった後、変換する前に名前付きタイムゾーンシンボルに変換する必要がないことに気付きました。

my_time = Time.now
west_coast_time = my_time.in_time_zone(-8) # Pacific Standard Time
east_coast_time = my_time.in_time_zone(-5) # Eastern Standard Time

これが意味することは、最初に適切な時間を適切な時間に設定することに焦点を当てることができ、それについてあなたがそれを考える方法(少なくとも私の頭の中で私はそれをこのように分割する)に集中し、最後にゾーンに変換することですあなたはあなたのビジネスロジックを検証したいです。

これはRuby 2.3.1でも機能します。


1
時々東方オフセットは-4ですが、どちらの場合も自動的に処理したいと思います。
OpenCoderX 2019年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.