2013-05-29 55 views
67

我有一個記錄foo在數據庫中具有:start_time:timezone屬性。Ruby/Rails - 更改時間的時區,而不更改值

例如,:start_time是UTC的時間 - 2001-01-01 14:20:00。例如, :timezone是一個字符串 - America/New_York

我想創建一個新的時間對象,其值爲:start_time,但其時區由:timezone指定。我不想加載:start_time,然後轉換爲:timezone,因爲Rails會很聰明,並且從UTC更新時間與該時區保持一致。

目前,

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

也許這將幫助:HTTP://api.rubyonrails。 org/classes/Time.html#method-c-use_zone – MrYoshiji

+2

我不認爲你正確使用時區。 如果你從本地保存到你的分貝作爲UTC,通過它的本地時間解析它並通過它的相對utc保存它有什麼錯誤? – Trip

+0

雅...我認爲最好的接受幫助你可能需要解釋*爲什麼*你需要這樣做?爲什麼你會在第一時間把錯誤的時間存儲在數據庫中? – nzifnab

回答

46

聽起來好像要沿着

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

行此說,這當地時間(使用區域)轉換爲UTC的東西。如果您已設置Time.zone那麼你當然可以到

Time.zone.local_to_utc(t) 

這將不使用連接於T時區 - 它假定它是本地您從轉換的時區。

這裏需要注意的一個邊緣情況是DST轉換:您指定的本地時間可能不存在或可能不明確。

+11

一個local_to_utc和Time.use_zone的組合是什麼,我需要:'Time.use_zone(self.timezone){Time.zone.local_to_utc(T)} .localtime' – rwb

+0

你有什麼建議對DST的轉變?假設轉換的目標時間在D/ST轉換之後,而Time.now在轉換之前。這會起作用嗎? –

6

您需要將時間偏移添加到您的時間後,您將其轉換。

做到這一點,最簡單的方法是

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

不知道你爲什麼會想這樣做....壽可能是最實際與時代工作,他們的構建方式。我想爲什麼你需要改變時間和時區會有所幫助。

+0

這對我有用。如果在使用偏移值時設置偏移值,則在夏時制轉換期間應該保持正確。如果使用DateTime對象,可以從中增加或減去「offset.seconds」。 – JosephK

4

事實上,我認爲你需要減去偏移在轉換之後,如:

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 

小巧的功能,我想出瞭解決的工作。如果有人有這樣做的更有效的方式,請發佈!

15

如果您使用Rails的,這裏是沿埃裏克·沃爾什的回答線的另一種方法:

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'加到最後。 – Sooie

+0

@Brian Murphy-Dye使用此功能我遇到夏令時問題。你能編輯你的問題以提供一個與DST協同工作的解決方案嗎? 也許更換'Time.zone.now'的東西,是最接近你想改變將工作時間? –

0

如由原始問我創建了一些輔助方法剛剛做同樣的事情之一該職位的作者爲Ruby/Rails - Change the timezone of a Time, without changing the value

而且我已經記錄幾個特點我觀察以及這些傭工包含的方法完全忽略自動日光節約適用,而時間的轉換是沒有可用出的現成的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 

實例可以導軌控制檯或紅寶石腳本上包裹在一個類或模塊的上述方法之後嘗試:

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 

希望這有助於。

1

我花了顯著的時間與時區掙扎的歡迎,並用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。

2

我剛剛遇到了同樣的問題,這裏就是我要做的事情:

t = t.asctime.in_time_zone("America/New York") 

這裏是documentation on asctime

+0

它只是做我的預期 – Kaz