使用日期对象每月迭代

所以我有两个ruby Date对象,我想每月迭代它们。 例如,如果我有Date.new(2008,12)和Date.new(2009,3),它将产生我2008-12,2009-1,2009-2,2009-3(当然是Date对象)。 我尝试过使用范围,但它每天都会产生。 我看到了Date的step方法,但它只允许我传递天数(并且每个月都有不同的数量)。 有人有主意吗?

我在Date类中添加了以下方法:

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 

你使用它像:

 >> t = Date.today => # >> t.all_months_until(t+100) => [#, #, #, #] 

好的,所以,更多rubyish方法恕我直言将是一些东西:

 class Month> 1 end end 

 >> t = Month.today => # >> (t..t+100).to_a => [#, #, #, #] 

但是你需要小心使用月份的第一天(或者在月份中实现这样的逻辑)……

这是非常Ruby的东西:

每个月的第一天

 (Date.new(2008, 12)..Date.new(2011, 12)).select {|d| d.day == 1} 

它将为您提供该范围内每个月的第一天的数组。

每个月的最后一天

 (Date.new(2008, 12)..Date.new(2012, 01)).select {|d| d.day == 1}.map {|d| d - 1}.drop(1) 

请注意,结束日期必须是结束范围之后的月份。

我发现有时候在生成选择的月份列表时我需要这样做。 关键是日期上的>>运算符,它将日期提前一个月。

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

当然,您可以在循环中格式化结果。

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

将返回月份名称和年份(“March 2009”),而不是Date对象。 请注意,返回的Date对象将在该月的第1天设置。

我提出了以下解决方案。 它是日期范围的混合,可以为年份和月份添加迭代器。 它产生整个范围的子范围。

  require 'date' module EnumDateRange def each_year years = [] if block_given? grouped_dates = self.group_by {|date| date.year} grouped_dates.each_value do |dates| years << (yield (dates[0]..dates[-1])) end else return self.enum_for(:each_year) end years end def each_month months = [] if block_given? self.each_year do |range| grouped_dates = range.group_by {|date| date.month} grouped_dates.each_value do |dates| months << (yield (dates[0]..dates[-1])) end end else return self.enum_for(:each_month) end months end end first = Date.parse('2009-01-01') last = Date.parse('2011-01-01') complete_range = first...last complete_range.extend EnumDateRange complete_range.each_year {|year_range| puts "Year: #{year_range}"} complete_range.each_month {|month_range| puts "Month: #{month_range}"} 

会给你:

 Year: 2009-01-01..2009-12-31 Year: 2010-01-01..2010-12-31 Month: 2009-01-01..2009-01-31 Month: 2009-02-01..2009-02-28 Month: 2009-03-01..2009-03-31 Month: 2009-04-01..2009-04-30 Month: 2009-05-01..2009-05-31 Month: 2009-06-01..2009-06-30 Month: 2009-07-01..2009-07-31 Month: 2009-08-01..2009-08-31 Month: 2009-09-01..2009-09-30 Month: 2009-10-01..2009-10-31 Month: 2009-11-01..2009-11-30 Month: 2009-12-01..2009-12-31 Month: 2010-01-01..2010-01-31 Month: 2010-02-01..2010-02-28 Month: 2010-03-01..2010-03-31 Month: 2010-04-01..2010-04-30 Month: 2010-05-01..2010-05-31 Month: 2010-06-01..2010-06-30 Month: 2010-07-01..2010-07-31 Month: 2010-08-01..2010-08-31 Month: 2010-09-01..2010-09-30 Month: 2010-10-01..2010-10-31 Month: 2010-11-01..2010-11-30 Month: 2010-12-01..2010-12-31 
 MonthRange.new(date1..date2).each { |month| ... } MonthRange.new(date1..date2).map { |month| ... } 

如果使用此迭代器类,则可以使用所有Enumerable方法。 我也让它处理字符串,以便它可以采取表格输入。

 # Iterate over months in a range class MonthRange include Enumerable def initialize(range) @start_date = range.first @end_date = range.last @start_date = Date.parse(@start_date) unless @start_date.respond_to? :month @end_date = Date.parse(@end_date) unless @end_date.respond_to? :month end def each current_month = @start_date.beginning_of_month while current_month <= @end_date do yield current_month current_month = (current_month + 1.month).beginning_of_month end end end 

作为辅助方法:

 def iterate(d1, d2) date = d1 while date <= d2 yield date date = date >> 1 end end 

用法:

 start_date = Date.new(2008, 12) end_date = Date.new(2009, 3) iterate(start_date, end_date){|date| puts date} 

或者,如果你喜欢猴子补丁日期:

 class Date def upto(end_date) date = self while date <= end_date yield date date = date >> 1 end end end 

用法:

 start_date = Date.new(2008, 12) end_date = Date.new(2009, 3) start_date.upto(end_date){|date| puts date} 
 Date.new(2014,1,1).upto(Date.today).map {|date| "#{date.to_s[0..-4]}"}.uniq 

会给你一个每个月的字符串表示,包括它的年份。

 def each_month(date, end_date) ret = [] (ret << date; date += 1.month) while date <= end_date ret end