tags:

views:

254

answers:

2

So I have two ruby Date objects, and I want to iterate them every month. For example if I have Date.new(2008, 12) and Date.new(2009, 3), it would yield me 2008-12, 2009-1, 2009-2, 2009-3 (as Date objects of course). I tried using range, but it yields every day. I saw step method for Date however it only allows me to pass number of days (and each month has different number of those). Anyone have any ideas?

+4  A: 

I have added following method to Date class:

class Date
  def all_months_until to
    from = self
    from, to = to, from if from > to
    m = Date.new from.year, from.month
    result = []
    while m <= to
      result << m
      m >>= 1
    end

    result
  end
end

You use it like:

>> t = Date.today
=> #<Date: 2009-11-12 (4910295/2,0,2299161)>
>> t.all_months_until(t+100)   
=> [#<Date: 2009-11-01 (4910273/2,0,2299161)>, #<Date: 2009-12-01 (4910333/2,0,2299161)>, #<Date: 2010-01-01 (4910395/2,0,2299161)>, #<Date: 2010-02-01 (4910457/2,0,2299161)>]

Ok, so, more rubyish approach IMHO would be something along:

class Month<Date
  def succ
    self >> 1
  end
end

and

>> t = Month.today
=> #<Month: 2009-11-13 (4910297/2,0,2299161)>
>> (t..t+100).to_a
=> [#<Month: 2009-11-13 (4910297/2,0,2299161)>, #<Month: 2009-12-13 (4910357/2,0,2299161)>, #<Month: 2010-01-13 (4910419/2,0,2299161)>, #<Month: 2010-02-13 (4910481/2,0,2299161)>]

But you would need to be careful to use first days of month (or implement such logic in Month)...

Mladen Jablanović
Was hoping for something more rubyish...
Andrius
Well, the only "rubyish" approach that comes to my mind would be defining "Month" class (by inheriting Date), defining succ method and using Range on it.
Mladen Jablanović
Thanks Mladen! This all_months_until method was exactly what I was looking for.
jspooner
+1  A: 

I find that I need to do this sometimes when generating select lists of months. The key is the >> operator on Date, which advances the Date forward one month.

def months_between(start_month, end_month)
  months = []
  ptr = start_month
  while ptr <= end_month do
    months << ptr
    ptr = ptr >> 1
  end
  months
end

results = months_between(Date.new(2008,12), Date.new(2009,3))

Of course, you can format the results however you like in the loop.

months << "#{Date::MONTHNAMES[ptr.month]} #{ptr.year}"

Will return the month name and year ("March 2009"), instead of the Date object. Note that the Date objects returned will be set on the 1st of the month.

Jonathan Julian