【问题标题】:Ruby / Rails - Change the timezone of a Time, without changing the valueRuby / Rails - 更改时间的时区,而不更改值
【发布时间】:2013-05-24 23:12:06
【问题描述】:

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

: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

【问题讨论】:

  • 我认为您没有正确使用时区。如果您将其作为 UTC 从本地保存到您的数据库中,那么通过其本地时间解析它并通过其相对 utc 保存它有什么问题?
  • 是的...我认为为了最好地获得帮助,您可能需要解释为什么您需要这样做?为什么一开始就将错误的时间存储到数据库中?
  • @MrYoshiji 同意了。对我来说听起来像是 YAGNI 或过早的优化。
  • 如果这些文档有帮助,我们就不需要 StackOverflow :-) 有一个例子,它没有显示任何设置 - 典型。我还需要这样做以强制进行 Apples-To-Apples 比较,该比较不会在夏令时开始或结束时中断。

标签: ruby-on-rails ruby datetime time timezone


【解决方案1】:

听起来你想要一些类似于

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

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

Time.zone.local_to_utc(t)

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

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

【讨论】:

  • local_to_utc 和 Time.use_zone 的组合是我需要的:Time.use_zone(self.timezone) { Time.zone.local_to_utc(t) }.localtime
  • 您对 DST 过渡有何建议?假设要转换的目标时间在 D/ST 转换之后,而 Time.now 在更改之前。这行得通吗?
【解决方案2】:

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

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

这里是documentation on asctime

【讨论】:

  • 它符合我的预期
  • 这太好了,谢谢!在此基础上简化了my answerasctime 的一个缺点是它会丢弃任何亚秒级值(我的回答会保留)。
【解决方案3】:

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

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

【讨论】:

  • 要将 DateTime 对象转换回 TimeWithZone 对象,只需将 .in_time_zone 添加到末尾即可。
  • @Brian Murphy-Dye 我在使用此功能时遇到了夏令时问题。您可以编辑您的问题以提供适用于 DST 的解决方案吗?也许将Time.zone.now 替换为最接近您想要更改的时间的东西会起作用?
  • 我们使用了dt.change(zone: 'PST8PDT'),但这对我们有用。谢谢你,@Brian!
【解决方案4】:

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

最简单的方法是:

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

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

【讨论】:

  • 这对我有用。如果在使用时设置了偏移值,这应该在夏令时期间保持正确。如果使用 DateTime 对象,可以从中添加或减去“offset.seconds”。
  • 如果您不在 Rails 之外,您可以通过要求 active_support 的正确部分来使用 Time.in_time_zonerequire 'active_support/core_ext/time'
  • 这似乎做错了,这取决于你进入哪个方向,而且看起来并不总是可靠的。例如。如果我现在将斯德哥尔摩时间转换为伦敦时间,那么如果添加(而不是减去)偏移量,它就会起作用。但如果我把斯德哥尔摩换成赫尔辛基,加减都是错的。
【解决方案5】:

其实我觉得你转换后需要减去偏移量,如:

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 

【讨论】:

    【解决方案6】:

    取决于你打算在哪里使用这段时间。

    当你的时间是一种属性时

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

    【讨论】:

      【解决方案7】:
      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
      

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

      【讨论】:

        【解决方案8】:

        我创建了一些帮助方法,其中一个方法与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
        

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

        希望这会有所帮助。

        【讨论】:

          【解决方案9】:

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

          【讨论】:

          • 有时东部偏移量是-4,我想他们想在这两种情况下都自动处理。
          【解决方案10】:

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

          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”进行纳秒。如果您想要更高的精度,see this strftime reference

          【讨论】:

          • 即使我意识到我需要来自@jevon 的关键见解,而本页上关于 ActiveSupport 库的许多答案都缺少这种见解,但我仍然对 String 可能获得in_time_zone 方法,实际上它没有,至少对我来说:$ ruby -we 'require "active_support/core_ext/time"; puts(Time.now().strftime("%F %T.%N").in_time_zone("Europe/London"))' -e:1:in `&lt;main&gt;': undefined method `in_time_zone' for "2021-04-22 13:59:37.783521783":String (NoMethodError)
          • @MartinDorey 呵呵,这不是 Rails 做过的最糟糕的事情。当他们对 ERB 进行猴子补丁以处理 Rails HTML 安全时,我的想法令人难以置信。这段代码当然适用于我在 Rails 5.2 上,由 core_ext/string/zones 提供:Time.now.strftime("%F %T.%N").method(:in_time_zone) =&gt; #&lt;Method: String#in_time_zone(zone=...) …/myproject/gems/gems/activesupport-5.2.5/lib/active_support/core_ext/string/zones.rb:9&gt;
          【解决方案11】:

          这对我很有效

          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)
          

          【讨论】:

            【解决方案12】:

            问题是关于 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 说:

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

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2011-02-21
              • 2014-11-14
              • 1970-01-01
              • 2013-09-21
              • 1970-01-01
              • 2016-01-30
              • 2019-06-19
              • 1970-01-01
              相关资源
              最近更新 更多