Создайте список месяцев между интервалом в python

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

date1 = "2014-10-10"  # input start date
date2 = "2016-01-07"  # input end date
month_list = ['Oct-14', 'Nov-14', 'Dec-14', 'Jan-15', 'Feb-15', 'Mar-15', 'Apr-15', 'May-15', 'Jun-15', 'Jul-15', 'Aug-15', 'Sep-15', 'Oct-15', 'Nov-15', 'Dec-15', 'Jan-16']  # output

Ответ 1

>>> from datetime import datetime, timedelta
>>> from collections import OrderedDict
>>> dates = ["2014-10-10", "2016-01-07"]
>>> start, end = [datetime.strptime(_, "%Y-%m-%d") for _ in dates]
>>> OrderedDict(((start + timedelta(_)).strftime(r"%b-%y"), None) for _ in xrange((end - start).days)).keys()
['Oct-14', 'Nov-14', 'Dec-14', 'Jan-15', 'Feb-15', 'Mar-15', 'Apr-15', 'May-15', 'Jun-15', 'Jul-15', 'Aug-15', 'Sep-15', 'Oct-15', 'Nov-15', 'Dec-15', 'Jan-16']

Обновление: немного объяснений, как было запрошено в одном комментарии. Здесь три проблемы: анализ дат в соответствующих структурах данных (strptime); получение диапазона дат с учетом двух крайностей и шага (один месяц); форматирование выходных дат (strftime). Тип datetime перегружает оператор вычитания, поэтому end - start имеет смысл. Результатом является объект timedelta который представляет разницу между двумя датами, а атрибут .days получает эту разницу, выраженную в днях. Не существует атрибута .months, поэтому мы повторяем один раз за один раз и преобразуем даты в желаемый формат вывода. Это дает много дубликатов, которые OrderedDict удаляет, сохраняя элементы в правильном порядке.

Теперь это просто и красно, потому что позволяет модулю datetime выполнять всю работу, но это также ужасно неэффективно. Мы призываем много методов для каждого дня, пока нам нужно только выводить месяцы. Если производительность не является проблемой, приведенный выше код будет в порядке. В противном случае нам придется работать немного больше. Давайте сравним приведенную выше реализацию с более эффективной:

from datetime import datetime, timedelta
from collections import OrderedDict

dates = ["2014-10-10", "2016-01-07"]

def monthlist_short(dates):
    start, end = [datetime.strptime(_, "%Y-%m-%d") for _ in dates]
    return OrderedDict(((start + timedelta(_)).strftime(r"%b-%y"), None) for _ in xrange((end - start).days)).keys()

def monthlist_fast(dates):
    start, end = [datetime.strptime(_, "%Y-%m-%d") for _ in dates]
    total_months = lambda dt: dt.month + 12 * dt.year
    mlist = []
    for tot_m in xrange(total_months(start)-1, total_months(end)):
        y, m = divmod(tot_m, 12)
        mlist.append(datetime(y, m+1, 1).strftime("%b-%y"))
    return mlist

assert monthlist_fast(dates) == monthlist_short(dates)

if __name__ == "__main__":
    from timeit import Timer
    for func in "monthlist_short", "monthlist_fast":
        print func, Timer("%s(dates)" % func, "from __main__ import dates, %s" % func).timeit(1000)

На моем ноутбуке я получаю следующий вывод:

monthlist_short 2.3209939003
monthlist_fast 0.0774540901184

Конкретная реализация примерно в 30 раз медленнее, поэтому я бы не рекомендовал ее в критически важных приложениях :)

Ответ 2

Я нашел очень лаконичный способ сделать это с Пандами, поделившись информацией на случай, если это кому-нибудь поможет:


ОБНОВЛЕНИЕ: Я получил его в одну строку с помощью этого сообщения :)

pd.date_range('2014-10-10','2016-01-07', 
              freq='MS').strftime("%Y-%b").tolist()

СТАРЫЙ ОТВЕТ:

daterange = pd.date_range('2014-10-10','2016-01-07' , freq='1M') 
daterange = daterange.union([daterange[-1] + 1])  
daterange = [d.strftime('%y-%b') for d in daterange]

Вторая строка предотвращает удаление последней даты из списка.

Ответ 3

Вы должны использовать Календарь и Datetime

import calendar
from datetime import *
date1 = datetime.strptime("2014-10-10", "%Y-%m-%d")
date2 = datetime.strptime("2016-01-07", "%Y-%m-%d")
date1 = date1.replace(day = 1)
date2 = date2.replace(day = 1)
months_str = calendar.month_name
months = []
while date1 < date2:
    month = date1.month
    year  = date1.year
    month_str = months_str[month][0:3]
    months.append("{0}-{1}".format(month_str,str(year)[-2:]))
    next_month = month+1 if month != 12 else 1
    next_year = year + 1 if next_month == 1 else year
    date1 = date1.replace( month = next_month, year= next_year)

print months

Этот код возвращает

