Повторяйте каждый месяц с объектами даты - PullRequest
19 голосов
/ 12 ноября 2009

Итак, у меня есть два объекта ruby ​​Date, и я хочу повторять их каждый месяц. Например, если у меня есть Date.new (2008, 12) и Date.new (2009, 3), это даст мне 2008-12, 2009-1, 2009-2, 2009-3 (как объекты Date, конечно). Я пытался использовать диапазон, но он дает каждый день. Я видел пошаговый метод для даты, однако он позволяет мне пропустить только количество дней (и каждый месяц имеет разное количество дней). У кого-нибудь есть идеи?

Ответы [ 8 ]

68 голосов
/ 18 апреля 2011

Вот что-то очень рубиновое:

первый день каждого месяца

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

Просто обратите внимание, что конечной датой должен быть месяц после конечного диапазона.

10 голосов
/ 12 ноября 2009

Я считаю, что иногда мне нужно это делать при создании списков выбора месяцев. Ключом является оператор >> для даты, который переводит дату вперед на один месяц.

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}"

Возвращает название месяца и год («март 2009») вместо объекта Date. Обратите внимание, что возвращаемые объекты Date будут установлены 1-го числа месяца.

10 голосов
/ 12 ноября 2009

Я добавил следующий метод в класс 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
=> #<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)>]

Хорошо, так что более рубиновый подход ИМХО был бы чем-то вроде:

class Month<Date
  def succ
    self >> 1
  end
end

и

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

Но вы должны быть осторожны, чтобы использовать первые дни месяца (или реализовать такую ​​логику в Месяце) ...

6 голосов
/ 07 апреля 2011

Я придумал следующее решение. Это миксин для диапазонов дат, который добавляет итератор для года и месяца. Выдает поддиапазоны полного диапазона.

    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
5 голосов
/ 10 января 2013
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
2 голосов
/ 15 января 2016
Date.new(2014,1,1).upto(Date.today).map {|date| "#{date.to_s[0..-4]}"}.uniq

Предоставит вам строковое представление каждого месяца, включая год.

1 голос
/ 07 апреля 2011

В качестве вспомогательного метода:

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}
0 голосов
/ 11 марта 2015
def each_month(date, end_date)
  ret = []
  (ret << date; date += 1.month) while date <= end_date
  ret
end
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...