【问题标题】:Active Record transforming dates inconsistently when querying a postgresql view查询 postgresql 视图时 Active Record 转换日期不一致
【发布时间】:2021-12-03 03:36:34
【问题描述】:

我有一个 Rails 应用程序正在创建日历事件的视图,其中一些存储为带有 starts_at 列的日历事件,其中一些具有从重复计划创建的生成的 starts_at 列。

视图是一个联合,看起来像这样(简化):

(
  SELECT
    'appointment' AS event_type,
    starts_at
  FROM
    appointments
)
UNION
(
  SELECT
    'schedule' AS event_type,
    (
      to_timestamp(
        CONCAT(
          start_date,
          ' ',
          lpad(start_hour :: text, 2, '0'),
          ':',
          lpad(start_minute :: text, 2, '0'),
          ':00.000000'
        ),
        'YYYY-MM-DD hh24:mi:ss:us'
      ) at time zone 'UTC'
    ) :: timestamp without time zone AS starts_at
  FROM
    schedule_items
)

这很好用,当我在 postgres 中查询视图时,我得到:

 event_type  |          ends_at
-------------+----------------------------
 schedule    | 2021-10-18 08:00:00
 schedule    | 2021-11-08 09:00:00
 appointment | 2021-10-14 17:44:15.122543

这些都是正确的 UTC 时间,而不是本地时区。

我在这个视图周围包裹了一个 ActiveRecord 模型(使用 scenic gem 生成视图)但是当我查询模型时,它为 appointment 记录提供了正确的时间,但为 schedule 提供了不正确的时间(生成的)记录。

约会显示在上面的 UTC 时间(当前比英国当地时区晚 1 小时)。

计划时间显示在上述时间加上 1 小时(以 UTC 表示),因此当 Active Record 投射时比 UTC 提前 2 小时。

如果我为属性构建自定义cast_type,我可以看到它在上面第一次读取为2021-10-18 08:00:00 UTC(有效地将其转换为当地时间但标记为UTC)但对于约会记录,它正在正确读取它作为2021-10-14 17:44:15.122566 UTC

如果我在 Active Record 中使用基本 SQL 查询,我会得到以下结果:

irb(main):045:0> r = ActiveRecord::Base.connection.execute('select starts_at from calendar_events')
irb(main):045:0> r[0]
=> {"starts_at"=>2021-10-18 09:00:00.000000 UTC}
irb(main):046:0> r[2]
=> {"starts_at"=>2021-10-14 17:44:15.122543 UTC}

这表明第一条记录的时间解析错误,最后一条记录正确。

如果我在本地使用 pg gem,我会得到与使用 sql 查询相同的结果:

irb(main):001:0> conn = PG.connect( dbname: 'my_db' )
=> #<PG::Connection:0x00000001222d3a48>
irb(main):002:1* conn.exec('select * from calendar_events') do |result|
irb(main):003:2*   result.each do |row|
irb(main):004:2*     puts row['starts_at']
irb(main):005:1*   end
irb(main):006:0> end
2021-10-14 17:44:15.122543
2021-11-08 09:00:00
2021-10-18 08:00:00

这显示了我期望的结果。

日历表中的日期时间列类型与用于在视图中创建日期时间的类型相同,例如

starts_at timestamp without time zone

rails 应用程序和 Postgres 数据库都设置为 Europe/London 时区,所有时间戳都以 timestamp without time zone 类型写入数据库,其值在 UTC 中。

我尝试了多种方法来解决它(例如,为模型上的属性创建自定义 cast_type,添加 self.skip_time_zone_conversion_for_attributesattribute_before_type_cast['starts_at'])这些都没有解决 ActiveRecord 似乎正在转换某些日期的问题UTC 到本地时间,但仍将它们标记为 UTC。

所以我有点茫然,所以任何人的任何建议都将不胜感激!

【问题讨论】:

  • 如果我在数据库中将时间戳格式化为 ISO8601 字符串而不是实际时间戳,则更令人困惑的是:2021-10-18T08:00:00.000000Z 当它被加载到 ActiveRecord 中时,它会在转换之前转换为 2021-10-18T09:00:00.000000Z。使用pg gem 直接进行查询,通过 ActiveRecord 进行查询会奇怪地转换字符串(与转换日期的方式相同)。就像它正在对它认为是日期时间的字符串进行某种本地时间转换......
  • 另外,如果您认为这是一个错误,您可以填写此模板并在 Rails github 问题跟踪器上提交错误github.com/rails/rails/blob/main/guides/bug_report_templates/…
  • @Eyeslandic 如果我加上时区,它有趣地相似 - 数据库有 2021-11-08 09:00:00+00 (这实际上是错误的,因为我们希望它是 08:00)并且当 ActiveRecord 解析它时它至少有相同的值:2021-10-18 09:00:00 +0000 并呈现为 Mon, 18 Oct 2021 10:00:00.000000000 BST +01:00。我真的不知道这是一个错误,还是与我在 Postgres 中的演员表和时区有关,所以不确定它是否属于错误,因为我确定我一定错过了一些重要的东西!
  • @Eyeslandic 我做了更多的测试,我更确信它毕竟是 ActiveRecord 中的一个错误。我创建了一个测试脚本并在任何情况下都将其作为错误提交:github.com/rails/rails/issues/43467。我怀疑它与 psql 中的时区设置有关。在这种情况下,带有生成时间戳的奇怪视图似乎是唯一的问题。

标签: ruby-on-rails postgresql datetime view rails-activerecord


【解决方案1】:

好的,如果其他人遇到这个问题,这里的解决方案是:

  1. 确保 postgresql.conf 中的时区设置为“Etc/UTC”(不是安装程序可能默认使用的本地时区 - 例如,在 OSX 上使用 homebrew 安装默认为本地 TZ)
  2. 在视图中生成日期时,请使用以下类型的转换:
SELECT
  (
    to_timestamp(
      '2021-11-18 09:00:00', 'YYYY-MM-DD HH24::m1:ss'
    ) at time zone 'UTC' at time zone 'Europe/London'
  ) :: timestamp without time zone as starts_at;

然后 ActiveRecord 会按照您的预期对所有内容进行排序。显然,在这种情况下,postgres 上的时区设置似乎确实与 ActiveRecord 交互。

psql 中执行原始查询会使数据看起来是相同的,但使用 UTC 的详细会话时区从 ActiveRecord 读取时显然不完全相同。

时区确实很难。如果我的本地机器设置为“Etc/UTC”,就像我所有的生产系统一样,那么就不会有问题。

因此,如果您确实在本地安装 postgres 以进行 Rails 开发并希望在视图中使用生成的日期,那么确保将时区设置为“Etc/UTC”很重要。

这确实很难解决,但要感谢@eyeslandic,他提出了有关 Rails 的错误报告,然后我创建了一个测试脚本来演示该问题。

一旦我有了正确的设置组合,就很容易使用该脚本对其进行验证。否则很难深究。

【讨论】:

  • 这些错误报告模板在调试边缘情况时非常有用。很高兴你解决了。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-03-05
  • 1970-01-01
  • 2021-07-05
相关资源
最近更新 更多