Ruby / Rails – 更改时间的时区,而不更改值

我在数据库中有一个loggingfoo :start_time:timezone属性。

例如:start_time是UTC中的时间 – 2001-01-01 14:20:00 。 例如:timezone是一个string – America/New_York

我想创build一个新的Time对象,其值为: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 

听起来像你想要的东西沿线

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

这就是说,将这个本地时间(使用区域)转换为utc。 如果你有Time.zone设置,那么你当然可以

 Time.zone.local_to_utc(t) 

这不会使用附加到t的时区 – 它假定它是从您转换的时区的本地。

一个需要注意的边缘情况是DST转换:您指定的本地时间可能不存在或可能不明确。

如果你使用的是Rails,下面是Eric Walsh的回答的另一种方法:

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

您需要将时间偏移添加到您的时间后,您将其转换。

最简单的方法是

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

不知道为什么你会想这样做…. tho可能是最好的实际工作时间与他们的方式。 我想你为什么需要改变时间和时区的一些背景将是有益的。

其实,我认为你需要在转换之后减去偏移量,如下所示:

 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 

取决于你要在哪里使用这个时间。

当你的时间是一个属性

如果将时间用作属性,则可以使用相同的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 

当你设置一个单独的variables

使用相同的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 
 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 

快速的小function,我想出来解决这个工作。 如果有人有这样做的更有效的方式,请张贴它!

我花了很多时间在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 

这意味着你可以专注于在你想要的区域首先获得适当的时间安排,你可以考虑这个方法(至less在我的脑海中,我是这样划分的),然后转换到区域你想validation你的业务逻辑。

这也适用于Ruby 2.3.1。

我刚刚面临同样的问题,这就是我要做的事情:

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

这里是关于asctime的文档

我创build了一些帮助器方法,其中一种方法与Ruby / Rails中原创作者的要求相同- 更改时间的时区,而不更改值 。

此外,我还logging了我观察到的一些特性,而且这些帮助程序包含的方法可以完全忽略适用的自动日光节约,而时间转换在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 eg 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 

希望这可以帮助。