Time.now против Time.zone.now в Rails 4 июня 10

Функции Time.now и Time.zone.now в Ruby on Rails практически идентичны. Возвращают одни и те же значения, ведут себя одинаково. Из-за этого легко перепутать и использовать более короткую формулировку Time.now. А зря – по крайней мере, если ты используешь часовой пояс, отличный от UTC.

В чем же разница между этими функциями? А вот в чем:

>> Time.now.class
=> Time
>> Time.now.to_s :db
=> "2010-06-04 19:00:00"
>> Time.zone.now.class
=> ActiveSupport::TimeWithZone
>> Time.zone.now.to_s :db
=> "2010-06-04 16:00:11"

Дело в том, что Rails, начиная с версии 2.1, хранят время в базе в UTC, а при сохранении и чтении переводят его из и в часовой пояс, используемый в приложении.

За перевод времени в UTC отвечает, как ни странно, метод to_s(:db). Точнее странно то, что, хоть он и доступен у объекта типа Time, работает он неверно и отдает время в текущем часовом поясе. Если использовать Time.now в условиях запроса – они сместятся на определенное количество часов. Если записывать Time.now в базу – в базу запишется неправильное значение, потому что при чтении рельсы лишний раз добавят к нему смещение часового пояса.

Короче, наиболее простым и адекватным решением этой проблемы будет замена по всему проекту Time.now на Time.zone.now.

…А еще из-за это проблемы не стоит использовать в запросах функцию MySQL NOW() и аналогичные, поскольку они ничего не знают о настройках рельсов.

Комментарии

  • FX Poster 5 июня 2010

    «NOW() и аналогичные» функции MySQL не стоит использовать еще и потому, что если в запросе есть эти функции – то результат запроса не кешируется.

    • Леонид Шевцов 5 июня 2010

      Паша, но если в запросе фигурирует текущее время, то как ты его не пиши, все равно его кешировать бессмысленно.

  • cmon_son 5 июня 2010

    FX Poster, видимо идет речь о вставке. что так кешировать?

    • Леонид Шевцов 5 июня 2010

      Нет, не только о вставке, а и о выборке. Например, выборке объектов, у которых не истек какой-нибудь срок. Вот в таком случае запрос с NOW() будет возвращать (во 2 часовом поясе) объекты, которые уже два часа как истекли.

  • Максим 5 июня 2010

    Я сейчас делаю сайт по теме планирования встреч (appointment scheduling) и мне пришлось вообще перекрыть метод to_s в классе ActiveSupport::TimeWithZone так, чтобы время в базе хранилось не в UTC. Требования таковы, что для каждого пользователя хранится часовой пояс и он должен иметь возможность его поменять в любой момент. При этом время для ранее забронированных встреч меняться не должно. Чуть позже хочу написать заметку про хранение времени в базе не в UTC для сайтов на Rails.

    • Леонид Шевцов 5 июня 2010

      Так можно же указывать часовой пояс для каждого пользователя непосредственно в рельсе.

      Мне кажется странным, что при смене часового пояса пользователем время встречи для него должно оставаться тем же. А как же другие участники той же встречи?

  • FX Poster 5 июня 2010

    Не, нифига чувак ты не прав. У тебя время будет с точностью до секунды, скорее всего. А за секунду тот же MySQL может много операций выполнить при хорошей нагрузке.

  • Максим 5 июня 2010

    Ну да, в before filter что-то типа Time.zone = @user.time_zone происходит.

    Это пока еще Minimal Viable Product, если пользоваться модной терминологией, нужна максимальная гибкость, а потом возможно будут изменения согласно пожеланиям пользователей. У встречи только два участника – специалист и его посетитель. На сайте планируется заранее время, когда посетитель попадет к специалисту.

Оставить комментарий

  • (или OpenID)
  •