2012-12-15 47 views
3

我必须显示在过去的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调用。数据库中的API CALL与时间2012-12-14 05:00:00一起存储(因为它在格林尼治标准时间,所以它增加了8个小时)。

现在对于那个用户来说,如果我去查看我的日常用法,用我做的查询,它会显示我的api调用不是在2012年12月14日21:00:00进行的,因为在数据库它的存储为2012-12-14 05:00:00,对于用户来说,api的使用情况将显示他在第二天做了api调用,他会认为系统工作不正常。

因此,我怎么能根据用户时区对基于数据库时区的api调用进行分组?

回答

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 

我将在后面添加一个赏金,看看是否有人能告诉我一个更好的解决方案,以这些种类的导轨和时区报告

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? 
+0

是的,我知道时间.zone和我知道使用红宝石分组记录,但离开数据库引擎进行分组更有效,所以我真正想要的是一个很好的方式来处理时区,同时让引擎做复杂的操作。 – rorra

2

我不认为Rails有这样的范围,将转换DB日期在用户时区做一个“组”在它之前。

但是,你可以定义自己的范围来处理,例如:

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

-1

我认为这是一个很好的做法,以UTC存储所有记录,所以不需要转换任何内容,并且将正确计算时间内部值。在向用户显示时间时仅转换为用户的本地时区。晚

+1

我已经以UTC存储了所有内容,而当我必须按天或小时进行分组时,问题就出现了,因为用户有不同的时区。 – rorra

+0

好了,在这种情况下,很容易,因为一切都在UTC,当用户查询他在某一天(当地时间)所做的api调用时,只需将他的本地时间转换为db中的UTC时间等值,构建查询。 在你的例子中,用户的api调用自他当地时间12/14 21:00以后将成为db的UTC时间,因为12/15 5:00 – lionel

+0

但是这让我回到原来的问题。假设你位于洛杉矶,所以它的GMT-8。数据库以GMT + 0格式存储日期和时间。你昨天晚上10点打了50个电话,今天凌晨4点又打了50个电话。分贝存储在今天,第一个在上午6点,其他人在下午12点。如果我按天分组,则数据库将与时区格林尼治标准时间+0组合,所以它会说你今天做了100个API调用,但我需要它说你今天做了50个API调用并且做了正确的时区转换,而我正在寻找一种更干净的方式来利用轨道时区。 – rorra

1

一点点,但是这里来PostgreSQL的答案:

首先定义这个变种使事情变得更加易读:

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