Obtén una variedad única de meses y años entre dos fechas

Tengo dos objetos Date , por ejemplo:

 first = Fri, 02 Dec 2016 last = Wed, 01 Mar 2017 

¿Cuál es la manera más eficiente de obtener una variedad única de meses y años entre ellos? En este caso estoy después:

 Dec 2016 Jan 2017 Feb 2017 Mar 2017 

 require 'date' def doit(first, last) (12*last.year + last.month - 12*first.year - first.month).times.map do first.strftime("%b %Y") first = first >> 1 end end first = Date.parse('Fri, 02 Dec 2016') last = Date.parse('Wed, 01 Mar 2017') doit(first, last) #=> ["Dec 2016", "Jan 2017", "Feb 2017", "Mar 2017"] 

Podría crear una matriz de fechas, luego usar strftime para establecer el formato correcto y uniq para evitar valores repetidos, como este:

 (first..last).map{ |date| date.strftime("%b %Y") }.uniq #=> ["Dec 2016", "Jan 2017", "Feb 2017", "Mar 2017"] 

Como el usuario solicita la forma más eficiente (y solo por diversión), aquí hay un punto de referencia simple de las soluciones propuestas:

 require 'benchmark' Benchmark.bmbm(10) do |bm| bm.report('Cary') do first = Date.new(1000, 1, 1) last = Date.new(2100, 1, 1) def doit(first, last) (12*last.year + last.month - 12*first.year - first.month).times.map do first.strftime("%b %Y") first = first >> 1 end end doit(first, last) end bm.report('Simple Lime') do first = Date.new(1000, 1, 1) last = Date.new(2100, 1, 1) dates = [] while first.beginning_of_month < last.end_of_month dates << first.strftime("%b %Y") first = first.next_month end end bm.report('Máté') do first = Date.new(1000, 1, 1) last = Date.new(2100, 1, 1) (first.beginning_of_month..last).map { |d| d.strftime("%b %Y") if d.day == 1 }.compact end bm.report('Gerry/Dan') do first = Date.new(1000, 1, 1) last = Date.new(2100, 1, 1) (first..last).map{ |date| date.strftime("%b %Y") }.uniq end end 

Resultados:

 Rehearsal ----------------------------------------------- Cary 0.020000 0.000000 0.020000 ( 0.025968) Simple Lime 0.190000 0.000000 0.190000 ( 0.192860) Máté 0.460000 0.020000 0.480000 ( 0.481839) Gerry/Dan 0.810000 0.020000 0.830000 ( 0.835931) -------------------------------------- total: 1.520000sec user system total real Cary 0.020000 0.000000 0.020000 ( 0.024871) Simple Lime 0.150000 0.000000 0.150000 ( 0.150696) Máté 0.390000 0.010000 0.400000 ( 0.398637) Gerry/Dan 0.710000 0.010000 0.720000 ( 0.711155) 

No es muy bonito, pero tampoco pasa por cada día individualmente, por lo que debería ser bastante más rápido para grandes rangos.

 first = Date.new(2016, 12, 2) last = Date.new(2017, 3, 1) dates = [] while first.beginning_of_month < last.end_of_month dates << first.strftime("%b %Y") first = first.next_month end puts dates.inspect # => ["Dec 2016", "Jan 2017", "Feb 2017", "Mar 2017"] 

Asumiendo estas variables:

 first = Date.new(2016, 12, 2) last = Date.new(2017, 3, 1) 

Una solución de una sola línea:

 (first.beginning_of_month..last).map { |d| d.strftime("%b %Y") if d.day == 1 }.compact #=> ["Dec 2016", "Jan 2017", "Feb 2017", "Mar 2017"] 
 start_date=Date.new(2016,12,2) end_date=Date.new(2017,3,1) (start_date..end_date).map{ |d| d.strftime("%b %Y") }.uniq => ["Dec 2016", "Jan 2017", "Feb 2017", "Mar 2017"]