【问题标题】:Rails/Ruby: TimeWithZone comparison inexplicably failing for equivalent valuesRails/Ruby:TimeWithZone 比较对于等效值莫名其妙地失败
【发布时间】:2014-08-01 10:58:41
【问题描述】:

在我当前的项目中比较 DateTime,特别是比较 ActiveSupport::TimeWithZone 的两个实例时,我遇到了一个糟糕的时间(不是双关语)。问题是我的两个 TimeWithZone 实例具有相同的值,但所有比较都表明它们不同。

在执行期间暂停调试(使用RubyMine),我可以看到以下信息:

timestamp = {ActiveSupport::TimeWithZone} 2014-08-01 10:33:36 UTC
started_at = {ActiveSupport::TimeWithZone} 2014-08-01 10:33:36 UTC

timestamp.inspect = "Fri, 01 Aug 2014 10:33:36 UTC +00:00"
started_at.inspect = "Fri, 01 Aug 2014 10:33:36 UTC +00:00"

但比较表明值不相等:

timestamp <=> started_at = -1

我在搜索中找到的最接近的答案 (Comparison between two ActiveSupport::TimeWithZone objects fails) 在这里表明了同样的问题,我尝试了适用的解决方案但没有成功(尝试了 db:test:prepare 并且我没有运行 Spring)。

此外,即使我尝试转换为显式类型,它们在比较时仍然不等价。

到时间:

timestamp.to_time = {Time} 2014-08-01 03:33:36 -0700
started_at.to_time = {Time} 2014-08-01 03:33:36 -0700

timestamp.to_time <=> started_at.to_time = -1

to_datetime:

timestamp.to_datetime = {Time} 2014-08-01 03:33:36 -0700
started_at.to_datetime = {Time} 2014-08-01 03:33:36 -0700

timestamp.to_datetime <=> started_at.to_datetime = -1    

到目前为止,我发现的唯一“解决方案”是使用to_i 转换这两个值,然后进行比较,但这对于我希望进行比较的任何地方的代码来说都非常尴尬(此外,看起来应该是不必要的):

timestamp.to_i = 1406889216
started_at.to_i = 1406889216

timestamp.to_i <=> started_at.to_i = 0

非常感谢任何建议!

【问题讨论】:

  • 我强烈怀疑这些值在几分之一秒内不同。尝试使用to_f 而不是to_i,您可能会看到不同之处。或者将它们格式化为包含几分之一秒。
  • @JonSkeet 你明白了,非常感谢!我在对原始帖子的编辑中详细说明了细节,但长话短说是从数据库中检索一个值,另一个在内存中,由于我在 MySQL 上运行,毫秒数据只被截断了一个值,但是不是两者。
  • @GabeStah 你应该发布你的解决方案作为答案

标签: ruby-on-rails ruby datetime comparison


【解决方案1】:

已解决

正如上面 Jon Skeet 所指出的,由于时间上隐藏的毫秒差异,比较失败了:

timestamp.strftime('%Y-%m-%d %H:%M:%S.%L') = "2014-08-02 10:23:17.000"
started_at.strftime('%Y-%m-%d %H:%M:%S.%L') = "2014-08-02 10:23:17.679"

这一发现使我走上了一条奇怪的道路,最终发现了最终导致问题的原因。这是在测试期间和使用 MySQL 作为我的数据库时发生的此问题的组合。

问题仅在测试中显示,因为在出​​现此问题的测试中,我正在针对包含上述字段的几个相关模型运行一些测试。在测试期间必须将一个模型的实例保存到数据库中——包含timestamp 值的模型。然而,另一个模型正在执行处理,因此会自引用在测试代码中创建的自身实例。

这导致了第二个罪魁祸首,即我使用 MySQL 作为数据库,在存储 datetime 值时,存储毫秒信息(不像 PostgreSQL) .

这意味着timestamp 变量在从 MySQL 数据库中检索到其 ActiveRecord 后被读取,实际上被舍入并剔除了毫秒数据,而started_at 变量只是保留在内存中在测试期间,因此原始毫秒仍然存在。

我自己的(低于标准的)解决方案基本上是强制我的测试中的两个模型(而不仅仅是一个)从数据库中检索自己。

TLDR;如果可能,请尽可能使用 PostgreSQL!

【讨论】:

    【解决方案2】:

    如果您将 Ruby 中生成的时间与从数据库加载的时间进行比较,这似乎会发生。

    例如:

    time = Time.zone.now
    Record.create!(mark: time)
    record = Record.last
    
    

    在这种情况下,record.mark == time 将失败,因为 Ruby 将时间缩短到纳秒,而不同的数据库具有不同的精度。

    如果是 postgres DateTime 类型,则为毫秒。

    当您检查时,您可以看到 record.mark.sec == time.msec - record.mark.nsec != time.nsec

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-03-12
      • 2013-05-08
      • 1970-01-01
      • 1970-01-01
      • 2012-05-19
      相关资源
      最近更新 更多