Time.now против Time.zone.now в Rails

04 июня 2010

Функции 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() и аналогичные, поскольку они ничего не знают о настройках рельсов.



Двадцать один комментарий. Напиши еще один
  1. 8cbd31fea81477c4b353eb32209df69f # 04 июня 2010 FX Poster (blog.fxposter.org) написал:

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

    1. 777894ea5153122bfa6b83f5bbf23622 # 05 июня 2010 Леонид Шевцов (автор) написал:

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

  2. Fa3475538ed10e6fc9309c97671750d5 # 05 июня 2010 cmon_son написал:

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

    1. 777894ea5153122bfa6b83f5bbf23622 # 05 июня 2010 Леонид Шевцов (автор) написал:

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

  3. D93971bdb81a7b2cd9f8eb4b5e1b507e # 05 июня 2010 Максим (maxnik.heroku.com) написал:

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

    1. 777894ea5153122bfa6b83f5bbf23622 # 05 июня 2010 Леонид Шевцов (автор) написал:

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

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

  4. 8cbd31fea81477c4b353eb32209df69f # 05 июня 2010 FX Poster (blog.fxposter.org) написал:

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

  5. D93971bdb81a7b2cd9f8eb4b5e1b507e # 05 июня 2010 Максим (maxnik.heroku.com) написал:

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

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

  6. Cd1f784997dd76c27fa3d64279d7dcdc # 21 декабря 2010 SuddenHead написал:

    Спасибо, пригодилось

  7. Ab074ca0a48f06ab110c374fc7db258e # 08 февраля 2011 Kir (iempire.ru) написал:

    А где в конфигах Rails можно установить часовой пояс, который хочу использовать?
    Ибо серверное время отличается от того пояса, который мне нужен (+1)

    1. 777894ea5153122bfa6b83f5bbf23622 # 09 февраля 2011 Леонид Шевцов (автор) написал:

      В Rails 2.3 – config/environment.rb, в Rails 3 – config/application.rb

      config.time_zone = 'UTC +1:00'
      
      1. Ab074ca0a48f06ab110c374fc7db258e # 09 февраля 2011 Kir (iempire.ru) написал:

        Не прокатило =(
        Value assigned to config.time_zone not recognized.Run «rake -D time» for a list of tasks for finding appropriate time zone names.

        Rails 3.0.3

        1. 777894ea5153122bfa6b83f5bbf23622 # 09 февраля 2011 Леонид Шевцов (автор) написал:

          Тогда список доступных временных зон можно посмотреть командой rake time:zones:all, что и предлагается. :)

          1. Ab074ca0a48f06ab110c374fc7db258e # 09 февраля 2011 Kir (iempire.ru) написал:

            Да я посмотрел, и там точь-в-точь ‘UTC +01:00’ только с ноликом, как в вашем ответе!
            А рельсы все равно ошибку выкатывают…

            1. 777894ea5153122bfa6b83f5bbf23622 # 09 февраля 2011 Леонид Шевцов (автор) написал:

              Я думаю, все-таки надо написать одну из зон, которые под UTC +01:00:

              Amsterdam
              Belgrade
              Berlin
              Bern
              Bratislava
              Brussels
              Budapest
              Copenhagen
              Ljubljana
              Madrid
              Paris
              Prague
              Rome
              Sarajevo
              Skopje
              Stockholm
              Vienna
              Warsaw
              West Central Africa
              Zagreb

              1. Ab074ca0a48f06ab110c374fc7db258e # 09 февраля 2011 Kir (iempire.ru) написал:

                config.time_zone = "Prague" – так заработало!

                А rake time:zones:all названия городов не выводил :( Спасибо!

  8. F91efda1043d10f4b4aee0a156e2cfd1 # 27 июня 2011 Nakilon написал:

    А как же Time.now.utc ?

    1. 777894ea5153122bfa6b83f5bbf23622 # 27 июня 2011 Леонид Шевцов (автор) написал:

      Можно, конечно, и Time.now.utc. Но по-моему это неправильно, поскольку вносит в код лишнюю зависимость – знание часового пояса, используемого БД.

  9. 8305a5337ead32f1d78011e26ee0d19b # 31 октября 2011 petRUShka написал:

    Спасибо за разъяснение, меня всегда бесило странное поведение Time.now.

    Но вообще это странно. Это больше похоже на баг.

  10. 732b8fdb2beaea91d49f2e305877879a # 28 декабря 2011 BatMan| написал:

    статья потеряла актуальность?
    удалите если так
    Time.zone.now
    => Wed, 28 Dec 2011 14:36:52 YEKT +06:00
    ruby-1.9.2-p290 :048 > Time.now
    => 2011-12-28 08:36:58 +0000
    ruby-1.9.2-p290 :049 > Time.zone.now.to_s :db
    => «2011-12-28 08:37:07»
    ruby-1.9.2-p290 :050 > Time.now.to_s :db
    => «2011-12-28 08:37:18»
    ruby-1.9.2-p290 :051 >

  11. 732b8fdb2beaea91d49f2e305877879a # 28 декабря 2011 BatMan| написал:

    Time.now.utc.to_s :db
    => «2011-12-28 08:40:57»

(нужна разметка?)

  • **жирный**
  • > цитата

отменить