使用日期对象每月迭代

And*_*ius 19 ruby date

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

The*_*Who 70

这是非常Ruby的东西:

每个月的第一天

(Date.new(2008, 12)..Date.new(2011, 12)).select {|d| d.day == 1}
Run Code Online (Sandbox Code Playgroud)

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

每个月的最后一天

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

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

  • 真?4000年是一个足够大的日期范围?Benchmark.measure {(Date.new(1,1).. Date.new(4000,12)).select {| d | d.day == 1}} => 1.170000 0.000000 1.170000(1.181518) (10认同)
  • 对于大日期范围而言效率低下 (5认同)
  • (Date.today.beginning_of_month..x.beginning_of_month).select {| d | d.day == 1} <<<包括第一个月 (2认同)

Mla*_*vić 10

我在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
Run Code Online (Sandbox Code Playgroud)

你使用它像:

>> t = Date.today
=> #<Date: 2009-11-12 (4910295/2,0,2299161)>
>> t.all_months_until(t+100)   
=> [#<Date: 2009-11-01 (4910273/2,0,2299161)>, #<Date: 2009-12-01 (4910333/2,0,2299161)>, #<Date: 2010-01-01 (4910395/2,0,2299161)>, #<Date: 2010-02-01 (4910457/2,0,2299161)>]
Run Code Online (Sandbox Code Playgroud)

好吧,所以,更多rubyish方法恕我直言将是:

class Month<Date
  def succ
    self >> 1
  end
end
Run Code Online (Sandbox Code Playgroud)

>> t = Month.today
=> #<Month: 2009-11-13 (4910297/2,0,2299161)>
>> (t..t+100).to_a
=> [#<Month: 2009-11-13 (4910297/2,0,2299161)>, #<Month: 2009-12-13 (4910357/2,0,2299161)>, #<Month: 2010-01-13 (4910419/2,0,2299161)>, #<Month: 2010-02-13 (4910481/2,0,2299161)>]
Run Code Online (Sandbox Code Playgroud)

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

  • 好吧,我想到的唯一"rubyish"方法是定义"Month"类(通过继承Date),定义succ方法并在其上使用Range. (2认同)

Jon*_*ian 10

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

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))
Run Code Online (Sandbox Code Playgroud)

当然,您可以在循环中设置您喜欢的结果.

months << "#{Date::MONTHNAMES[ptr.month]} #{ptr.year}"
Run Code Online (Sandbox Code Playgroud)

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

  • 这是唯一有效的答案,因为它解释了`Date#>>` (2认同)

Dir*_*urs 6

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

    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}"}
Run Code Online (Sandbox Code Playgroud)

会给你:

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
Run Code Online (Sandbox Code Playgroud)


Edw*_*son 5

MonthRange.new(date1..date2).each { |month| ... }
MonthRange.new(date1..date2).map { |month| ... }
Run Code Online (Sandbox Code Playgroud)

如果使用此迭代器类,则可以使用所有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
Run Code Online (Sandbox Code Playgroud)

  • 对于begin_of_month,您需要ActiveSupport(来自Rails) (2认同)