ruby-on-rails - Rails 升级使默认时间格式发生变化。如何还原?
问题描述
我正在升级 Rails 应用程序
- Rails 4.2 -> 5.2(后续升级到 Rails 6 待定)
- 红宝石 2.2 -> 2.5
- Postgres 9.1 -> 10
在各个步骤中。由于 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 客户所期望的。
解决方案
目前,我正在为 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
我建议阅读这些文章,它们可以解决问题。
推荐阅读
- base64 - 需要帮助在 powershell 脚本中解码 base64 压缩字符串
- bash - bash 不能使用 Docker 的 SHELL 指令
- google-bigquery - 如何获取支付页面中拥有特定产品的用户数量
- python - FTP 下载的数据包含文字 unicode,例如 \x00 和 \n
- android - 单击按钮时重置/重新启动片段中的所有内容
- tcl - 在 TCL 构建脚本中设置 mem 配置选项
- javascript - 如何制作可点击的按钮?
- python - 如何以列格式打印斐波那契数
- python - 访问接收到的 TRAP 的 varBinds
- java - 如何为 Java spring 项目正确简单地设置 Gitlab-CI/CD