user984621
user984621

Reputation: 48453

Loop through years + months

I am trying to loop through years and months. If @user.created_at is November 2015, then I'd like to get this output:

November 2015
December 2015
January 2016
February 2016
March 2016

I tried the following:

Hash[(@user.created_at.month..Date.today.month).map { |month| [ month, 0 ] }].merge(...)

But the above code returns

stack level too deep

because it loops 11..3. How can I figure out this issue?

Upvotes: 3

Views: 2331

Answers (6)

Shannon Scott Schupbach
Shannon Scott Schupbach

Reputation: 1278

Adding months to a date object in Rails using the Integer#month() method will take care of all the wrapping of years for you. It's also smart enough to not skip a month with less than 31 days should the user's join date fall on the 31st of some month. If you add a month to January 31st, the return value will be February 28th (or 29th in the case of a leap year).

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

Upvotes: 0

Cary Swoveland
Cary Swoveland

Reputation: 110685

This is a pure-Ruby solution.

require 'date'

If given

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

then

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

Upvotes: 0

steenslag
steenslag

Reputation: 80065

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

Upvotes: 2

Jordan Running
Jordan Running

Reputation: 106027

One thing you can do is turn each date into a "month number" that's the year multiplied by 12 plus the zero-based month, and then iterate over them, e.g.:

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]

Remember that the months are zero-based, so 2 is March and 10 is November. As expected, nov2015 and mar2016 have a difference of four. Now we can iterate from one to the other:

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

Perfect! But it's better if we can put this into a method that returns an Enumerator so we can use any of the Enumerable methods (each, map, each_cons, you name it) on it:

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

Then:

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" ]

Upvotes: 2

tadman
tadman

Reputation: 211590

If you want to compute the upcoming months/years you could always use advance:

start = @user.created_at.to_date.beginning_of_month

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

That should properly step through days/months. You may want to just stick in dates instead of just the month number.

If you want to do "up to today" then try this:

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

Upvotes: 4

MTarantini
MTarantini

Reputation: 999

I would begin with the user created_at date and loop through until it has caught up to today:

start = @user.created_at
months = []
while start <= Time.zone.now
  months << [start.strftime('%B'), start.year]
  start += 1.month
end
months

Result:

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

This way you don't have to calculate the difference in months between created_at and now.

Upvotes: 0

Related Questions