2013-05-29 40 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