LoginSignup
0
0

More than 3 years have passed since last update.

Rubyでナノ秒、PostgreSQLだとマイクロ秒

Last updated at Posted at 2020-03-28

環境: circleci/ruby:2.4.2-stretch-node-browsers, circleci/postgres:10.12

とあるアプリケーションで、新しくCircleCIを設定してRspecを動かしてみたら、エラーが大量に出ました。

expected: 2020-03-25 12:36:20.477148202 +0900
got: 2020-03-25 12:36:20.477148000 +0900

Rubyはナノ秒(小数点以下9桁)まで時刻を保持し、PostgreSQLはマイクロ秒(小数点以下6桁)までしか保持しないので、比較が失敗しまくっていると判明。ひえー、どうすんのこれ。助けて!

用語

参照: Orders_of_magnitude_(time)

名前 単位
ミリ秒 10-3 0.123
マイクロ秒 10-6 0.123456
ナノ秒 10-9 0.123456789

以下、3桁ずつピコ 10-12、フェムト 10-15、アット 10-18、ゼプト 10-21、ヨクト 10-24と続きます。

Macでは

Mac(rbenvでインストール)では、RubyのTime#nsecはナノ秒(9桁)を表しますが、実際はマイクロ秒(6桁)で切り取られるので、Postgresとの違いに気づきませんでした。

t = Time.now
t.to_f #=> 1585394326.856343
t.nsec #=> 856343000

Ubuntuだとナノ秒までしっかり入ります。

t = Time.now
t.to_f #=> 1585394640.8082483
t.nsec #=> 808248257

対策

Time#<=> を上書きしてミリ秒で比較することでエラーを減らすことができました。<=> を上書きするだけで、== も != も < も対策できます。Time#<=> を上書きすると、ActiveSupport::TimeWithZone の比較にも使われます。

マイクロ秒で比較(floor(6))ではうまく行きませんでした。

spec/supports/time_patch_helper.rb
if ENV['CIRCLECI']
  class Time
    alias_method :old_cmp, :<=>
    def <=>(val)
      if val.try(:acts_like_time?)
        self.to_f.floor(3) <=> val.to_f.floor(3)
      else
        old_cmp(val)
      end
    end
  end
end

ちなみにRubyには時刻の小数点以下を丸めるメソッド Time#round があり、Ruby 2.7 では切り落としを行う Time#floorが追加されています。

今後やりたいこと

  • 上記の対策よりもうまい方法を考える。
  • Rubyのソースコードをチェックしたり、Linuxでコンパイルしてみたりする(たぶんC言語の#defineに違いがある)。
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0