Как создать итератор месяца
Я хотел бы создать функцию 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)