新手问题 [求助] 关于 Rails 中奇怪的时区问题

wfwdex · 2017年05月11日 · 最后由 wfwdex 回复于 2017年05月13日 · 2562 次阅读

前提是我在application.rb中已经加入了:

config.time_zone = 'Beijing'
config.active_record.default_timezone = :local

我在 rails console 中查询用户的登录日志,返回的创建时间是: 2017-05-10 15:13:10

irb(main):001:0> User.find(2).user_signin_logs.order(created_at: :desc).first
  User Load (0.4ms)  SELECT  `users`.* FROM `users` WHERE `users`.`id` = 2 LIMIT 1
  UserSigninLog Load (0.6ms)  SELECT  `user_signin_logs`.* FROM `user_signin_logs` WHERE `user_signin_logs`.`user_id` = 2 ORDER BY `user_signin_logs`.`created_at` DESC LIMIT 1
=> #<UserSigninLog id: "2e20a942-3593-11e7-8338-00163e01107f", user_id: 2, ip: "27.17.98.36", ip_country: "中国", ip_province: "湖北", ip_city: "武汉", created_at: "2017-05-10 15:13:10", updated_at: "2017-05-10 15:13:10">

我用同样的 sql 语句直接去查数据库,得到的结果却是:2017-05-10 23:13:10

mysql> SELECT  `user_signin_logs`.* FROM `user_signin_logs` WHERE `user_signin_logs`.`user_id` = 2 ORDER BY `user_signin_logs`.`created_at` DESC LIMIT 1;

+--------------------------------------+---------+------------+-------------+-----------+--------------+---------------------+---------------------+
| id                                   | user_id |  ip           | ip_country | ip_province | ip_city | created_at          | updated_at          |
+--------------------------------------+---------+------------+-------------+-----------+--------------+---------------------+---------------------+
| 2e20a942-3593-11e7-8338-00163e01107f |       2  | 中国       | 湖北        | 武汉    |  2017-05-10 23:13:10 | 2017-05-10 23:13:10 |
+--------------------------------------+---------+------------+-------------+-----------+--------------+---------------------+---------------------+
1 row in set (0.02 sec)

看了 @boyishwei 的贴子 Rails 中的时区及时间问题 了解到created_at字段不管有没有设置 config.active_record.default_timezone = :local 都会记录 UTC 时间。

所以我的问题是,如何让created_at在 console 里默认就显示 localtime?而不是每次都要加.localtime.getlocal('+08:00')

多谢回复。

数据库里存的是 UTC 时间,只要设置了 config.time_zone = 'Beijing',ar 取出数据后就会自动转成 CST+8 的时间。TimeWithZone 和 Time 的用法是一样的,所以没必要非要显示 localtime。

adamshen 回复

可如我在 console 里查询的那样,结果里显示的依然是 UTC 时间:created_at: "2017-05-10 15:13:10" 并没有自动转成 CST+8

只有单独取这个字段的值的时候才会是 CST+8 时间。

wfwdex 回复

你在 console 里显示的结果只不过是调用 inspect 的结果,显示的其实是 ar 里 attribute_for_inspect(:created_at) 这个方法的结果,没有必要非要这里显示 cst+8 吧

你在编程的时候,调用 create_at 属性的结果是一个 TimeWithZone 的 instance。而你在命令行里看到的,是这个 instance 的 to_s(:db) 方法的结果。

你没有必要太在意 to_s(:db) 方法的输出,因为那个并不影响实际的值。

def attribute_for_inspect(attr_name)
  value = read_attribute(attr_name)

  if value.is_a?(String) && value.length > 50
    "#{value[0, 50]}...".inspect
  elsif value.is_a?(Date) || value.is_a?(Time)
    %("#{value.to_s(:db)}")
  else
    value.inspect
  end
end

强烈建议,应用层,数据库全部走 UTC

曾经踩过坑,跌得挺惨

hz_qiuyuanxin 回复

能说一下具体场景吗?如果不使用 UTC 的话,会在什么场景掉进坑里?

wfwdex 回复
  1. 如果面向全球的情况下,如果不统一为 UTC 后续基本玩不转,在新的服务器上面跑都得注意这一些事情
  2. APP 和数据库不统一的话,写代码都得时刻注意到底该用啥时区,这不是很累么
  3. 时区应该只是展示层的事情

像你这么配置,你很多时候弄不清楚目前究竟是啥时区,究竟应该用什么时区,换个人来维护更容易出错。 还有,你的这个配置在不同区域的服务器上面,由于你采用了 :locale 会使得你的代码行为不一致,这简直就是挖个坑自己跳进去。 万一错了,你还得搞清楚哪些数据错了,然后迁移数据。

如果统一成了 UTC,你根本就不需要考虑上面那么多问题,只需要考虑用户的输入的时候是什么时区,然后使用

param_time = Time.zone.parse(params[:time])

然后就没了,ActiveRecord 会自动帮你把时间转换成为配置的时区,ActiveRecord 的时区配置,与数据库一直这点很重要。

再加上 Rails app 的时区与这两个一致,我认为刚好就对上了 约定优于配置 的理念。

综上,建议

  1. Rails 的时区不需要配置了,默认就全是 UTC
  2. 数据库的时区也配置为 UTC
# config.time_zone = 'UTC'
# config.active_record.default_timezone = :utc

三楼的说法是对的,不过这样的配置数据库里存的并不是 utc 的值,还是北京时间。但是显示整个 Model object 的时候,因为会调用 TimeWithZone#to_s(:db), 所以会转成显示 utc 时间。当你实际实用的时候,比如User.find(2).user_signin_logs.order(created_at: :desc).first.created_at 这是显示的就是北京时间。

另外 @hz_qiuyuanxin 这样的配置其实是有个问题的,比如我用户都是国内的,就没必要 config.time_zone = 'UTC',那样配置的话显示的都是 utc 时间,不是本地时间。比如 ruby china 的配置就是 config.time_zone = 'Beijing'

nowherekai 回复

我上面写了一个观点:时区应该只是展示层的事情

hz_qiuyuanxin 回复

明白了,多谢,我下一步就去改成 UTC

10 楼 已删除
需要 登录 后方可回复, 如果你还没有账号请 注册新账号