edx
edx

Reputation: 1347

create a range of months between two dates in ruby

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?

Upvotes: 14

Views: 13549

Answers (10)

vladiim
vladiim

Reputation: 1960

A potential one-line approach.

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

Upvotes: 0

Mykhailo Kniazevich
Mykhailo Kniazevich

Reputation: 1

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

Upvotes: 0

Motine
Motine

Reputation: 1872

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

Upvotes: 0

KNejad
KNejad

Reputation: 2518

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

Upvotes: 0

Sean M
Sean M

Reputation: 2030

  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]

Upvotes: 1

StephenOTT
StephenOTT

Reputation: 1

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)

Upvotes: 0

pguardiario
pguardiario

Reputation: 55002

For example:

((Date.today - 90)..Date.today).map{|d| [d.year, d.month]}.uniq
#=> [[2012, 12], [2013, 1], [2013, 2], [2013, 3]]

Upvotes: 31

ZirconCode
ZirconCode

Reputation: 817

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

Upvotes: 0

dbenhur
dbenhur

Reputation: 20398

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

Upvotes: 3

edx
edx

Reputation: 1347

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

Upvotes: 1

Related Questions