['Oct-14', 'Nov-14', 'Dec-14', 'Jan-14', 'Feb-15', 'Mar-15', 'Apr-15', 'May-15', 'Jun-15', 'Jul-15', 'Aug-15', 'Sep-15', 'Oct-15', 'Nov-15', 'Dec-15', 'Jan-15']

Ответ 4

С пандами у вас может быть один лайнер:

import pandas as pd

date1 = "2014-10-10"  # input start date
date2 = "2016-01-07"  # input end date

month_list = [i.strftime("%b-%y") for i in pd.date_range(start=date1, end=date2, freq='MS')]

Ответ 5

Проделав подобный материал ранее, я принял удар по решению этого. Использование отдельных компонентов для этого является более гибким и позволяет вам смешивать и сопоставлять их для разных случаев использования. Они также могут быть легче протестированы таким образом, как вы можете видеть в доктринах в iterate_months.

Также я предлагаю использовать объекты datetime.date для ввода, поскольку вы можете просто сделать больше с ними. Для этого вам придется сначала проанализировать введенную строку, но это очень легко сделать.

Разбор строк даты

def datify(date):
    if isinstance(date, datetime.date):
        return date
    elif isinstance(date, datetime.datetime):
        return date.date()
    else:
        # taken from simleo answer
        return datetime.strptime(date, "%Y-%m-%d")

Во-первых, мы повторяем месяцы

import datetime


def iterate_months(start_date, end_date):
    """Iterate monthly between two given dates.

    Emitted will be the first day of each month.

    >>> list(iterate_months(datetime.date(1999, 11, 1),
    ...                     datetime.date(2000, 2, 1)))
    [datetime.date(1999, 11, 1), datetime.date(1999, 12, 1),\
 datetime.date(2000, 1, 1), datetime.date(2000, 2, 1)]

    """
    assert isinstance(start_date, datetime.date)
    assert isinstance(end_date, datetime.date)
    assert start_date < end_date

    year = start_date.year
    month = start_date.month
    while True:
        current = datetime.date(year, month, 1)
        yield current
        if current.month == end_date.month and current.year == end_date.year:
            break
        else:
            month = ((month + 1) % 12) or 12
            if month == 1:
                year += 1


if __name__ == '__main__':
    import doctest
    doctest.testmod()

Чтобы отформатировать даты, используйте что-то вроде этого

def format_month(date):
    return date.strftime(r"%b-%y")

Объединяя все это

start = datify("2014-10-10")
end = datify("2016-01-07")

for entry in iterate_months(start, end):
    print format_month(entry)

Или сохраните его как список:

result = list(iterate_months(start, end))

Ответ 6

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

date1 = "2014-10-10"
date2 = "2016-01-07"

y0 = int( date1.split('-')[0] ) # 2014
y1 = int( date2.split('-')[0] ) # 2016

m0 = int( date1.split('-')[1] ) - 1 # 10-1 --> 9 because will be used for indexing
m1 = int( date2.split('-')[1] ) - 1 # 01-1 --> 0 because will be used for indexing

months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
result = []
start = m0
for y in range(y0, y1+1):
    for m in range(start,12):
        result.append( str( months[m  % 12])+'-'+str(y) )
        if y == y1 and (m % 12) == m1:
            break
    start = 0

print result

$ python date.py

['Oct-2014', 'Nov-2014', 'Dec-2014', 'Jan-2015', 'Feb-2015', 'Mar-2015', 'Apr-2015', 'May-2015', 'Jun-2015', 'Jul-2015', 'Aug-2015', 'Sep-2015', 'Oct-2015', 'Nov-2015', 'Dec-2015', 'Jan-2016']

Ответ 7

Если вы хотите сохранить ваши даты в формате Python, вы можете попробовать использовать to_pydatetime().

import pandas as pd
from datetime import datetime

datemin = datetime(2010, 1, 1)
datemax = datetime(2019, 12, 31)

# First day of month
pd.date_range(datemin, datemax, freq='MS').to_pydatetime().tolist()

# Last day of month
pd.date_range(datemin, datemax, freq='M').to_pydatetime().tolist()

Список смещенных псевдонимов

Ответ 8

Вот мое решение с простым пониманием списка, которое использует range чтобы знать, где должны начинаться и заканчиваться месяцы

from datetime import datetime as dt
sd = dt.strptime('2014-10-10', "%Y-%m-%d") 
ed = dt.strptime('2016-01-07', "%Y-%m-%d") 

lst = [dt.strptime('%2.2d-%2.2d' % (y, m), '%Y-%m').strftime('%b-%y') \
       for y in xrange(sd.year, ed.year+1) \
       for m in xrange(sd.month if y==sd.year else 1, ed.month+1 if y == ed.year else 13)]

print lst

производит

['Oct-14', 'Nov-14', 'Dec-14', 'Jan-15', 'Feb-15', 'Mar-15', 'Apr-15', 'May-15', 'Jun-15', 'Jul-15', 'Aug-15', 'Sep-15', 'Oct-15', 'Nov-15', 'Dec-15', 'Jan-16']