Rails / Postgres查询按天分组的行和时区

时间:2013-06-20 18:18:08

标签: ruby-on-rails postgresql time zone impressions

我正在尝试显示特定用户时区中过去30天内每天的展示次数。麻烦的是,取决于时区,计数并不总是相同,我在查询中无法反映这一点。

例如,在第一天采取CDT(-5)晚上11点发生的两次展示,并在CDT凌晨1:00展示一次展示。如果使用UTC(+0)查询,您将在第二天获得所有3次展示,而不是第一天和第二天两次。两次CDT时间都在UTC的第二天降落。

这就是我现在正在做的事情,我知道我必须在这里错过一些简单的事情:

start = 30.days.ago
finish = Time.now

# if the users time zone offset is less than 0 we need to make sure
# that we make it all the way to the newest data
if Time.now.in_time_zone(current_user.timezone) < 0
  start += 1.day
  finish += 1.day
end

(start.to_date...finish.to_date).map do |date|
  # get the start of the day in the user's timezone in utc so we can properly
  # query the database
  day = date.to_time.in_time_zone(current_user.timezone).beginning_of_day.utc
  [ (day.to_i * 1000), Impression.total_on(day) ]
end

展示次数模型:

class Impression < ActiveRecord::Base
  def self.total_on(day)
    count(conditions: [ "created_at >= ? AND created_at < ?", day, day + 24.hours ])
  end
end

我一直在看其他帖子,似乎我可以让数据库为我处理很多繁重的工作,但我没有成功使用AT TIME ZONE或{{1}之类的东西}。

我没有看起来真的很脏,我知道我必须遗漏一些明显的东西。任何帮助表示赞赏。

2 个答案:

答案 0 :(得分:2)

好的,在this awesome article的帮助下,我想我已经弄明白了。我的问题源于不了解系统Ruby时间方法和时区感知Rails方法之间的区别。一旦我使用around_filter like this为用户设置了正确的时区,我就可以使用内置的Rails方法来简化代码:

# app/controllers/application_controller.rb

class ApplicationController < ActionController::Base
  around_filter :set_time_zone

  def set_time_zone
    if logged_in?
      Time.use_zone(current_user.time_zone) { yield }
    else
      yield
    end
  end
end

# app/controllers/charts_controller.rb

start = 30.days.ago
finish = Time.current

(start.to_date...finish.to_date).map do |date|
  # Rails method that uses Time.zone set in application_controller.rb
  # It's then converted to the proper time in utc
  time = date.beginning_of_day.utc
  [ (time.to_i * 1000), Impression.total_on(time) ]
end

# app/models/impression.rb

class Impression < ActiveRecord::Base
  def self.total_on(time)
    # time.tomorrow returns the time 24 hours after the instance time. so it stays UTC
    count(conditions: [ "created_at >= ? AND created_at < ?", time, time.tomorrow ])
  end
end

我可以做的更多,但我现在感觉好多了。

答案 1 :(得分:1)

假设around_filter正确工作并在块中设置Time.zone,您应该能够将查询重构为:

class Impression < ActiveRecord::Base
  def self.days_ago(n, zone = Time.zone)
    Impression.where("created_at >= ?", n.days.ago.in_time_zone(zone))
  end
end