【问题标题】:Ruby on Rails time zone strange behaviourRuby on Rails 时区奇怪的行为
【发布时间】:2013-09-08 00:07:04
【问题描述】:

有属性whenduration的任务模型。

create_table "tasks", force: true do |t|
  ...
  t.datetime "when"
  t.integer  "duration"
  ...
end

我编写了检查任务是否处于活动状态的方法,以便我可以在页面上显示它。 这是主动方法:

  def active?
    if (self.when + self.duration) > Time.now
      true
    end
  end

我尝试在控制台中检查对象:

t.when + t.duration
=> Sun, 08 Sep 2013 01:01:00 UTC +00:00
DateTime.now
=> Sun, 08 Sep 2013 01:57:13 +0200
t.active?
=> true

它是true,但我为duration 输入了 1:00 时间和 1 分钟,我希望它不应该是真的。

似乎数据库中的when 列没有保存在正确的时区,所以它给出了不正确的结果。如何解决这个问题?

【问题讨论】:

  • 您是如何在duration 中存储“1 分钟”的?
  • 我展示了与这些字段相关的迁移,它以整数形式存储,因此为 60。
  • 当您将60 添加到DateTime 时会发生什么?只需在控制台中快速输入DateTime.now + 60,然后查看+ 60 对时间戳的作用。
  • [7] pry(main)> DateTime.now => Sun, 08 Sep 2013 19:52:51 +0200 [8] pry(main)> DateTime.now + 60 => Thu, 07 Nov 2013 19:52:59 +0200 但这与我的问题无关。看一下:[9] pry(main)> Task.last.when => Sun, 08 Sep 2013 16:58:00 UTC +00:00 [10] pry(main)> Task.last.when + Task.last.duration => Sun, 08 Sep 2013 16:59:00 UTC +00:00 因为when列是TimeWithZone,看一下:[11] pry(main)> Task.last.when.class => ActiveSupport::TimeWithZone

标签: ruby-on-rails ruby timezone


【解决方案1】:

似乎当数据库中的列没有保存在正确的时区时

1) Rails 在将时间插入数据库之前会自动将其转换为 UTC 时间(这是一个的事情),这意味着时间的偏移量为“+ 0000”。这意味着如果您将晚上 8 点的时间保存到数据库中,并且您的服务器位于偏移量为“+0600”的时区,那么等效的 UTC 时间是下午 2 点,因此下午 2 点会保存在数据库中。换句话说,您本地服务器的时间比 UTC 时间早 6 小时,这意味着当您服务器所在时区的晚上 8 点时,UTC 时区的下午 2 点。

2) 当您比较日期时,ruby 会考虑时区偏移量——换句话说,ruby 会将所有时间转换为同一时区,然后比较时间。这是一个例子:

2.0.0p247 :086 > x = DateTime.strptime('28-01-2013 08:00:00 PM +6', '%d-%m-%Y %I:%M:%S %p %z')
 => Mon, 28 Jan 2013 20:00:00 +0600 
2.0.0p247 :087 > y = DateTime.strptime('28-01-2013 08:20:00 PM +7', '%d-%m-%Y %I:%M:%S %p %z')
 => Mon, 28 Jan 2013 20:20:00 +0700 
2.0.0p247 :088 > x < y
 => false 

如果只是比较两个 Datetime 对象的时间,x 小于 y。但是,y 在偏移量为 +7 的时区中的时间为晚上 8:20,这相当于在偏移量为 +6 的时区中的时间晚上 7:20。因此,y 实际上小于 x。您需要将苹果与苹果进行比较,这意味着您需要在心理上比较已转换为同一时区的时间,以获得与 ruby​​/rails 产生的相同结果。

3) 您可以使用 rails utc() 方法将 Time.now 转换为 UTC 时间:

2.0.0p247 :089 > x = Time.now
 => 2013-09-07 8:00:00 +0600 
2.0.0p247 :090 > x.utc
 => 2013-09-07 02:00:00 UTC 

这就是 ruby​​ 在将 Time.now 与 task.when + task.duration 进行比较之前所做的事情

4) 您可能会发现使用您想要使用的时间创建 DateTime 对象更方便:

DateTime.strptime('28-01-2013 08:00:00 PM +0', '%d-%m-%Y %I:%M:%S %p %z'

由于您可以将偏移量指定为零,因此您不必创建预期转换为 UTC 时间的时间。

或者你可以使用change()方法,它会导致offset()改变而不转换时间:

2.0.0p247 :011 > x = DateTime.now
 => Sun, 08 Sep 2013 00:34:08 +0600 
2.0.0p247 :012 > x.change offset: "+0000"
 => Sun, 08 Sep 2013 00:34:08 +0000 

【讨论】:

  • 您推荐什么解决方案?我想知道为什么默认情况下它不起作用。我想知道当我迁移到另一台服务器(例如 Heroku)时是否会更改默认时区,如果它可以工作。
  • 一切正常,只是你不明白事情是如何工作的。例如,你会添加 1/8 + 1/4 并得到 2/8。不,你必须在相加之前将 1/8 和 1/4 转换为公分母,所以你这样做 1/8 + 2/8 = 3/8。时代也是如此。在比较时间之前,rails 会将所有时间转换为公分母 UTC。但你并没有在你的时代精神上这样做。例如,您有一个任务,开始时间为晚上 8 点,偏移量为“+0000”,并且您认为晚上 9 点的时间和“+0200”的偏移量应该大于晚上 8 点的时间——但您忽略了偏移量。
  • 要正确比较时间,您必须考虑偏移量,rails 就是这样做的。写Time.now.utc真的有那么难吗?
  • 又是一样的。我不明白的是,当我尝试保存 21:00 UTC+2 时,它应该像 19:00 UTC+0 一样保存在数据库中,但它不是这样保存的。数据库中的值为 21:00 UTC+0。
【解决方案2】:

ActiveRecord 默认以 UTC 存储时间戳。更改默认时区见How to change default timezone for Active Record in Rails?

您也可以只使用 Time#in_time_zone 将 t.when 转换为您的时区,请参阅http://api.rubyonrails.org/classes/ActiveSupport/TimeWithZone.html

【讨论】:

    猜你喜欢
    • 2015-07-25
    • 1970-01-01
    • 1970-01-01
    • 2016-01-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多