Python: все месяцы?

Я хочу получить все месяцы между нынешним и августом 2010 года, в виде списка, отформатированного следующим образом:

['2010-08-01', '2010-09-01', .... , '2016-02-01']

Сейчас это то, что у меня есть:

months = []
for y in range(2010, 2016):
    for m in range(1, 13):
        if (y == 2010) and m < 8:
            continue
        if (y == 2016) and m > 2:
            continue
        month = '%s-%s-01' % (y, ('0%s' % (m)) if m < 10 else m)
        months.append(month)

Каким будет лучший способ сделать это?

Ответ 1

dateutil.relativedelta здесь.

Я оставил форматирование как упражнение.

from dateutil.relativedelta import relativedelta
import datetime

result = []

today = datetime.date.today()
current = datetime.date(2010, 8, 1)    

while current <= today:
    result.append(current)
    current += relativedelta(months=1)

Ответ 2

использовать datetime и timedelta стандартные модули Python - без установки каких-либо новых библиотек

from datetime import datetime, timedelta

now = datetime(datetime.now().year, datetime.now().month, 1)
ctr = datetime(2010, 8, 1)
list = [ctr.strftime('%Y-%m-%d')]

while ctr <= now:
    ctr += timedelta(days=32)
    list.append( datetime(ctr.year, ctr.month, 1).strftime('%Y-%m-%d') )

Я добавляю 32 дней для ввода нового месяца каждый раз (самые длинные месяцы 31 дней)

Ответ 3

Я посмотрел документацию dateutil. Оказывается, это обеспечивает еще более удобный способ, чем использование dateutil.relativedelta: правила повторения (примеры)

Для поставленной задачи это так же просто, как

from dateutil.rrule import *
from datetime import date

months = map(
    date.isoformat,
    rrule(MONTHLY, dtstart=date(2010, 8, 1), until=date.today())
)

мелкий шрифт

Обратите внимание, что мы немного обманываем, здесь. Элементы dateutil.rrule.rrule имеют тип datetime.datetime, даже если мы передаем dtstart и until типа datetime.date, как мы это делали выше. Я позволил map передавать их в функцию date isoformat, которая просто превращает их в строки, как если бы это были просто даты без какой-либо информации о времени суток.

Поэтому, казалось бы, эквивалентное понимание списка

[day.isoformat()
    for day in rrule(MONTHLY, dtstart=date(2010, 8, 1), until=date.today())]

вернет список вроде

['2010-08-01T00:00:00',
 '2010-09-01T00:00:00',
 '2010-10-01T00:00:00',
 '2010-11-01T00:00:00',
 ⋮
 '2015-12-01T00:00:00',
 '2016-01-01T00:00:00',
 '2016-02-01T00:00:00']

Таким образом, если мы хотим использовать понимание списка вместо map, мы должны сделать что-то вроде

[dt.date().isoformat()
    for dt in rrule(MONTHLY, dtstart=date(2010, 8, 1), until=date.today())]

Ответ 4

Вы можете уменьшить количество операторов if до двух строк вместо четырех строк, потому что второй оператор if, который делает то же самое с предыдущим оператором if, немного избыточен.

if (y == 2010 and m < 8) or (y == 2016 and m > 2):
    continue

Ответ 5

Я не знаю, лучше ли это, но такой подход можно считать более "питоническим":

months = [
    '{}-{:0>2}-01'.format(year, month)
        for year in xrange(2010, 2016 + 1)
        for month in xrange(1, 12 + 1)
        if not (year <= 2010 and month < 8 or year >= 2016 and month > 2)
]

Основные отличия здесь:

  • Поскольку мы хотим, чтобы итерация создавала список, используйте описание списка вместо объединения элементов списка в цикле for.
  • Вместо того, чтобы явно различать цифры ниже 10 и числа 10 и выше, используйте возможности формата спецификации формата мини-языка для .format() метод str, чтобы указать
  • xrange вместо range возвращает генератор вместо списка, так что итерационные значения могут быть получены по мере их потребления и не должны храниться в памяти. (Не важно для диапазонов это малое, но это хорошая идея, чтобы привыкнуть к этому в Python 2.) Примечание: в Python 3 нет функции xrange и range уже возвращает генератор вместо списка.
  • Сделайте + 1 для верхних границ явным. Это облегчает для читателей кода кода распознавание того, что мы хотим указать инклюзивную привязку к методу (range или xrange), который рассматривает верхнюю границу как исключительную. В противном случае они могут задаться вопросом, что такое сделка с номером 13.

Ответ 6

У меня есть другой способ использования datetime, timedelta и calender:

from calendar import monthrange
from datetime import datetime, timedelta

def monthdelta(d1, d2):
    delta = 0
    while True:
        mdays = monthrange(d1.year, d1.month)[1]
        d1 += timedelta(days=mdays)
        if d1 <= d2:
            delta += 1
        else:
            break
    return delta

start_date = datetime(2016, 1, 1)
end_date = datetime(2016, 12, 1)

num_months = [i-12 if i>12 else i for i in range(start_date.month, monthdelta(start_date, end_date)+start_date.month+1)]
monthly_daterange = [datetime(start_date.year,i, start_date.day, start_date.hour) for i in num_months]

Ответ 7

Другой подход, который не требует никаких дополнительных библиотек, ни вложенных, ни циклов while. Просто конвертируйте ваши даты в абсолютное количество месяцев из некоторой контрольной точки (это может быть любая дата на самом деле, но для простоты мы можем использовать 1 января 0001). Например,

a=datetime.date(2010,2,5)
abs_months = a.year * 12 + a.month

Когда у вас есть число, представляющее месяц, в котором вы находитесь, вы можете просто использовать range для циклического перебора месяцев, а затем преобразовать обратно:

Решение обобщенной проблемы:

import datetime

def range_of_months(start_date, end_date):
    months = []
    for i in range(start_date.year * 12 + start_date.month, end_date.year*12+end_date.month + 1)
        months.append(datetime.date((i-13) // 12 + 1, (i-1) % 12 + 1, 1))
    return months

Дополнительные примечания/объяснения:

Здесь // делит округление до ближайшего целого числа, а % 12 дает остаток при делении на 12, например, 13 % 12 - это 1.

(Обратите также внимание, что в приведенном выше date.year *12 + date.month не приводится количество месяцев с 1 января 0001 года. Например, если date = datetime.datetime(1,1,1), то date.year * 12 + date.month дает 13. Если бы я хотел сделать фактическое количество месяцев Мне нужно было бы вычесть 1 из года и месяца, но это только усложнило бы вычисления. Все, что имеет значение, - это то, что у нас есть последовательный способ преобразования в и из некоторого целочисленного представления того, в каком месяце мы находимся.)