有谁知道如何在 rails 2.3 中正确处理用户时区? [英] Does anyone know how to appropriately deal with user timezones in rails 2.3?

查看:37
本文介绍了有谁知道如何在 rails 2.3 中正确处理用户时区?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我们正在构建一个需要在多个时区显示日期(更重要的是计算日期)的 Rails 应用.

We're building a rails app that needs to display dates (and more importantly, calculate them) in multiple timezones.

谁能告诉我如何在 rails 2.3(.5 或 .8)中使用用户时区

Can anyone point me towards how to work with user timezones in rails 2.3(.5 or .8)

我见过的最全面的文章详细介绍了用户时区的工作方式:http://wiki.rubyonrails.org/howtos/time-zones... 虽然不清楚这是什么时候写的,也不清楚是什么版本的 rails.具体来说,它指出:

The most inclusive article I've seen detailing how user time zones are supposed to work is here: http://wiki.rubyonrails.org/howtos/time-zones... although it is unclear when this was written or for what version of rails. Specifically it states that:

Time.zone - 实际用于显示目的的时区.这可以手动设置以在每个请求的基础上覆盖 config.time_zone."

"Time.zone - The time zone that is actually used for display purposes. This may be set manually to override config.time_zone on a per-request basis."

关键术语是显示目的"和每个请求的基础".

Keys terms being "display purposes" and "per-request basis".

在我的机器本地,这是真的.然而,在生产上,两者都不是真的.设置 Time.zone 在请求结束后持续存在(对于所有后续请求),并且还会影响 AR 保存到数据库的方式(基本上将任何日期视为已经在 UTC 中,即使它不是),从而保存完全不合适的值.

Locally on my machine, this is true. However on production, neither are true. Setting Time.zone persists past the end of the request (to all subsequent requests) and also affects the way AR saves to the DB (basically treating any date as if it were already in UTC even when its not), thus saving completely inappropriate values.

我们使用passenger 在生产环境中运行Ruby Enterprise Edition.如果这是我的问题,我们是否需要切换到 JRuby 或其他什么?

We run Ruby Enterprise Edition on production with passenger. If this is my problem, do we need to switch to JRuby or something else?

为了说明问题,我现在将以下操作放入 ApplicationController 中:

To illustrate the problem I put the following actions in my ApplicationController right now:

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,即使它不应该.


It should be clear above that the 2nd call to /test shows Time.zone set to Mountain, even though it shouldn't.

另外,检查数据库发现在test2之后运行的测试操作保存了一条日期为2010-12-22 15:00:00的JunkLead记录,这显然是错误的.

Additionally, checking the database reveals that the test action when run after test2 saved a JunkLead record with a date of 2010-12-22 15:00:00, which is clearly wrong.

推荐答案

经过详尽的研究,现在完全清楚 Time.zone 在大多数 Rails 版本(包括 2.3 和 3)中都被破坏了.此功能使用中央线程散列来存储设置的值(应该是线程安全的,但不是),并最终修改所有后续请求的行为.此外,与文档相反,设置 Time.zone 会修改 ActiveRecord 行为并将日期时间保存在新区域中,而不是在配置中指定的区域(通常是 UTC).

After exhaustive research it is now COMPLETELY clear that Time.zone is broken in most all versions of Rails (including 2.3 & 3). This feature uses a central thread hash to store the value that is set (which is supposed to be thread safe, and isn't) and ends up modifying the behavior for all subsequent requests. Additionally, contrary to documentation, setting Time.zone modifies ActiveRecord behavior and saves datetimes in the new zone, instead of the one specified in config (which is usually UTC).

在 Rails 修复此问题之前,我们选择手动使用时区,可以通过未公开的默认方法访问:

Until Rails gets this fixed, we chose instead to work with Timezones manually, which can be accessed via the undocumented default method:

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

此外,我修补了 Time 和 ActiveSupport::TimeWithZone 以提供一个时间片刻到不同区域的轻松转换.需要说明的是,我指的是不同区域的相应时刻,而不是同时时刻.

Additionally I patched Time and ActiveSupport::TimeWithZone to provide easy conversion of a time moment to a different zone. To be clear, I mean the corresponding moment of time in a different zone, not the simultaneous moment.

>> 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

这篇关于有谁知道如何在 rails 2.3 中正确处理用户时区?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