Кто-нибудь знает, как правильно обращаться с часовыми поясами пользователей в рельсах 2.3? - PullRequest
7 голосов
/ 25 декабря 2010

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

Может кто-нибудь подсказать мне, как работать с пользовательскими часовыми поясами в rails 2.3 (.5или .8)

Самая содержательная статья, которую я видел, подробно рассказывающая о том, как пользовательские часовые пояса должны работать, находится здесь: http://wiki.rubyonrails.org/howtos/time-zones..., хотя неясно, когда это было написано или для какой версии рельсов,В частности, в нем говорится:

"Time.zone - часовой пояс, который фактически используется для отображения. Он может быть установлен вручную для переопределения config.time_zone для каждого запроса."

Используются ключевые термины «цели отображения» и «на основе запроса».

Локально на моей машине это действительно так.Однако на производстве, ни один из них не соответствует действительности.Настройка Time.zone сохраняется после окончания запроса (ко всем последующим запросам), а также влияет на способ сохранения AR в БД (в основном обрабатывая любую дату, как если бы она уже была в UTC, даже если ее нет), сохраняя тем самым совершенно неподходящие значения.

Мы запускаем Ruby Enterprise Edition на производстве вместе с пассажирами.Если это моя проблема, нам нужно переключиться на JRuby или что-то еще?

Чтобы проиллюстрировать проблему, я поместил следующие действия в мой ApplicationController прямо сейчас:

def test
p_time = Time.now.utc
s_time = Time.utc(p_time.year, p_time.month, p_time.day, p_time.hour)

logger.error "TIME.ZONE" + Time.zone.inspect
logger.error ENV['TZ'].inspect
logger.error p_time.inspect
logger.error s_time.inspect

jl = JunkLead.create!
jl.date_at = s_time

logger.error s_time.inspect
logger.error jl.date_at.inspect

jl.save!

logger.error s_time.inspect
logger.error jl.date_at.inspect


render :nothing => true, :status => 200
end


def test2
Time.zone = 'Mountain Time (US & Canada)'
logger.error "TIME.ZONE" + Time.zone.inspect
logger.error ENV['TZ'].inspect

render :nothing => true, :status => 200
end


def test3
Time.zone = 'UTC'
logger.error "TIME.ZONE" + Time.zone.inspect
logger.error ENV['TZ'].inspect


render :nothing => true, :status => 200
end

и они даютследующее:

