Ruby on rails 在rails 3.2中使用时区存储时间戳

Ruby on rails 在rails 3.2中使用时区存储时间戳,ruby-on-rails,ruby,ruby-on-rails-3,postgresql,activerecord,Ruby On Rails,Ruby,Ruby On Rails 3,Postgresql,Activerecord,我试图将所有时间戳及其包含的时区存储在rails应用程序中。我可以将ActiveRecord转换为utc,但我有多个应用程序访问同一个数据库,其中一些应用程序是根据时区要求实现的。所以我想做的是让activerecord像往常一样转换我的时间戳,然后用字符串“America/Los_Angeles”或附加到时间戳的任何适当时区将它们写入数据库。我目前正在JRuby1.7.8上运行Rails3.2.13,它实现了Ruby1.9.3API。我的数据库是postgres 9.2.4,与activere

我试图将所有时间戳及其包含的时区存储在rails应用程序中。我可以将ActiveRecord转换为utc,但我有多个应用程序访问同一个数据库,其中一些应用程序是根据时区要求实现的。所以我想做的是让activerecord像往常一样转换我的时间戳,然后用字符串“America/Los_Angeles”或附加到时间戳的任何适当时区将它们写入数据库。我目前正在JRuby1.7.8上运行Rails3.2.13,它实现了Ruby1.9.3API。我的数据库是postgres 9.2.4,与
activerecord jdbcpostgresql适配器连接。列类型为带时区的时间戳

我已经用
activerecord-native\u db\u types\u override
gem更改了自然的activerecord映射,方法是在my environment.rb中添加以下行:

  NativeDbTypesOverride.configure({
    postgres: {
      datetime: { name: "timestamp with time zone" },
      timestamp: { name: "timestamp with time zone" }
    }
  })
My application.rb当前包含

  config.active_record.default_timezone = :utc
  config.time_zone = "Pacific Time (US & Canada)"

我怀疑我可以重写
ActiveSupport::TimeWithZone.to_s
,并将其更改为:db格式以输出正确的字符串,但我还不能使其正常工作。非常感谢您的帮助。

正如您所建议的,一种方法是覆盖
ActiveSupport::TimeWithZone.to

您可以尝试以下方法:

def to_s(format = :default)
  if format == :db
    time_with_timezone_format
  elsif formatter = ::Time::DATE_FORMATS[format]
    formatter.respond_to?(:call) ? formatter.call(self).to_s : strftime(formatter)
  else
    time_with_timezone_format
  end
end

private

def time_with_timezone_format
  "#{time.strftime("%Y-%m-%d %H:%M:%S")} #{formatted_offset(false, 'UTC')}" # mimicking Ruby 1.9 Time#to_s format
end
我没有对此进行测试,但看看,这看起来是有效的,会给出一个类似于:
“2013-12-26 10:41:50+0000”

据我所知,这样做的问题是,您仍然无法返回正确的时区:

对于带有时区的时间戳,内部存储的值始终以UTC(通用协调时间,传统上称为格林威治标准时间,GMT)为单位。指定了明确时区的输入值将使用该时区的适当偏移量转换为UTC

这正是最初的
ActiveSupport::TimeWithZone.to
所做的

因此,获取正确时区的最佳方法可能是将显式时区值设置为数据库中的新列

这意味着您将能够保留Postgres和Rails的本机日期功能,同时还能够在必要时在正确的时区中显示时间


您可以使用这个新列,然后使用Ruby的
Time::getlocal
或Rails的
ActiveSupport::TimeWithZone来显示正确的区域。在\u Time\u zone
中,我可以建议将它们保存在iso8601中吗

这将允许您:

  • 也可以选择将它们存储为UTC 就像时区偏移一样
  • 符合国际标准

  • 在两种情况下使用相同的存储格式,包括偏移量和 没有

因此,其中一个db列可以使用UTC格式的偏移量1(通常)

从Ruby的角度来看,它非常简单

Time.now.iso8601
Time.now.utc.iso8601
ActiveRecord应该与转换无缝地工作

此外,大多数API使用这种格式(谷歌),因此最适合跨应用程序兼容性


postgresql的to_char()应该为您提供正确的格式,以防默认设置出现问题。

在遇到同样的问题后,我明白了这件事的悲惨真相:

Postgres不支持在其任何日期/时间类型中存储时区

所以你根本没有办法把一个时刻和它的时区都存储在一列中。在我提出另一种解决方案之前,让我用以下内容来支持这一点:

所有时区感知日期和时间都存储在UTC内部。在显示给客户端之前,它们将转换为时区配置参数指定的区域中的本地时间

因此,对于您来说,没有简单地将时区“附加”到时间戳的好方法。但这并不可怕,我保证!这只意味着你需要另一个专栏

我(相当简单)提出的解决方案:

  • 将时区存储在字符串列中(我知道是gross)
  • 只需编写一个getter,而不是覆盖到
  • 假设您需要在
    列中进行分解:

    def local_explodes_at
      explodes_at.in_time_zone(self.time_zone)
    end
    
    如果要自动存储时区,请覆盖setter:

    def explodes_at=(t)
      self.explodes_at = t
      self.time_zone = t.zone #Assumes that the time stamp has the correct offset already
    end
    
    为了确保t.zone返回正确的时区,需要将time.zone设置为正确的时区。您可以为使用的每个应用程序、用户或对象轻松更改
    时区。有很多方法可以做到这一点,我很喜欢Ryan Bates的方法,所以以一种对您的应用程序有意义的方式来实现它

    如果您想获得更多乐趣,并且需要在多个列上使用此getter,您可以在所有列中循环,并为每个datetime定义一个方法:

    YourModel.columns.each do |c|
      if c.type == :datetime
        define_method "local_#{c.name}" do
          self.send(c.name).in_time_zone(self.time_zone)                                                                                                                           
        end
      end
    end
    
    YourModel.first.local_created_at #=> Works.
    YourModel.first.local_updated_at #=> This, too.
    YourModel.first.local_explodes_at #=> Ooo la la
    
    这不包括setter方法,因为您确实不希望每个datetime列都能够写入self.time\u zone。你必须决定在哪里使用它。如果您真的想变得更有趣,您可以通过在模块中定义它并将其导入到每个模型中,在所有模型中实现这一点

    module AwesomeDateTimeReader
      self.columns.each do |c|
        if c.type == :datetime
          define_method "local_#{c.name}" do
            self.send(c.name).in_time_zone(self.time_zone)                                                                                                                           
          end
        end
      end
    end
    
    class YourModel < ActiveRecord::Base
      include AwesomeDateTimeReader
      ...
    end
    
    模块AwesomeDateTimeReader
    self.columns.each do|c|
    如果c.type==:datetime
    定义_方法“local#{c.name}”do
    self.send(c.name).in_时区(self.time_时区)
    结束
    结束
    结束
    结束
    类YourModel
    下面是一个相关的有用答案:


    希望这有帮助

    为什么不将它们处理/存储为“带时区的时间戳”(utc),并设置pg客户端时区,使其返回您想要的格式?您可以设置postgres在进入过程中这样做吗?如果是这样的话,这对我来说是可行的,但它们需要与UTC时区一起存储。据我所知,您不能在进入数据库的过程中自动转换。我不认为那样