Rails/Ruby: сравнение TimeWithZone необъяснимо не соответствует эквивалентным значениям

У меня есть ужасное время (без каламбура) с сопоставлением 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

Самый близкий ответ, который я нашел в поиске (Сравнение двух объектов ActiveSupport:: TimeWithZone не работает) указывает на ту же проблему здесь, и я пробовал решения, которые были применимы без любой успех (попробованный db: test: подготовьте, и я не запустил Spring).

Кроме того, даже если я пытаюсь преобразовать в явные типы, они все равно не эквивалентны при сравнении.

TO_TIME:

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

Любые советы будут очень оценены!

Ответ 1

решаемые

Как показал Джон Скит выше, сравнение не удавалось из-за скрытых миллисекундных различий во времени:

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, которая была прочитана после того, как ее ActiveRecord была извлечена из базы данных MySQL, была эффективно округлена и выбрита миллисекундных данных, тогда как переменная started_at была просто сохранена в памяти во время тестирования и, таким образом, исходные миллисекунды все еще присутствовали.

Мое собственное (субпаративное) решение состоит в том, чтобы по существу заставить обе модели (а не только одну) в моем тесте извлечь из базы данных.

TL;DR; Если это вообще возможно, используйте 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