14

I need to create logfiles per month for a range of months. Therefor I need all [year,month] tuples in a given range

How do you do iterating over dates?

How can this be done if I'd need to iterate every day?

1

10 Answers 10

32

For example:

((Date.today - 90)..Date.today).map{|d| [d.year, d.month]}.uniq
#=> [[2012, 12], [2013, 1], [2013, 2], [2013, 3]]
Sign up to request clarification or add additional context in comments.

2 Comments

"NoMethodError: undefined method 'today' for Date:Class" unless you require 'date'
Not sure why you consider this snark. Many people are surprised that there's a built in Date class out of box in irb or pry that's a crippled shadow of the Date you get after you require 'date' and the docs do a terrible job telling you when you need to require from stdlib.
3

Ruby Date supports producing successive days and offers a next_month method which could be used to efficiently iterate over months.

Here's a generic method that adapts to the precision of your inputs:

require 'date'

def date_tuples(from,to)
  prec   = from.size
  start  = Date.new(*from)
  finish = Date.new(*to)

  filter_on = [:day,:mon].first(3-prec)
  filter = ->(d) { filter_on.all? {|attr| d.send(attr) == 1 } }

  (start..finish)
    .select(&filter)
    .map { |d| [d.year,d.mon,d.day].first(prec) }
end

[7] pry(main)> date_tuples([2012],[2015])
=> [[2012], [2013], [2014], [2015]]
[8] pry(main)> date_tuples([2012,10],[2013,3])
=> [[2012, 10], [2012, 11], [2012, 12], [2013, 1], [2013, 2], [2013, 3]]
[9] pry(main)> date_tuples([2012,10,25],[2012,11,6])
=> [[2012, 10, 25],
 [2012, 10, 26],
 [2012, 10, 27],
 [2012, 10, 28],
 [2012, 10, 29],
 [2012, 10, 30],
 [2012, 10, 31],
 [2012, 11, 1],
 [2012, 11, 2],
 [2012, 11, 3],
 [2012, 11, 4],
 [2012, 11, 5],
 [2012, 11, 6]]

Comments

2
  start_date = 1.year.ago.to_date
  end_date = Date.current.yesterday
  monthly = [Date.new(start_date.year, start_date.beginning_of_month.month, 1)]
  (start_date..end_date).each do |d|
    month_date = Date.new(d.year, d.next_month.beginning_of_month.month, 1)
    monthly << month_date if monthly.exclude?(month_date) && month_date < end_date - 1.month
  end
  monthly

=> [Fri, 01 Sep 2017, Sun, 01 Oct 2017, Wed, 01 Nov 2017, Fri, 01 Dec 2017, Sun, 01 Jan 2017, Thu, 01 Feb 2018, Thu, 01 Mar 2018, Sun, 01 Apr 2018, Tue, 01 May 2018, Fri, 01 Jun 2018, Sun, 01 Jul 2018, Wed, 01 Aug 2018]

Comments

1

I came up with this solution to generate a list of all [year,month] tuples in the range:

first=[2012,10]
last=[2013,03]
(first[0]..last[0]).to_a.product((1..12).to_a).select{|ym|(first..last).cover?(ym)}
=> [[2012, 10], [2012, 11], [2012, 12], [2013, 1], [2013, 2], [2013, 3]]

2 Comments

This is pretty nice, but doesn't nicely extend to days as months don't all have the same set of days as years do months.
This seems to be the only answer that doesn't need to need to go over every single day to do the calculation, so it's much more efficient than the rest
0
require 'date'
Time.new(2011).to_date.upto(Time.now.to_date) do |a|
    puts ""+a.day.to_s+","+a.month.to_s+","+a.year.to_s
end

Or getting your month/year tuples:

require 'date'
result = []
Time.new(2002).to_date.upto(Time.now.to_date) do |a|
    result << [a.month,a.year]
end
result.uniq!

Use the upto method from date: http://ruby-doc.org/stdlib-2.0/libdoc/date/rdoc/Date.html#method-i-upto

4 Comments

How does this help OP iterate by months?
Also, your block arg and puts statement disagree about the name of the yielded element.
Why call Time.new.to_date instead of Date.new directly?
Sorry, posted rashly, thanks for catching those, fixed it now =)
0

Here is a way i wrote to solve this issue. This was designed for working with hash data like: {Sun, 01 Jan 2012=>58, Wed, 01 Feb 2012=>0, Thu, 01 Mar 2012=>0} but could be easily modified for array data.

See: https://github.com/StephenOTT/add_missing_dates_ruby where i have provided a working code sample

But the key piece of code is:

def addMissingMonths (datesHash)
    count = 0
    result = {}

    datesHash.keys.each do |x|
        if x != datesHash.keys.last
            (x+1.month).upto(datesHash.keys[count+1]-1.month) do |a|
                result[a.at_beginning_of_month] = 0
            end
        end

        count += 1
    end

    return result.merge!(datesHash)
end

The key content to look at is: (x+1.month).upto(datesHash.keys[count+1]-1.month)

Comments

0

Most of the answers here require iterating over every day in the range. Meaning if you are doing this for a range of a few years, you could be having a loop with thousands of iterations.

This snippet creates a loop with as many steps as there are months in the range, which is more efficient:

require 'date'

start_date = Date.new(2010, 10)
end_date = Date.new(2011, 4)

current_month = start_date

date_tuples = []

while current_month <= end_date
  date_tuples << [current_month.year, current_month.month]
  current_month = current_month.next_month
end

pp date_tuples 
# => [[2010, 10], [2010, 11], [2010, 12], [2011, 1], [2011, 2], [2011, 3], [2011, 4]]

One quirk of this method is that it will only work with dates that are on the first day of the month. So if you have a date like Date.new(2020, 10, 12) you need to convert it to the first day of the month

Comments

0

I had a slighly different problem, but it is related: I needed the months between a given start & end year.

def month_start_dates_between_years(start_year, end_year)
  years = (start_year..end_year).to_a
  month_tuples = years.product((1..12).to_a)
  return month_tuples.map { |tuple| Date.new(*tuple) }
end

Comments

0
date1 = Date.new(2024,4,25)
date2 = Date.new(2025,2,8)     
month_distance = 12 * (date2.year - date1.year) + date2.month - date1.month + 1
month_distance.times.map { |m| d = date1 + m.months; [d.year, d.month] }

# => [[2024, 4], [2024, 5], [2024, 6], [2024, 7], [2024, 8], [2024, 9], [2024, 10], [2024, 11], [2024, 12], [2025, 1], [2025, 2]]

1 Comment

As it’s currently written, your answer is unclear. Please edit to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers in the help center.
0

A potential one-line approach.

(start_date..end_date).to_a.map { |date| date.strftime("%B %Y") }.uniq

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.