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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/16818180/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-02 22:04:20  来源:igfitidea点击:

Ruby / Rails - Change the timezone of a Time, without changing the value

ruby-on-railsrubydatetimetimetimezone

提问by rwb

I have a record fooin the database which has :start_timeand :timezoneattributes.

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

The :start_timeis a Time in UTC - 2001-01-01 14:20:00, for example. The :timezoneis a string - America/New_York, for example.

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

I want to create a new Time object with the value of :start_timebut whose timezone is specified by :timezone. I do not want to load the :start_timeand then convert to :timezone, because Rails will be clever and update the time from UTC to be consistent with that timezone.

我想创建一个新的 Time 对象,其值为 ,:start_time但其时区由:timezone. 我不想加载:start_time然后转换为:timezone,因为 Rails 会很聪明并从 UTC 更新时间以与该时区保持一致。

Currently,

目前,

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

Instead, I want to see

相反,我想看

=> Sat, 01 Jan 2000 14:20:00 EST -05:00

ie. I want to do:

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

采纳答案by Frederick Cheung

Sounds like you want something along the lines of

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

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

This says convert this local time (using the zone) to utc. If you have Time.zoneset then you can of course to

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

Time.zone.local_to_utc(t)

This won't use the timezone attached to t - it assumes that it's local to the time zone you are converting from.

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

One edge case to guard against here is DST transitions: the local time you specify may not exist or may be ambiguous.

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

回答by Zhenya

I've just faced the same problem and here is what I'm going to do:

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

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

Here is the documentation on asctime

这是关于 asctime文档

回答by Brian

If you're using Rails, here is another method along the lines of Eric Walsh's answer:

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

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

回答by j_mcnally

You need to add the time offset to your time after you convert it.

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

The easiest way to do this is:

最简单的方法是:

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

I am not sure why you would want to do this, though it is probably best to actually work with times the way they are built. I guess some background on why you need to shift time and timezones would be helpful.

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

回答by Peter Alfvin

Actually, I think you need to subtract the offset after you convert it, as in:

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

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 

回答by Sergei Zinin

Depends on where you are going to use this Time.

取决于您将在何处使用此时间。

When your time is an attribute

当你的时间是一种属性

If time is used as an attribute, you can use the same date_time_attribute gem:

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

When you set a separate variable

当你设置一个单独的变量时

Use the same date_time_attribute gem:

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

回答by Eric Walsh

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

Quick little function I came up with to solve the job. If someone has a more efficient way of doing this please post it!

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

回答by kkurian

t.change(zone: 'America/New_York')

The premise of the OP is incorrect: “I do not want to load the :start_time and then convert to :timezone, because Rails will be clever and update the time from UTC to be consistent with that timezone.” This is not necessarily true, as demonstrated by the answer provided here.

OP 的前提是不正确的:“我不想加载 :start_time 然后转换为 :timezone,因为 Rails 会很聪明并从 UTC 更新时间以与该时区保持一致。” 正如此处提供的答案所证明的那样,这不一定正确。

回答by Henrik N

Here's another version that worked better for me than the current answers:

这是另一个比当前答案更适合我的版本:

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

It carries over nanoseconds using "%N". If you desire another precision, see this strftime reference.

它使用“%N”进行纳秒。如果您需要更高的精度,请参阅此 strftime 参考

回答by Jignesh Gohel

I have created few helper methods one of which just does the same thing as is asked by the original author of the post at Ruby / Rails - Change the timezone of a Time, without changing the value.

我创建了几个辅助方法,其中一个方法与Ruby/Rails帖子的原始作者所要求的相同- Change the timezone of a Time, without changed the value

Also I have documented few peculiarities I observed and also these helpers contains methods to completely ignore automatic day-light savings applicable while time-conversions which is not available out-of-the-box in Rails framework:

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

Examples which can be tried on rails console or a ruby script after wrapping the above methods in a class or module:

将上述方法包装在类或模块中后,可以在 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

Hope this helps.

希望这可以帮助。