Processing ApplicationController#test (for 98.202.196.203 at 2010-12-24 22:15:50) [GET]
TIME.ZONE#<ActiveSupport::TimeZone:0x2c57a68 @tzinfo=#<TZInfo::DataTimezone: Etc/UTC>, @name="UTC", @utc_offset=0>
nil
Fri Dec 24 22:15:50 UTC 2010
Fri Dec 24 22:00:00 UTC 2010
Fri Dec 24 22:00:00 UTC 2010
Fri, 24 Dec 2010 22:00:00 UTC +00:00
Fri Dec 24 22:00:00 UTC 2010
Fri, 24 Dec 2010 22:00:00 UTC +00:00
Completed in 21ms (View: 0, DB: 4) | 200 OK [http://www.dealsthatmatter.com/test]


Processing ApplicationController#test2 (for 98.202.196.203 at 2010-12-24 22:15:53) [GET]
TIME.ZONE#<ActiveSupport::TimeZone:0x2c580a8 @tzinfo=#<TZInfo::DataTimezone: America/Denver>, @name="Mountain Time (US & Canada)", @utc_offset=-25200>
nil
Completed in 143ms (View: 1, DB: 3) | 200 OK [http://www.dealsthatmatter.com/test2]


Processing ApplicationController#test (for 98.202.196.203 at 2010-12-24 22:15:59) [GET]
TIME.ZONE#<ActiveSupport::TimeZone:0x2c580a8 @tzinfo=#<TZInfo::DataTimezone: America/Denver>, @name="Mountain Time (US & Canada)", @utc_offset=-25200>
nil
Fri Dec 24 22:15:59 UTC 2010
Fri Dec 24 22:00:00 UTC 2010
Fri Dec 24 22:00:00 UTC 2010
Fri, 24 Dec 2010 15:00:00 MST -07:00
Fri Dec 24 22:00:00 UTC 2010
Fri, 24 Dec 2010 15:00:00 MST -07:00
Completed in 20ms (View: 0, DB: 4) | 200 OK [http://www.dealsthatmatter.com/test]

Processing ApplicationController#test3 (for 98.202.196.203 at 2010-12-24 22:16:03) [GET]
TIME.ZONE#<ActiveSupport::TimeZone:0x2c57a68 @tzinfo=#<TZInfo::DataTimezone: Etc/UTC>, @name="UTC", @utc_offset=0>
nil
Completed in 17ms (View: 0, DB: 2) | 200 OK [http://www.dealsthatmatter.com/test3]

Processing ApplicationController#test (for 98.202.196.203 at 2010-12-24 22:16:04) [GET]
TIME.ZONE#<ActiveSupport::TimeZone:0x2c57a68 @tzinfo=#<TZInfo::DataTimezone: Etc/UTC>, @name="UTC", @utc_offset=0>
nil
Fri Dec 24 22:16:05 UTC 2010
Fri Dec 24 22:00:00 UTC 2010
Fri Dec 24 22:00:00 UTC 2010
Fri, 24 Dec 2010 22:00:00 UTC +00:00
Fri Dec 24 22:00:00 UTC 2010
Fri, 24 Dec 2010 22:00:00 UTC +00:00
Completed in 151ms (View: 0, DB: 4) | 200 OK [http://www.dealsthatmatter.com/test]

Выше должно быть ясно, что второй вызов / test показывает, что для Time.zone установлено значение Mountain, хотя это не должно быть.

Кроме того,проверка базы данных показывает, что действие test при запуске после test2 сохранило запись JunkLead с датой 2010-12-22 15:00:00, что явно неверно.

Ответы [ 3 ]

9 голосов
/ 03 января 2011

После исчерпывающих исследований теперь совершенно ясно, что Time.zone не работает в большинстве всех версий Rails (включая 2.3 и 3).Эта функция использует хэш центрального потока для хранения установленного значения (которое, как предполагается, является потокобезопасным и не является) и в конечном итоге изменяет поведение для всех последующих запросов.Кроме того, в отличие от документации, настройка Time.zone изменяет поведение ActiveRecord и сохраняет дату и время в новой зоне вместо той, которая указана в config (обычно это UTC).

Пока Rails не исправит это, мы выбрали вместо этогодля работы с часовыми поясами вручную, доступ к которым можно получить с помощью недокументированного метода по умолчанию:

ActiveSupport::TimeZone['Arizona'].now (or .local, or .parse).

Кроме того, я установил патч Time и ActiveSupport :: TimeWithZone, чтобы обеспечить простое преобразование момента времени в другую зону.Чтобы было ясно, я имею в виду соответствующий момент времени в другой зоне, а не одновременный момент.

>> Time.utc(2011)
=> Sat Jan 01 00:00:00 UTC 2011
>> Time.utc(2011).in_time_zone('Arizona')
=> Fri, 31 Dec 2010 17:00:00 MST -07:00 #Simultaneous
>> Time.utc(2011).change_zone('Arizona')
=> Sat, 01 Jan 2011 00:00:00 MST -07:00 #Corresponding

Патч выглядит следующим образом:

module ActiveSupport #:nodoc:
  module CoreExtensions #:nodoc:
    module Time #:nodoc:
      module ZoneCalculations
        def self.included(base) #:nodoc:
          base.class_eval do
            alias_method_chain :change, :zone
          end
        end

        def change_zone(new_zone)
          new_zone = ::Time.__send__(:get_zone, new_zone)
          return self if new_zone.name == zone
          new_zone.local(year,month,day,hour,min,sec,usec)
        end

        def change_with_zone(options)
          result = change_without_zone(options)
          options[:zone] ? result.change_zone(options[:zone]) : result
        end

      end
    end
  end
  class TimeWithZone
    def change_zone(new_zone)
      time.change_zone(new_zone)
    end
    def change(options)
      time.change(options)
    end
  end

end

class Time
  include ActiveSupport::CoreExtensions::Time::ZoneCalculations
end
8 голосов
/ 15 января 2011

Вы правы в том, что Rails не сбрасывает часовой пояс автоматически для каждого запроса.Автор вики приводит пример, где часовой пояс устанавливается в фильтре before, поэтому он никогда не испытывает проблемы с «утечками» часовых поясов между запросами (так как часовой пояс настроен правильно перед запросом).Пример, используемый в документации RDoc для TimeZone.zone=, аналогичен.Поэтому я думаю, что это проблема с документацией.

Изменение часового пояса - это не локальный запрос, а локальный поток.Rails хранит выбранный часовой пояс в текущем потоке (см. Thread.current.[:time_zone]), а не в текущем запросе.Поскольку один и тот же поток обрабатывает несколько запросов, изменения в Time.zone остаются постоянными между запросами.

Я думаю, что правильным способом использования часовых поясов в вашем сценарии является использование Time.use_zone:

def my_action
  Time.zone # 'UTC'
  Time.use_zone('Mountain Time (US & Canada)') do
    Time.zone # 'Mountain Time (US & Canada)'
  end
  Time.zone # 'UTC'
end

У меня не было времени просмотреть ваши проблемы с ActiveRecord.Я вернусь с обновлением, когда я это сделаю.

0 голосов
/ 26 декабря 2010

У меня была эта проблема, когда я строил систему поддержки билетов. Мое решение было следующим.

Установите часовой пояс Datetime SQL и ActiveRecord на UTC.

Установите желаемый формат времени в конфигурации вашего приложения.

Создайте класс CSS для того, как вы хотите, чтобы CSS вашей даты выглядел. Сделайте имя уникальным. НАПРИМЕР. UTCDate

Написать javascript для поиска на странице названных классов DOM Date (UTCDate сверху). Поскольку Javascript выполняется конечным пользователем, он заменит значение тем, что установлено на локальном компьютере. Если у вас возникла ошибка синтаксического анализа, оставьте время в UTC. Смотрите здесь для соответствующего JavaScript

Преимущество этого решения в том, что у вашего приложения нет глупого средства выбора часового пояса, как у многих форумов. Это также снижает нагрузку на сервер и базовый размер кода (поскольку вам не нужно хранить или обрабатывать какую-либо информацию о часовых поясах клиента) для выполнения важной, но сложной задачи.

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