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?
For example:
((Date.today - 90)..Date.today).map{|d| [d.year, d.month]}.uniq
#=> [[2012, 12], [2013, 1], [2013, 2], [2013, 3]]
require 'date'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.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]]
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]
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]]
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
Time.new.to_date instead of Date.new directly?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)
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
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
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]]
ActiveSupportin your projectRangecan be simply extended to work with variable-step sizes with timezone support (see stackoverflow.com/questions/19093487/ruby-create-range-of-dates/…).