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