Rails TimeZone
Ruby предлагает два класса для управления временем: Time
и DateTime
. TZInfo
является отдельной библиотекой часовых поясов, которая обеспечивает преобразования с учетом перехода на летнее время и включает в себя данные о 582 различных часовых поясах.
Часовой пояс в Rails
В Rails ActiveSupport::TimeZone
является оберткой для TZInfo
, предоставляющая набор из 146 зон с упрощенным названием, например Moscow
вместо Europe/Moscow
. Вместе с ActiveSupport::TimeWithZone
Rails представляет такой же API
как у Time
в Ruby.
Что посмотреть все доступные часовые пояса:
rails time:zones:all
Для проверки текущей зоны
> Time.zone
Установить часовой пояс (в консоли, временно)
# console rails c
Time.zone = 'Europe/Moscow'
Для определения зоны в приложении - config/application.rb
config.time_zone = 'Moscow'
Часовой пояс для пользователя
Временная зона по умолчанию в Rails - UTC. Лучшим решением для большинства приложений будет использовать UTC и разрешить каждому пользователю устанавливать свою персональную зону.
Для использования пользовательского часового пояса в ApplicationController
# app/controllers/application_controller.rb
around_action :set_time_zone, if: :current_user
private
def set_time_zone(&block)
Time.use_zone(current_user.time_zone, &block)
end
Для сохранения в часового пояса в профиле пользователя используем тип string
create_table :users do |t|
t.string :time_zone, default: "UTC"
...
end
Пользователь сможет выбрать и использовать свой часовой.
API
При работе с API лучше всего использовать стандарт ISO8601, представление даты и времени в виде строки. Преимущества ISO8601 в то, что строка однозначна, читаема, имеет широкую поддержку и легко сортируется.
> Time.now.utc.iso8601
=> "2019-03-15T12:03:06Z"
Знак Z
в конце указывает, что время представлено в UTC, а не в местном часовом поясе. Для преобразования в экземляр Time
> Time.iso8601("2019-03-15T12:03:06Z")
В приложении Rails есть три разных часовых пояса
- Системное время
- Время приложения
- Время базы данных
Допустим мы установили часовой пояс в Moscow
# это время на машине, "системное время"
> Time.now
=> 2019-03-15 12:07:37 +0000
# установим часовой пояс в Moscow
> Time.zone = 'Moscow'
=> "Moscow"
# Но мы опять получаем системное время
irb(main)> Time.now
=> 2019-03-15 12:09:23 +0000
# мы должны использовать `zone` чтобы получить время
# в часовом поясе
irb(main)> Time.zone.now
=> Fri, 15 Mar 2019 15:11:02 MSK +03:00
# такое же результат получим используя `current`
irb(main)> Time.current
=> Fri, 15 Mar 2019 15:20:58 MSK +03:00
# или если перевести системное время во время приложения
# используя `in_time_`
irb(main)> Time.now.in_time_zone
=> Fri, 15 Mar 2019 15:22:51 MSK +03:00
# используя Date мы опят получим системное время
irb(main)> Date.today
=> Fri, 15 Mar 2019
# а через `zone` время установленного часового пояса
irb(main)> Time.zone.today
=> Fri, 15 Mar 2019
# также и для "затра", чтобы получить корректный результат
# учитывающий часовой пояс
irb(main)> Time.zone.tomorrow
=> Sat, 16 Mar 2019
# при использовании Rails помощников мы также получаем корректный результат
irb(main)> 1.day.from_now
=> Sat, 16 Mar 2019 15:28:24 MSK +03:00
Запросы
Rails сохраняет временные метки в базе данных в UTC. Нужно всегда использовать Time.current
при любых запросах к базе данных, чтобы Rails переводил и сравнивал время правильно.
Post.where('published_at > ?', Time.current)
В итоге
- всегда работайте в
UTC
- используйте
Time.current
илиTime.zone.today
- используете
Time.current.today
- используйте
2.hours.ago
- Используйте
Date.current
- используйте
Time.zone.parse('2019-03-19 10:00:00')
- используйте
Time.strptime(string, '%Y-%m-%dT%H:%M:%Sz').in_time_zone
НЕ используйте
- Time.now, Date.today, Date.today.to_time, Time.parse, Time.strptime