Generar lista de meses entre intervalos en python

Quiero generar una lista de python que contenga todos los meses que ocurren entre dos fechas, con la entrada y la salida con el siguiente formato:

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 

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

Actualización: un poco de explicación, según lo solicitado en un comentario. Hay tres problemas aquí: analizar las fechas en estructuras de datos apropiadas (tiempo de strptime ); obtener el rango de fechas dados los dos extremos y el paso (un mes); formateo de las fechas de salida ( strftime ). El tipo datetime sobrecarga al operador de sustracción, de modo que el end - start tiene sentido. El resultado es un objeto timedelta que representa la diferencia entre las dos fechas, y el atributo .days obtiene esta diferencia expresada en días. No hay ningún atributo .months , por lo que iteramos un día a la vez y convertimos las fechas al formato de salida deseado. Esto produce muchos duplicados, que el OrderedDict elimina mientras mantiene los artículos en el orden correcto.

Ahora esto es simple y conciso porque permite que el módulo datetime haga todo el trabajo, pero también es horriblemente ineficiente. Estamos llamando a muchos métodos para cada día, mientras que solo necesitamos generar meses. Si el rendimiento no es un problema, el código anterior estará bien. De lo contrario, tendremos que trabajar un poco más. Comparemos la implementación anterior con una más eficiente:

 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) 

En mi portátil, obtengo la siguiente salida:

 monthlist_short 2.3209939003 monthlist_fast 0.0774540901184 

La implementación concisa es aproximadamente 30 veces más lenta, por lo que no lo recomendaría en aplicaciones de tiempo crítico 🙂

Encontré una forma muy breve de hacer esto con Pandas, compartiendo en caso de que ayude a alguien:


ACTUALIZACIÓN: Lo he reducido a una sola línea con la ayuda de esta publicación 🙂

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

ANTIGUA RESPUESTA:

 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] 

La segunda línea evita que la última fecha se elimine de la lista.

Tienes que usar Calendar y Datetime

 import calendar from datetime import * date1 = datetime.strptime("2014-10-10", "%Y-%m-%d") date2 = datetime.strptime("2016-01-07", "%Y-%m-%d") 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 

Este código devuelve

 ['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'] 

Con los pandas, puedes tener una sola línea como esta:

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

Encuentre a continuación mi enfoque a este problema usando iteraciones basadas en módulos simples y divididas sin importar ningún módulo especial.

 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 dates.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'] 

Habiendo hecho cosas similares anteriormente, intenté resolver esto. El uso de distintos componentes para hacer esto es más flexible y le permite mezclarlos y combinarlos para diferentes casos de uso. También se pueden probar más fácilmente de esta manera, como se puede ver en las pruebas en iterate_months .

También sugiero usar objetos datetime.date para su entrada, ya que puede hacer más con ellos. Para hacerlo, primero deberá analizar su cadena de entrada, pero esto se hace muy fácilmente.

Analizando las cadenas de fecha

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

Primero, iteramos a través de los meses.

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

Para formatear tus fechas, usa algo como esto

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

Poniendolo todo junto

 start = datify("2014-10-10") end = datify("2016-01-07") for entry in iterate_months(start, end): print format_month(entry) 

O guárdelo como una lista:

 result = list(iterate_months(start, end)) 

Aquí está mi solución con una lista de comprensión simple que usa el range para saber dónde deben comenzar y terminar los meses

 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 

produce

 ['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']