为了构建一个漂亮的图表,我必须显示过去30天内每天有多少api调用。到目前为止,我没有任何问题。需要注意的是,数据库和系统时区是UTC格式,但大多数用户位于太平洋时间(GMT -8)。
为了获得按使用日期分组的api用法,我可以执行类似
的操作ApiCall.
where(user_id: @user.id).
where("requested_at > ?", Time.zone.now.to_date - 30.days).
group("date(requested_at)").
select("count(*) as qty, date(requested_at) as requested_at").all
现在的问题是,位于加利福尼亚的用户的时间与位于英格兰的用户的时间大不相同,而这正是我失败的地方。
用户在2012-12-14 21:00:00 GMT -8进行位于加利福尼亚州的API呼叫。 DB中的API CALL存储时间为2012-12-14 05:00:00(因为它在GMT中,所以它增加了8小时)。
现在对于那个用户,如果我去查看我的日常使用情况,通过我所做的查询,它将显示我的api呼叫未在2012-12-14 21:00:00进行,因为在数据库上它存储为2012-12-14 05:00:00,并且对于用户来说api用法将显示他在第二天进行了api调用,并且他会认为系统工作正常。
所以简单来说,如何根据用户时区而不是基于数据库时区对api调用进行分组?
答案 0 :(得分:2)
是的,通过使用mysql转换时区功能,它不是一个很好的解决方案,但它的工作原理
ApiCall.
where(user_id: @user.id).
where("requested_at > ?", Time.zone.now.to_date - 30.days).
group("date(convert_tz(requested_at, '+00:00', '-08:00'))").
select("count(*) as qty, date(requested_at) as requested_at").all
稍后我会添加赏金,看看是否有人可以告诉我有关使用rails和时区的这类报告的更好解决方案
答案 1 :(得分:2)
您可以设置请求的时区,并在Rails中进行日期排序。
ApiCall.
where(user_id: @user.id).
where("requested_at > ?", Time.zone.now.to_date - 30.days).
group_by({|api_call| api_call.requested_at.to_date})
通常在Rails中,您可以在控制器操作上的before_filter中设置时区。
Time.zone = current_user.time_zone if logged_in?
答案 2 :(得分:2)
我不认为Rails带有这种范围,可以在对用户时区进行“组”之前转换数据库日期。
但是,您可以定义自己的范围来处理它,例如:
class ApiCall
scope :group_by_date_in_timezone, lambda{|date_column|
group("date(convert_tz(#{date_column}, '+00:00', '#{Time.zone.formatted_offset}'))")
}
然后你的查询变得更清晰了:
ApiCall.
where(user_id: @user.id).
where("requested_at > ?", Time.zone.now.to_date - 30.days).
group_by_date_in_timezone(:requested_at).
select("count(*) as qty, requested_at").all
# 'date(requested_at) as requested_at' would be also in DB Time zone
# Instead we can select 'requested_at' to let Rails do the user timezone conversion
# you can easily convert it to a date later with #to_date
此范围对其他模型也可能有用,然后您可以根据此问题的答案将其定义为全局ActiveRecord范围:Hacking ActiveRecord: add global named scope。
答案 3 :(得分:1)
有点晚了,但Postgresql的回答是:
首先定义此var以使事物更具可读性:
requested_at_day = "date(requested_at AT TIME ZONE \'UTC\'
AT TIME ZONE \'#{Time.zone.tzinfo.identifier}\')"
然后:
ApiCall.
where(user_id: @user.id).
where("requested_at > ?", Time.zone.now.to_date - 30.days).
group(requested_at_day).
select("count(*) as qty, #{requested_at_day} as requested_at_day").all
答案 4 :(得分:-1)
我认为以UTC格式存储所有记录是一种很好的做法,因此不需要转换任何内容,并且可以正确计算时间间隔。仅在向用户显示时间时转换为用户的本地时区。