121

foo在数据库中有一条记录,其中包含:start_time:timezone属性。

例如,这:start_time是 UTC 时间 - 2001-01-01 14:20:00。例如,这:timezone是一个字符串 - America/New_York

我想创建一个新的 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

IE。我想要做:

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

12 回答 12

84

听起来你想要一些类似的东西

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

这表示将本地时间(使用区域)转换为 UTC。如果你已经Time.zone设置了,那么你当然可以

Time.zone.local_to_utc(t)

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

这里要注意的一种极端情况是 DST 转换:您指定的本地时间可能不存在或可能不明确。

于 2013-05-29T16:14:37.757 回答
46

我刚刚遇到了同样的问题,这就是我要做的:

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

这是关于 asctime 的文档

于 2017-09-28T16:20:49.610 回答
24

如果您使用的是 Rails,这里是 Eric Walsh 回答的另一种方法:

def set_in_timezone(time, zone)
  Time.use_zone(zone) { time.to_datetime.change(offset: Time.zone.now.strftime("%z")) }
end
于 2015-08-26T19:13:45.590 回答
7

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

最简单的方法是:

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

我不确定您为什么要这样做,尽管最好按照它们的构建方式实际使用时间。我想有关为什么需要改变时间和时区的一些背景知识会有所帮助。

于 2013-05-29T16:22:41.830 回答
6

实际上,我认为您需要在转换后减去偏移量,如:

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 
于 2013-05-29T16:39:57.800 回答
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
于 2013-12-02T14:58:41.810 回答
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

我想出的快速小功能来解决这项工作。如果有人有更有效的方法,请发布!

于 2015-07-03T00:50:23.640 回答
0

我创建了一些辅助方法,其中一个方法与 Ruby/Rails 上的帖子的原作者提出的问题相同- 更改时间的时区,而不更改值

此外,我还记录了我观察到的一些特性,并且这些帮助程序包含完全忽略自动夏令时适用的方法,而时间转换在 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

希望这可以帮助。

于 2016-12-13T11:25:12.360 回答
0

我也花了很多时间与 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。

于 2017-01-10T23:07:09.943 回答
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 参考

于 2020-04-15T10:03:22.640 回答
0

这对我很有效

date = '23/11/2020'
time = '08:00'
h, m = time.split(':')
timezone = 'Europe/London'

date.to_datetime.in_time_zone(timezone).change(hour: h, min: m)
于 2020-10-13T10:27:40.583 回答
0

问题是关于 Rails 的,但似乎和我一样,这里并不是每个人都在 ActiveSupport 火车上,所以还有另一种选择:

irb(main):001:0> require "time"
=> true
irb(main):003:0> require "tzinfo"
=> true
irb(main):004:0> t = Time.parse("2000-01-01 14:20:00 UTC")
=> 2000-01-01 14:20:00 UTC
irb(main):005:0> tz = TZInfo::Timezone.get("America/New_York")
=> #<TZInfo::DataTimezone: America/New_York>
irb(main):008:0> utc = tz.local_to_utc(t)
=> 2000-01-01 19:20:00 UTC
irb(main):009:0> tz.utc_to_local(utc)
=> 2000-01-01 14:20:00 -0500
irb(main):010:0> 

local_to_utc不做相反的事情utc_to_local可能看起来像一个错误,但至少记录在案:https ://github.com/tzinfo/tzinfo说:

时间的偏移量被忽略 - 它被视为时区的本地时间

于 2021-04-22T21:13:43.193 回答