Как создать итератор месяца

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

def months(start_month, start_year, end_month, end_year):

Вызов months(8, 2010, 3, 2011) вернется:

((8, 2010), (9, 2010), (10, 2010), (11, 2010), (12, 2010), (1, 2011), (2, 2011), (3, 2011))

Функция может просто вернуть кортеж кортежей, но мне бы хотелось увидеть его как генератор (т.е. используя yield).

Я проверил модуль calendar python и, похоже, не предоставил эту функцию. Я мог бы написать неприятный цикл for, чтобы сделать это достаточно легко, но мне интересно посмотреть, как изящно это может сделать про.

Спасибо.

Ответы

Ответ 1

Календарь работает следующим образом.

def month_year_iter( start_month, start_year, end_month, end_year ):
    ym_start= 12*start_year + start_month - 1
    ym_end= 12*end_year + end_month - 1
    for ym in range( ym_start, ym_end ):
        y, m = divmod( ym, 12 )
        yield y, m+1

Все многопользовательские вещи работают так. Ноги, дюймы, часы, минуты и секунды и т.д. И т.д. Единственное, что не прост - это месяцы или месяцы-недели, потому что месяцы нерегулярны. Все остальное регулярно, и вам нужно работать в тончайших единицах.

Ответ 2

Возможно, элегантность или скорость этого можно было бы улучшить, но это простое решение:

def months(start_month, start_year, end_month, end_year):
    month, year = start_month, start_year
    while True:
        yield month, year
        if (month, year) == (end_month, end_year):
            return
        month += 1
        if (month > 12):
            month = 1
            year += 1

EDIT: И здесь менее простой, который позволяет избежать необходимости использовать yield, используя понимание генератора:

def months2(start_month, start_year, end_month, end_year):
    return (((m_y % 12) + 1, m_y / 12) for m_y in
            range(12 * start_year + start_month - 1, 12 * end_year + end_month))

Ответ 3

months с использованием модуля dateutil

from dateutil.rrule import rrule, MONTHLY
from datetime import datetime

def months(start_month, start_year, end_month, end_year):
    start = datetime(start_year, start_month, 1)
    end = datetime(end_year, end_month, 1)
    return [(d.month, d.year) for d in rrule(MONTHLY, dtstart=start, until=end)]

Пример использования

print months(11, 2010, 2, 2011)
#[(11, 2010), (12, 2010), (1, 2011), (2, 2011)]

Или в форме итератора

def month_iter(start_month, start_year, end_month, end_year):
    start = datetime(start_year, start_month, 1)
    end = datetime(end_year, end_month, 1)

    return ((d.month, d.year) for d in rrule(MONTHLY, dtstart=start, until=end))

Использование итератора

for m in month_iter(11, 2010, 2, 2011):
    print m
    #(11, 2010)
    #(12, 2010)
    #(1, 2011)
    #(2, 2011)

Ответ 4

Более простая версия подхода dfan, а также более простое решение, чем S. Lott (без деления, без модуляции):

def months(start_month, start_year, end_month, end_year):

    month, year = start_month, start_year

    while (year, month) <= (end_year, end_month):

        yield month, year

        month += 1
        if month > 12:
            month = 1
            year += 1

Этот подход близок к методу, который можно было бы использовать, если бы им приходилось делать это вручную. Он работает в тот же промежуток времени, что и S. Lott (тесты в коде выше занимают столько же времени, что и деление и по модулю).

Ответ 5

Это не так мало, как другие решения, но это просто понять. В принципе, он имеет две ветки.

  • Начальный год совпадает с конечным годом
  • Начальный год отличается от конца года

Последний случай имеет три фазы:

  • С начала месяца до декабря начала года
  • Каждый месяц с каждого года между началом года и до конца года
  • С января по конец месяца в конце года

Если конечный год - год после начала года, вторая фаза выше пропущена (нет необходимости в явном тесте, диапазон просто пуст).

def months(start_month, start_year, end_month, end_year):
    if start_year == end_year:
        for month in xrange(start_month, end_month+1):
           yield month, start_year
    else:
        for month in xrange(start_month, 13):
            yield month, start_year
        for year in xrange(start_year+1, end_year):
            for month in xrange(1, 13):
               yield month, year
        for month in xrange(1, end_month+1):
           yield end_month, end_year

Для Python 3.x измените xrange на range.

Ответ 6

Ваш вопрос немного неоднозначен в том, что вы запрашиваете итератор, но затем показываете функцию, возвращающую кортеж кортежей. Итак, здесь оба:

import calendar
import datetime

def months_iter(start_month, start_year, end_month, end_year):
    start_date = datetime.date(start_year, start_month, 1)
    end_date = datetime.date(end_year, end_month, 1)
    date = start_date
    while date <= end_date:
        yield (date.month, date.year)
        days_in_month = calendar.monthrange(date.year, date.month)[1]
        date += datetime.timedelta(days_in_month)

def months(start_month, start_year, end_month, end_year):
    return tuple(d for d in months_iter(start_month, start_year, end_month, end_year))

print months(8, 2010, 3, 2011)

# ((8, 2010), (9, 2010), (10, 2010), (11, 2010), (12, 2010), (1, 2011), (2, 2011), (3, 2011))

Ответ 7

Немного поиграть с встроенными итераторами Python, но, конечно, не изящно;)

из datetime import timedelta, date

class MonthRange:
    def __init__ (self, date1, date2):
        self.start_date = date1 - timedelta(days=1)
        self.end_date = date2
        self.data = self.start_date
    def __iter__(self):
        return self
    def next(self):
        if self.data >= self.end_date.replace(day=1) + timedelta(days=32):
            raise StopIteration
        ret = self.data
        self.data = self.data + timedelta(days=32)
        return ret.replace(day=1)

for x in MonthRange(date.today(), date(2012, 11, 01)):
    print (x.year, x.month)

Ответ 8

    for year in range(2014, 2018):
        for month in range(start_month_number, 13):
            this_month = datetime.date.today().replace(year=year, month=month, day=1)