Obtener (año, mes) para los últimos X meses

Tengo algo muy simple que hacer en python: necesito una lista de tuplas (year,month) para los últimos x meses que comienzan (e incluyen) a partir de hoy. Entonces, para x = 10 y hoy (julio de 2011), el comando debe mostrar:

 [(2011, 7), (2011, 6), (2011, 5), (2011, 4), (2011, 3), (2011, 2), (2011, 1), (2010, 12), (2010, 11), (2010, 10)] 

Solo se debe usar la implementación de fecha y hora predeterminada de python. Se me ocurrió la siguiente solución:

 import datetime [(d.year, d.month) for d in [datetime.date.today()-datetime.timedelta(weeks=4*i) for i in range(0,10)]] 

Esta solución genera la solución correcta para mis casos de prueba, pero no estoy cómoda con esta solución: se supone que un mes tiene cuatro semanas y esto simplemente no es cierto. Podría reemplazar las weeks=4 con days=30 lo que haría una mejor solución pero aún no es correcta.

La otra solución que me vino a la mente es usar cálculos simples y restar 1 de un contador de meses y si el contador de mes es 0, restar 1 de un contador de año. El problema con esta solución: requiere más código y tampoco es muy legible.

Entonces, ¿cómo se puede hacer esto correctamente?

No lo veo documentado en ninguna parte, pero time.mktimetime.mktime ” en el año correcto cuando se le den valores de mes fuera de rango, incluyendo valores negativos:

 x = 10 now = time.localtime() print [time.localtime(time.mktime((now.tm_year, now.tm_mon - n, 1, 0, 0, 0, 0, 0, 0)))[:2] for n in range(x)] 

Lo mejor sería usar las funciones de división entera ( // ) y módulo ( % ), que representan el mes por el número de meses desde el año 0:

 months = year * 12 + month - 1 # Months since year 0 minus 1 tuples = [((months - i) // 12, (months - i) % 12 + 1) for i in range(10)] 

El - 1 en la expresión de los months se requiere para obtener la respuesta correcta cuando agregamos 1 al resultado de la función de módulo más adelante para obtener la indexación 1 (es decir, los meses van de 1 a 12 en lugar de 0 a 11).

O quizás quieras crear un generador:

 def year_month_tuples(year, month): months = year * 12 + month - 1 # -1 to reflect 1-indexing while True: yield (months // 12, months % 12 + 1) # +1 to reflect 1-indexing months -= 1 # next time we want the previous month 

Que podría ser utilizado como:

 >>> tuples = year_month_tuples(2011, 7) >>> [tuples.next() for i in range(10)] 

Utilizando relativedelta

 import datetime from dateutil.relativedelta import relativedelta def get_last_months(start_date, months): for i in range(months): yield (start_date.year,start_date.month) start_date += relativedelta(months = -1) >>> X = 10 >>> [i for i in get_last_months(datetime.datetime.today(), X)] >>> [(2013, 2), (2013, 1), (2012, 12), (2012, 11), (2012, 10), (2012, 9), (2012, 8), (2012, 7), (2012, 6), (2012, 5)] 

Si creas una función para hacer los cálculos matemáticos de fecha, se vuelve casi tan agradable como tu implementación original:

 def next_month(this_year, this_month): if this_month == 0: return (this_year - 1, 12) else: return (this_year, this_month - 1) this_month = datetime.date.today().month() this_year = datetime.date.today().year() for m in range(0, 10): yield (this_year, this_month) this_year, this_month = next_month(this_year, this_month) 

Actualización : agregando una versión de timedelta todos modos, ya que se ve más bonita 🙂

 def get_years_months(start_date, months): for i in range(months): yield (start_date.year, start_date.month) start_date -= datetime.timedelta(days=calendar.monthrange(start_date.year, start_date.month)[1]) 

No necesita trabajar con timedelta ya que solo necesita el año y el mes, lo cual es fijo.

 def get_years_months(my_date, num_months): cur_month = my_date.month cur_year = my_date.year result = [] for i in range(num_months): if cur_month == 0: cur_month = 12 cur_year -= 1 result.append((cur_year, cur_month)) cur_month -= 1 return result if __name__ == "__main__": import datetime result = get_years_months(datetime.date.today(), 10) print result 

Si desea hacerlo sin las bibliotecas de fecha y hora, puede convertir a meses desde el año 0 y luego volver a convertir

 start_year = 2014 start_month = 5 end_year = 2013 end_month = 7 print list = [(a/12,a % 12+1) for a in range(12*start_year+start_month-1,12*end_year+end_month-2,-1)] 

[(2014, 5), (2014, 4), (2014, 3), (2014, 2), (2014, 1), (2013, 12), (2013, 11), (2013, 10), ( 2013, 9), (2013, 8), (2013, 7)]

O puede definir una función para obtener el último mes y luego imprimir los meses (es un poco rudimentario)

 def last_month(year_month):#format YYYY-MM aux = year_month.split('-') m = int(aux[1]) y = int(aux[0]) if m-1 == 0: return str(y-1)+"-12" else: return str(y)+"-"+str(m-1) def print_last_month(ran, year_month= str(datetime.datetime.today().year)+'-'+str(datetime.datetime.today().month)): i = 1 if ran != 10: print( last_month(year_month) ) print_last_month(i+1, year_month= last_month(year_month))