首页 > 解决方案 > Rails 升级使默认时间格式发生变化。如何还原?

问题描述

我正在升级 Rails 应用程序

在各个步骤中。由于 Rails 升级需要 Postgres 升级,因此我无法以合理的方式将升级分开。

目前,我正在为 Rails 5.2 中处理“时间”对象的方式而苦苦挣扎。ActiveSupport::TimeWithZone即使数据库列没有时区,AR 对象中的“时间”列现在也以 . 形式返回。以前它是一个普通Time对象,具有不同的默认 JSON 表示。

这使得许多 API 测试失败,这些测试以前都返回 UTC 时间。

对象的 Rails 4.2、Ruby 2.2、PG 9.1 示例PhoneNumber

2.2.6 :002 > p.time_weekdays_from
  => 2000-01-01 07:00:00 UTC 
2.2.6 :003 > p.time_weekdays_from.class
  => Time 

Rails 5.2、Ruby 2.5、PG 10 的示例:

irb(main):016:0> p.time_weekdays_from
  => Sat, 01 Jan 2000 11:15:00 CET +01:00
irb(main):018:0> p.time_weekdays_from.class
  => ActiveSupport::TimeWithZone

我已经添加了一个初始化程序来暂时覆盖它,这似乎工作正常,但我仍然想了解为什么要进行此更改以及为什么即使“没有时区的时间”DB 列也被 Rails 视为如果他们有一个时区。

# This works, but why is it necessary?
module ActiveSupport
  class TimeWithZone
    def as_json(options = nil)
      self.utc.iso8601
    end
  end
end

PS:我并不总是想要 UTC,我只想要它用于 API,因为这是我们的 API 客户所期望的。

标签: ruby-on-railsrubypostgresql

解决方案


目前,我正在为 Rails 5.2 中处理“时间”对象的方式而苦苦挣扎。AR 对象中的“时间”列现在作为 ActiveSupport::TimeWithZone 返回,即使数据库列没有时区。以前它是一个普通的 Time 对象,具有不同的默认 JSON 表示。

尽管如此,我还是想了解为什么要进行这种更改,以及为什么即使是“没有时区的时间”数据库列也被 Rails 视为具有时区。

进行此更改是因为 Ruby 的默认设置Time不了解时区。ActiveSupport::TimeWithZone能够。这解决了处理时间、时区和数据库时的许多问题。

例如,假设您的应用程序的时区是America/Chicago. 以前,您必须决定是否要存储带时区或不带时区的时间。如果您选择没有时区,您将其存储为 UTC 还是 America/Chicago?如果您将其存储为 UTC,您是在加载时还是在显示时将其转换为美国/纽约?转换意味着从Time. 当您保存Time对象时,您必须小心记住Time转换到的时区并将其转换回数据库的时区。协调所有这些会导致许多错误。

Rails 5 引入了ActiveSupport::TimeWithZone. 这将时间存储为 UTC 和所需的时区来表示它。现在处理时间更简单:将其存储为 UTC(即。timestamp)并在加载时添加应用程序的时区。无需转换。Rails 会为您处理这个问题。

现在更改的是timestamp列,默认情况下,将按照应用程序的时区进行格式化。这需要一些时间来适应,但最终会使您对时间和时区的处理更加稳健。

> Time.zone.tzinfo.name
=> "America/Chicago"

> Time.zone.utc_offset
=> -21600

# Displayed in the application time zone
> Foo.last.created_at
=> Tue, 31 Dec 2019 17:16:14 CST -06:00

# Stored as UTC
> Foo.last.created_at.utc
=> 2019-12-31 23:16:14 UTC

如果您有手动进行时区转换的代码,请摆脱它。在UTC工作。时区现在只是格式化。

越多越好...

  • 使用对象,而不是字符串。
  • 在 UTC 中工作,时区用于格式化。
  • 如果您需要将时间转换为字符串,请明确格式化。
    def get_api_time
      '2000-01-01 07:00:00 UTC'
    end

    # bad: downgrading to strings, implicit formatting
    expected_time = Time.utc(2000, 1, 1, 7)
    expect( get_api_time ).to eq expected_time

    # good: upgrading to objects, format is irrelevant
    expected_time = Time.zone.parse('2000-01-01 07:00:00 UTC')
    expect(
      Time.zone.parse(get_api_time)
    ).to eq expected_time

    # better: refactor method to return ActiveSupport::TimeWithZone
    def get_api_time
      Time.zone.parse('2000-01-01 07:00:00 UTC')
    end

    expected_time = Time.zone.parse('2000-01-01 07:00:00 UTC')
    expect( get_api_time ).to eq expected_time

我建议阅读这些文章,它们可以解决问题。


推荐阅读