循环遍历年份和月份

3
我将尝试循环年份和月份。如果@user.created_at2015年11月,那么我想要得到以下输出:
November 2015
December 2015
January 2016
February 2016
March 2016

我尝试了以下方法:
Hash[(@user.created_at.month..Date.today.month).map { |month| [ month, 0 ] }].merge(...)

但是上面的代码返回错误信息:

堆栈级别太深

这是因为它循环了 11..3。如何解决这个问题?

你想循环多少年和月份?你可以添加像1个月这样的数字,以便逐渐递增月份。 - Vincent
6个回答

4

如果您想计算未来的几个月/年,您可以使用 advance

start = @user.created_at.to_date.beginning_of_month

Hash[(0..6).collect { |n| [ start.advance(months: n).month, 0 ] }]

这应该能够正确按日/月步进。您可能希望仅使用日期而不仅仅是月份数字。

如果您想要做到“直到今天”,请尝试以下操作:

date = @user.created_at.to_date.beginning_of_month
stop = Date.today.beginning_of_month
hash = { }

while (date <= stop)
  hash[date] = 0

  date = date.advance(months: 1)
end

感谢 @tadman 的回答。为什么有 0..6.collect?我想要做到“现在”,所以我这样做:Hash[@admin.created_at.to_date.beginning_of_month..Date.today.to_date.beginning_of_month.collect { |n| [ start.advance(months: n).month, 0 ] }],但是出现了错误信息:NoMethodError (undefined method collect' for 3:Fixnum):` - 我是否漏掉了一些日期转换的部分? - user984621
哦,好的,那应该可行。请确保在n..m中,n<=m。我忘记把范围分组了。编辑完成。应该是(0..6),表示"月份加零到加六",但您也可以用您自己的代码替换它。如果您不想迭代每一天,就不能使用范围。 - tadman

2

你可以将每个日期转换为“月份数字”,即将年份乘以12再加上零起点的月份,然后对它们进行迭代,例如:

from_date = Date.new(2015, 11) # Nov. 1, 2015
to_date = Date.today # Mar. 22, 2016

nov2015 = from_date.year * 12 + (from_date.month - 1)
# => 24190
nov2015.divmod(12)
# => [2015, 10]

mar2016 = to_date.year * 12 + (to_date.month - 1)
# => 24194
mar2016.divmod(12)
# => [2016, 2]

请记住月份是从零开始计数的,所以2代表三月,10代表十一月。如预期的那样,nov2015mar2016相差四个月。现在我们可以从一个日期迭代到另一个日期:

nov2015.upto(mar2016) do |month_num|
  year, month = month_num.divmod(12)
  puts Date.new(year, month + 1).strftime("%B %Y")
end
# => November 2015
#    December 2015
#    January 2016
#    February 2016
#    March 2016

太完美了!但是如果我们可以将这个放入一个返回枚举器的方法中,那么我们就可以在它上面使用任何可枚举的方法(each, map, each_cons等),你可以自行命名:

def months_enum(from_date, to_date)
  from = from_date.year * 12 + from_date.month - 1
  to = to_date.year * 12 + to_date.month - 1

  Enumerator.new do |y|
    from.upto(to) do |n|
      year, month = n.divmod(12)
      y << Date.new(year, month + 1)
    end
  end
end

然后:

from = Date.new(2015, 11, 1)
to = Date.today

months_enum(from, to).each do |date|
  puts date.strftime("%Y-%m")
end
# -> 2015-11
#    2015-12
#    2016-01
#    2016-02
#    2016-03

p months_enum(from, to).map {|date| date.strftime("%B %Y") }
# => [ "November 2015",
#      "December 2015",
#      "January 2016",
#      "February 2016",
#      "March 2016" ]

2
require 'date'
from_date = Date.new(2015, 11)
to_date   = Date.today

until from_date > to_date do
  puts from_date.strftime("%B %Y")
  from_date = from_date.next_month
end

#November 2015
#December 2015
#January 2016
#February 2016
#March 2016

嗨@steenslag,感谢您的回答。我正在尝试让它工作,但输出总是停在二月,从未到三月。 - user984621
@user984621,我交换了两行代码(puts..from_date = from_date...行)。也许你正在测试“旧”版本。 - steenslag

0
我会从用户创建的日期开始循环,一直到追上今天为止:
start = @user.created_at
months = []
while start <= Time.zone.now
  months << [start.strftime('%B'), start.year]
  start += 1.month
end
months

结果:

=> [["November", 2015], ["December", 2015], ["January", 2016], ["February", 2016], ["March", 2016]]

这样你就不必计算created_at和现在之间的月份差异了。


0

这是一个纯 Ruby 的解决方案。

require 'date'

如果给定
prev_date = "November 2015"
m = Date.strptime(prev_date,"%B %Y")
  #=> #<Date: 2015-11-01 ((2457328j,0s,0n),+0s,2299161j)> 

那么

n = Date.today
n -= n.day - 1
  #=> #<Date: 2016-03-01 ((2457449j,0s,0n),+0s,2299161j)>

while m <= n
  puts "#{Date::MONTHNAMES[m.month].ljust(8)} #{m.year}"
  m = m >> 1
end

November 2015
December 2015
January  2016
February 2016
March    2016

0
在Rails中,使用 Integer#month()方法将月份添加到日期对象中,它将为您处理所有年份的包装。 它还足够聪明,以防用户的加入日期落在某个月的31日以下,不会跳过少于31天的月份。 如果您将一个月添加到1月31日,则返回值将是2月28日(或闰年的29日)。
date_joined = @user.created_at
now = Time.now

until date_joined.month > now.month && date_joined.year == now.year
  puts date_joined.strftime("%B %Y")
  date_joined += 1.month
end

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接