【问题标题】:Ruby/Rails how to iterate months over a DateTime range?Ruby/Rails 如何在 DateTime 范围内迭代几个月?
【发布时间】:2015-10-27 10:03:11
【问题描述】:

我正在尝试根据 Rails 表中的数据构建图表:每个时间段的已售产品数量。

因为图表应该能够显示最近一小时(以 1 分钟为单位)、最后一天(以 1 小时为单位)、上周(以 1 天为单位)、上个月(以 1 为单位) -day 步骤)等,我试图通过迭代一系列 DateTime 对象来减少代码重复:

# To prevent code-duplication, iterate over different time ranges.
times = {
    :hour=>{newer_than: 1.hour.ago, timestep: :minute},
    :day=>{newer_than: 1.day.ago, timestep: :hour},
    :week=>{newer_than: 1.week.ago, , timestep: :day},
    :month=>{newer_than: 1.week.ago, , timestep: :day}
}


products = Product.all

# Create symbols `:beginning_of_minute`, `:beginning_of_hour`, etc. These are used to group products and timestamps by.
times.each do|name, t| 
  t[:beginning_of] = ("beginning_of_" << t[:timestep].to_s).to_sym 
end 

graphs = times.map do |name, t|
   graphpoints = {}
   seconds_in_a_day = 1.day.to_f
   step_ratio = 1.send(t[:timestep]).ago.to_f / seconds_in_a_day
   time_enum = 1.send(t[:timestep]).ago.to_datetime.step(DateTime.now, step_ratio)
   time_enum.each do |timestep|
       graphpoints[time_moment.send(timehash[:beginning_of]).to_datetime] = []
   end

   # Load all products that are visible in this graph size
   visible_products = products.select {|p| p.created_at >= t.newer_than}
   # Group them per graph point
   grouped_products = visible_products.group_by {|item| item.created_at.send(timehash[:beginning_of]).to_datetime}

   graphpoints.merge!(grouped_products)

   {
      points: graphpoints,
      labels: graphpoints.keys
   }
end

此代码适用于所有具有恒定大小(小时、天、周)的时间间隔。然而,几个月来,它使用 30 天的 step_ratio:1.month / 1.day == 30。显然,月份的天数不是恒定的。在我的脚本中,这会导致一个月可能会被“跳过”,因此会从图表中丢失。

如何解决这个问题?如何在记住月份中不同天数的同时迭代几个月?

【问题讨论】:

  • :month=>{newer_than: 1.week.ago, , timestep: :day} 是否正确? 1 周前?
  • 嘿@Qqwy!这个问题有什么进展吗?

标签: ruby-on-rails ruby datetime iteration


【解决方案1】:

使用groupdate 宝石。例如(来自文档的修改示例):

visible_products = Product.where("created_at > ?", 1.week.ago).group_by_day

# {
#   2015-07-29 00:00:00 UTC => 50,
#   2013-07-30 00:00:00 UTC => 100,
#   2013-08-02 00:00:00 UTC => 34
# }

此外,这会更快,因为您的分组/计数将由数据库本身完成,无需通过 Product.all 调用将所有记录传递给您的 Rails 代码,也无需创建 ActiveRecord 对象每一个(甚至不相关)。

【讨论】:

  • 是的,这可能是最好的解决方案。尝试了多种解决方案;我也尝试过months_time_enum = (1.year.ago..DateTime.now).select{|dt| dt.days == 1} 之类的东西。这行得通,但生成的代码仍然太慢而无法使用;在数据库中选择东西比在巨大的数组上循环更可取。
【解决方案2】:

如果您必须在巨大的数组中选择月份,只需在两个 Date:class 之间设置范围即可。

(1.year.ago.to_date..DateTime.now.to_date)).select{|date| date.day==1}.each do |date|
  p date
end

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-11-05
    • 1970-01-01
    • 2011-05-19
    • 2014-09-08
    • 1970-01-01
    • 2011-01-28
    相关资源
    最近更新 更多