Ruby/Rails-更改 Time 的时区,但不更改时区的值

我有一个记录 foo在数据库中有 :start_time:timezone属性。

例如,:start_time是 UTC-2001-01-01 14:20:00中的 Time。 :timezone是一个字符串 -America/New_York,例如。

我想创建一个值为 :start_time但其时区由 :timezone指定的新 Time 对象。我不想加载 :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
86677 次浏览

听起来你想要的东西

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

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

Time.zone.local_to_utc(t)

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

这里需要防范的一个边缘情况是 DST 转换: 您指定的本地时间可能不存在,或者可能不明确。

在转换时间后,需要将时间偏移量添加到时间中。

最简单的方法是:

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

我不知道为什么你会想要这样做,虽然它可能是最好的实际工作时间的方式建立。我想一些关于你为什么需要改变时区和时区的背景知识会很有帮助。

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

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 _ 属性 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 _ 属性 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

我想出来的解决问题的小方法。如果有人有一个更有效的方式这样做,请张贴它!

如果你正在使用 Rails,这里有另外一种方法,就像 Eric Walsh 的回答:

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

我已经创建了几个助手方法,其中一个方法只是做了与原作者在 Ruby/Rails-更改 Time 的时区,但不更改时区的值上要求的相同的事情。

另外,我已经记录了一些我观察到的特性,并且这些 helper 包含了一些方法,可以完全忽略适用于 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

希望这个能帮上忙。

我也花了很多时间在 TimeZone 上,在修改了 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。

我刚刚遇到了同样的问题,接下来我要做的是:

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

这是 关于 asctime 的文档

下面是另一个比现在的答案更适合我的版本:

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 引用

这招对我很管用

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)

这个问题和 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说:

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

我设法通过调用带有所需时区的 change来实现这一点:

>> t = Time.current.in_time_zone('America/New_York')
=> Mon, 08 Aug 2022 12:04:36.934007000 EDT -04:00
>> t.change(zone: 'Etc/UTC')
=> Mon, 08 Aug 2022 12:04:36.934007000 UTC +00:00

Https://api.rubyonrails.org/classes/activesupport/timewithzone.html#method-i-change