La mejor manera de encontrar los meses entre dos fechas.

Tengo la necesidad de poder encontrar con precisión los meses entre dos fechas en python. Tengo una solución que funciona pero no es muy buena (como en elegante) o rápida.

dateRange = [datetime.strptime(dateRanges[0], "%Y-%m-%d"), datetime.strptime(dateRanges[1], "%Y-%m-%d")] months = [] tmpTime = dateRange[0] oneWeek = timedelta(weeks=1) tmpTime = tmpTime.replace(day=1) dateRange[0] = tmpTime dateRange[1] = dateRange[1].replace(day=1) lastMonth = tmpTime.month months.append(tmpTime) while tmpTime < dateRange[1]: if lastMonth != 12: while tmpTime.month = lastMonth: tmpTime += oneWeek tmpTime = tmpTime.replace(day=1) months.append(tmpTime) lastMonth = tmpTime.month 

Así que, solo para explicar, lo que estoy haciendo aquí es tomar las dos fechas y convertirlas del formato iso en objetos datetime de python. Luego hago un bucle para agregar una semana al objeto de fecha y hora de inicio y verifico si el valor numérico del mes es mayor (a menos que el mes sea diciembre, luego verifica si la fecha es menor). de meses y sigue en bucle hasta que llegue a mi fecha de finalización.

Funciona perfectamente, simplemente no parece ser una buena manera de hacerlo …

Actualización 2018-04-20: parece que OP @Joshkunz estaba pidiendo encontrar qué meses están entre dos fechas, en lugar de “cuántos meses” están entre dos fechas. Así que no estoy seguro de por qué @JohnLaRooy se ha votado más de 100 veces. @Joshkunz indicó en el comentario debajo de la pregunta original que quería las fechas reales [o los meses], en lugar de encontrar el número total de meses .

Así apareció la pregunta deseada, entre dos fechas 2018-04-11 a 2018-06-01

 Apr 2018, May 2018, June 2018 

¿Y qué pasa si es entre el 2014-04-11 y el 2018-06-01 ? Entonces la respuesta sería

 Apr 2014, May 2014, ..., Dec 2014, Jan 2015, ..., Jan 2018, ..., June 2018 

Por eso tuve el siguiente pseudo código hace muchos años. Simplemente sugirió usar los dos meses como puntos finales y recorrerlos, incrementando en un mes a la vez. @Joshkunz mencionó que quería los “meses” y también mencionó que quería las “fechas”, sin saber exactamente, era difícil escribir el código exacto, pero la idea es usar un simple bucle para recorrer los puntos finales, y incrementando un mes a la vez.

La respuesta hace 8 años en 2010:

Si agrega por una semana, entonces funcionará aproximadamente 4.35 veces el trabajo según sea necesario. ¿Por qué no solo?

 1. get start date in array of integer, set it to i: [2008, 3, 12], and change it to [2008, 3, 1] 2. get end date in array: [2010, 10, 26] 3. add the date to your result by parsing i increment the month in i if month is >= 13, then set it to 1, and increment the year by 1 until either the year in i is > year in end_date, or (year in i == year in end_date and month in i > month in end_date) 

solo el código pseduo por ahora, no lo he probado, pero creo que la idea en la misma línea funcionará.

Comience por definir algunos casos de prueba, luego verá que la función es muy simple y no necesita bucles.

 from datetime import datetime def diff_month(d1, d2): return (d1.year - d2.year) * 12 + d1.month - d2.month assert diff_month(datetime(2010,10,1), datetime(2010,9,1)) == 1 assert diff_month(datetime(2010,10,1), datetime(2009,10,1)) == 12 assert diff_month(datetime(2010,10,1), datetime(2009,11,1)) == 11 assert diff_month(datetime(2010,10,1), datetime(2009,8,1)) == 14 

Debe agregar algunos casos de prueba a su pregunta, ya que hay muchos casos de esquina posibles que cubrir: hay más de una forma de definir el número de meses entre dos fechas.

Una línea para encontrar una lista de tiempos de datos, incrementada por mes, entre dos fechas.

 import datetime from dateutil.rrule import rrule, MONTHLY strt_dt = datetime.date(2001,1,1) end_dt = datetime.date(2005,6,1) dates = [dt for dt in rrule(MONTHLY, dtstart=strt_dt, until=end_dt)] 

Esto funcionó para mí …

 from datetime import datetime from dateutil import relativedelta date1 = datetime.strptime('2011-08-15 12:00:00', '%Y-%m-%d %H:%M:%S') date2 = datetime.strptime('2012-02-15', '%Y-%m-%d') r = relativedelta.relativedelta(date2, date1) r.months * (r.years+1) 

Obtenga el mes de finalización (en relación con el año y el mes del mes de inicio ex: 2011 enero = 13 si su fecha de inicio comienza en octubre de 2010) y luego genere las fechas de inicio del mes de inicio y el mes de finalización, de este modo:

 dt1, dt2 = dateRange start_month=dt1.month end_months=(dt2.year-dt1.year)*12 + dt2.month+1 dates=[datetime.datetime(year=yr, month=mn, day=1) for (yr, mn) in ( ((m - 1) / 12 + dt1.year, (m - 1) % 12 + 1) for m in range(start_month, end_months) )] 

Si ambas fechas son el mismo año, también podría escribirse simplemente como:

 dates=[datetime.datetime(year=dt1.year, month=mn, day=1) for mn in range(dt1.month, dt2.month + 1)] 

Puede calcularlo fácilmente usando rrule del módulo dateutil :

 from dateutil import rrule from datetime import date print(list(rrule.rrule(rrule.MONTHLY, dtstart=date(2013, 11, 1), until=date(2014, 2, 1)))) 

Te regalaré:

  [datetime.datetime(2013, 11, 1, 0, 0), datetime.datetime(2013, 12, 1, 0, 0), datetime.datetime(2014, 1, 1, 0, 0), datetime.datetime(2014, 2, 1, 0, 0)] 

Este post lo clava! Utilice dateutil.relativedelta .

 from datetime import datetime from dateutil import relativedelta date1 = datetime.strptime(str('2011-08-15 12:00:00'), '%Y-%m-%d %H:%M:%S') date2 = datetime.strptime(str('2012-02-15'), '%Y-%m-%d') r = relativedelta.relativedelta(date2, date1) r.months 

Un poco un poco de solución prettificada por @ Vin-G.

 import datetime def monthrange(start, finish): months = (finish.year - start.year) * 12 + finish.month + 1 for i in xrange(start.month, months): year = (i - 1) / 12 + start.year month = (i - 1) % 12 + 1 yield datetime.date(year, month, 1) 

Intenta algo como esto. Actualmente incluye el mes si ambas fechas están en el mismo mes.

 from datetime import datetime,timedelta def months_between(start,end): months = [] cursor = start while cursor <= end: if cursor.month not in months: months.append(cursor.month) cursor += timedelta(weeks=1) return months 

La salida se ve como:

 >>> start = datetime.now() - timedelta(days=120) >>> end = datetime.now() >>> months_between(start,end) [6, 7, 8, 9, 10] 

Puedes usar python-dateutil . Ver Python: Diferencia de 2 tiempos de datos en meses.

Hay una solución simple basada en 360 días, donde todos los meses tienen 30 días. Se adapta a la mayoría de los casos de uso en los que, dadas dos fechas, debe calcular la cantidad de meses completos más los días restantes.

 from datetime import datetime, timedelta def months_between(start_date, end_date): #Add 1 day to end date to solve different last days of month s1, e1 = start_date , end_date + timedelta(days=1) #Convert to 360 days s360 = (s1.year * 12 + s1.month) * 30 + s1.day e360 = (e1.year * 12 + e1.month) * 30 + e1.day #Count days between the two 360 dates and return tuple (months, days) return divmod(e360 - s360, 30) print "Counting full and half months" print months_between( datetime(2012, 01, 1), datetime(2012, 03, 31)) #3m print months_between( datetime(2012, 01, 1), datetime(2012, 03, 15)) #2m 15d print months_between( datetime(2012, 01, 16), datetime(2012, 03, 31)) #2m 15d print months_between( datetime(2012, 01, 16), datetime(2012, 03, 15)) #2m print "Adding +1d and -1d to 31 day month" print months_between( datetime(2011, 12, 01), datetime(2011, 12, 31)) #1m 0d print months_between( datetime(2011, 12, 02), datetime(2011, 12, 31)) #-1d => 29d print months_between( datetime(2011, 12, 01), datetime(2011, 12, 30)) #30d => 1m print "Adding +1d and -1d to 29 day month" print months_between( datetime(2012, 02, 01), datetime(2012, 02, 29)) #1m 0d print months_between( datetime(2012, 02, 02), datetime(2012, 02, 29)) #-1d => 29d print months_between( datetime(2012, 02, 01), datetime(2012, 02, 28)) #28d print "Every month has 30 days - 26/M to 5/M+1 always counts 10 days" print months_between( datetime(2011, 02, 26), datetime(2011, 03, 05)) print months_between( datetime(2012, 02, 26), datetime(2012, 03, 05)) print months_between( datetime(2012, 03, 26), datetime(2012, 04, 05)) 

También puede utilizar la biblioteca de flechas . Este es un ejemplo simple:

 from datetime import datetime import arrow start = datetime(2014, 1, 17) end = datetime(2014, 6, 20) for d in arrow.Arrow.range('month', start, end): print d.month, d.format('MMMM') 

Esto imprimirá:

 1 January 2 February 3 March 4 April 5 May 6 June 

¡Espero que esto ayude!

Defina un “mes” como 1/12 año, luego haga esto:

 def month_diff(d1, d2): """Return the number of months between d1 and d2, such that d2 + month_diff(d1, d2) == d1 """ diff = (12 * d1.year + d1.month) - (12 * d2.year + d2.month) return diff 

Puede intentar definir un mes como “un período de 29, 28, 30 o 31 días (según el año)”. Pero tú haces eso, tienes un problema adicional que resolver.

Si bien, por lo general, está claro que el 15 de junio + 1 mes debe ser el 15 de julio, por lo general no está claro si el 30 de enero + 1 mes es en febrero o marzo. En este último caso, puede ser obligado a calcular la fecha como el 30 de febrero y luego “corregirla” al 2 de marzo. Pero cuando haces eso, encontrarás que el 2 de marzo – 1 mes es claramente el 2 de febrero. Ergo, reductio ad absurdum (esta operación no está bien definida).

Mi solución simple:

 import datetime def months(d1, d2): return d1.month - d2.month + 12*(d1.year - d2.year) d1 = datetime.datetime(2009, 9, 26) d2 = datetime.datetime(2019, 9, 26) print(months(d1, d2)) 
 #This definition gives an array of months between two dates. import datetime def MonthsBetweenDates(BeginDate, EndDate): firstyearmonths = [mn for mn in range(BeginDate.month, 13)]

lastyearmonths = [mn for mn in range(1, EndDate.month+1)]

months = [mn for mn in range(1, 13)]

numberofyearsbetween = EndDate.year - BeginDate.year - 1

return firstyearmonths + months * numberofyearsbetween + lastyearmonths

#example BD = datetime.datetime.strptime("2000-35", '%Y-%j') ED = datetime.datetime.strptime("2004-200", '%Y-%j') MonthsBetweenDates(BD, ED)

al igual que la función de range , cuando el mes es 13 , vaya al próximo año

 def year_month_range(start_date, end_date): ''' start_date: datetime.date(2015, 9, 1) or datetime.datetime end_date: datetime.date(2016, 3, 1) or datetime.datetime return: datetime.date list of 201509, 201510, 201511, 201512, 201601, 201602 ''' start, end = start_date.strftime('%Y%m'), end_date.strftime('%Y%m') assert len(start) == 6 and len(end) == 6 start, end = int(start), int(end) year_month_list = [] while start < end: year, month = divmod(start, 100) if month == 13: start += 88 # 201513 + 88 = 201601 continue year_month_list.append(datetime.date(year, month, 1)) start += 1 return year_month_list 

ejemplo en shell de python

 >>> import datetime >>> s = datetime.date(2015,9,1) >>> e = datetime.date(2016, 3, 1) >>> year_month_set_range(s, e) [datetime.date(2015, 11, 1), datetime.date(2015, 9, 1), datetime.date(2016, 1, 1), datetime.date(2016, 2, 1), datetime.date(2015, 12, 1), datetime.date(2015, 10, 1)] 

Por lo general, 90 días NO son 3 meses literalmente, solo una referencia.

Entonces, finalmente, debe verificar si los días son mayores que 15 para agregar +1 al contador de mes. O mejor aún, agregue otro elif con contador de medio mes.

De esta otra respuesta de stackoverflow finalmente he terminado con eso:

 #/usr/bin/env python # -*- coding: utf8 -*- import datetime from datetime import timedelta from dateutil.relativedelta import relativedelta import calendar start_date = datetime.date.today() end_date = start_date + timedelta(days=111) start_month = calendar.month_abbr[int(start_date.strftime("%m"))] print str(start_date) + " to " + str(end_date) months = relativedelta(end_date, start_date).months days = relativedelta(end_date, start_date).days print months, "months", days, "days" if days > 16: months += 1 print "around " + str(months) + " months", "(", for i in range(0, months): print calendar.month_abbr[int(start_date.strftime("%m"))], start_date = start_date + relativedelta(months=1) print ")" 

Salida:

 2016-02-29 2016-06-14 3 months 16 days around 4 months ( Feb Mar Apr May ) 

Me he dado cuenta de que eso no funciona si agrega más de los días que quedan en el año actual, y eso es inesperado.

Aquí es cómo hacer esto con Pandas FWIW:

 import pandas as pd pd.date_range("1990/04/03", "2014/12/31", freq="MS") DatetimeIndex(['1990-05-01', '1990-06-01', '1990-07-01', '1990-08-01', '1990-09-01', '1990-10-01', '1990-11-01', '1990-12-01', '1991-01-01', '1991-02-01', ... '2014-03-01', '2014-04-01', '2014-05-01', '2014-06-01', '2014-07-01', '2014-08-01', '2014-09-01', '2014-10-01', '2014-11-01', '2014-12-01'], dtype='datetime64[ns]', length=296, freq='MS') 

Observe que comienza con el mes posterior a la fecha de inicio dada.

Se puede hacer usando datetime.timedelta, donde calender.monthrange puede obtener el número de días para saltar al próximo mes. los intervalos de mes entre semana (0-6 ~ lunes a domingo) y la cantidad de días (28-31) para un año y mes dados.
Por ejemplo: monthrange (2017, 1) devuelve (6,31).

Aquí está el script que usa esta lógica para iterar entre dos meses.

 from datetime import timedelta import datetime as dt from calendar import monthrange def month_iterator(start_month, end_month): start_month = dt.datetime.strptime(start_month, '%Y-%m-%d').date().replace(day=1) end_month = dt.datetime.strptime(end_month, '%Y-%m-%d').date().replace(day=1) while start_month <= end_month: yield start_month start_month = start_month + timedelta(days=monthrange(start_month.year, start_month.month)[1]) 

`

Suponiendo que upperDate sea siempre posterior a lowerDate y que ambos sean objetos datetime.date:

 if lowerDate.year == upperDate.year: monthsInBetween = range( lowerDate.month + 1, upperDate.month ) elif upperDate.year > lowerDate.year: monthsInBetween = range( lowerDate.month + 1, 12 ) for year in range( lowerDate.year + 1, upperDate.year ): monthsInBetween.extend( range(1,13) ) monthsInBetween.extend( range( 1, upperDate.month ) ) 

No he probado esto a fondo, pero parece que debería hacer el truco.

Aquí hay un método:

 def months_between(start_dt, stop_dt): month_list = [] total_months = 12*(stop_dt.year-start_dt.year)+(stop_dt.month-start_d.month)+1 if total_months > 0: month_list=[ datetime.date(start_dt.year+int((start_dt+i-1)/12), ((start_dt-1+i)%12)+1, 1) for i in xrange(0,total_months) ] return month_list 

Esto es primero calcular el número total de meses entre las dos fechas, inclusive. Luego crea una lista utilizando la primera fecha como base y realiza la aritmética de modula para crear los objetos de fecha.

De hecho, necesitaba hacer algo muy parecido ahora.

Terminé escribiendo una función que devuelve una lista de tuplas que indica el start y el end de cada mes entre dos conjuntos de fechas, por lo que pude escribir algunas consultas SQL en la parte posterior para los totales mensuales de ventas, etc.

Estoy seguro de que puede mejorarlo alguien que sabe lo que está haciendo, pero espero que ayude …

El valor devuelto tiene el aspecto siguiente (generación para hoy: 365 días hasta hoy como ejemplo)

 [ (datetime.date(2013, 5, 1), datetime.date(2013, 5, 31)), (datetime.date(2013, 6, 1), datetime.date(2013, 6, 30)), (datetime.date(2013, 7, 1), datetime.date(2013, 7, 31)), (datetime.date(2013, 8, 1), datetime.date(2013, 8, 31)), (datetime.date(2013, 9, 1), datetime.date(2013, 9, 30)), (datetime.date(2013, 10, 1), datetime.date(2013, 10, 31)), (datetime.date(2013, 11, 1), datetime.date(2013, 11, 30)), (datetime.date(2013, 12, 1), datetime.date(2013, 12, 31)), (datetime.date(2014, 1, 1), datetime.date(2014, 1, 31)), (datetime.date(2014, 2, 1), datetime.date(2014, 2, 28)), (datetime.date(2014, 3, 1), datetime.date(2014, 3, 31)), (datetime.date(2014, 4, 1), datetime.date(2014, 4, 30)), (datetime.date(2014, 5, 1), datetime.date(2014, 5, 31))] 

Código de la siguiente manera (tiene algunas cosas de depuración que pueden ser eliminadas):

 #! /usr/env/python import datetime def gen_month_ranges(start_date=None, end_date=None, debug=False): today = datetime.date.today() if not start_date: start_date = datetime.datetime.strptime( "{0}/01/01".format(today.year),"%Y/%m/%d").date() # start of this year if not end_date: end_date = today if debug: print("Start: {0} | End {1}".format(start_date, end_date)) # sense-check if end_date < start_date: print("Error. Start Date of {0} is greater than End Date of {1}?!".format(start_date, end_date)) return None date_ranges = [] # list of tuples (month_start, month_end) current_year = start_date.year current_month = start_date.month while current_year <= end_date.year: next_month = current_month + 1 next_year = current_year if next_month > 12: next_month = 1 next_year = current_year + 1 month_start = datetime.datetime.strptime( "{0}/{1}/01".format(current_year, current_month),"%Y/%m/%d").date() # start of month month_end = datetime.datetime.strptime( "{0}/{1}/01".format(next_year, next_month),"%Y/%m/%d").date() # start of next month month_end = month_end+datetime.timedelta(days=-1) # start of next month less one day range_tuple = (month_start, month_end) if debug: print("Month runs from {0} --> {1}".format( range_tuple[0], range_tuple[1])) date_ranges.append(range_tuple) if current_month == 12: current_month = 1 current_year += 1 if debug: print("End of year encountered, resetting months") else: current_month += 1 if debug: print("Next iteration for {0}-{1}".format( current_year, current_month)) if current_year == end_date.year and current_month > end_date.month: if debug: print("Final month encountered. Terminating loop") break return date_ranges if __name__ == '__main__': print("Running in standalone mode. Debug set to True") from pprint import pprint pprint(gen_month_ranges(debug=True), indent=4) pprint(gen_month_ranges(start_date=datetime.date.today()+datetime.timedelta(days=-365), debug=True), indent=4) 

Suponiendo que quisiera saber la “fracción” del mes en que estaban las fechas, lo que hice, entonces necesita trabajar un poco más.

 from datetime import datetime, date import calendar def monthdiff(start_period, end_period, decimal_places = 2): if start_period > end_period: raise Exception('Start is after end') if start_period.year == end_period.year and start_period.month == end_period.month: days_in_month = calendar.monthrange(start_period.year, start_period.month)[1] days_to_charge = end_period.day - start_period.day+1 diff = round(float(days_to_charge)/float(days_in_month), decimal_places) return diff months = 0 # we have a start date within one month and not at the start, and an end date that is not # in the same month as the start date if start_period.day > 1: last_day_in_start_month = calendar.monthrange(start_period.year, start_period.month)[1] days_to_charge = last_day_in_start_month - start_period.day +1 months = months + round(float(days_to_charge)/float(last_day_in_start_month), decimal_places) start_period = datetime(start_period.year, start_period.month+1, 1) last_day_in_last_month = calendar.monthrange(end_period.year, end_period.month)[1] if end_period.day != last_day_in_last_month: # we have lest days in the last month months = months + round(float(end_period.day) / float(last_day_in_last_month), decimal_places) last_day_in_previous_month = calendar.monthrange(end_period.year, end_period.month - 1)[1] end_period = datetime(end_period.year, end_period.month - 1, last_day_in_previous_month) #whatever happens, we now have a period of whole months to calculate the difference between if start_period != end_period: months = months + (end_period.year - start_period.year) * 12 + (end_period.month - start_period.month) + 1 # just counter for any final decimal place manipulation diff = round(months, decimal_places) return diff assert monthdiff(datetime(2015,1,1), datetime(2015,1,31)) == 1 assert monthdiff(datetime(2015,1,1), datetime(2015,02,01)) == 1.04 assert monthdiff(datetime(2014,1,1), datetime(2014,12,31)) == 12 assert monthdiff(datetime(2014,7,1), datetime(2015,06,30)) == 12 assert monthdiff(datetime(2015,1,10), datetime(2015,01,20)) == 0.35 assert monthdiff(datetime(2015,1,10), datetime(2015,02,20)) == 0.71 + 0.71 assert monthdiff(datetime(2015,1,31), datetime(2015,02,01)) == round(1.0/31.0,2) + round(1.0/28.0,2) assert monthdiff(datetime(2013,1,31), datetime(2015,02,01)) == 12*2 + round(1.0/31.0,2) + round(1.0/28.0,2) 

proporciona un ejemplo que calcula la cantidad de meses entre dos fechas inclusive, incluida la fracción de cada mes en la que está la fecha. Esto significa que puede calcular cuántos meses hay entre el 2015-01-20 y el 2015-02-14 , donde la fracción de la fecha en el mes de enero está determinada por el número de días en enero; o igualmente teniendo en cuenta que la cantidad de días en febrero puede cambiar de año en año.

Para mi referencia, este código también está en github – https://gist.github.com/andrewyager/6b9284a4f1cdb1779b10

Prueba esto:

  dateRange = [datetime.strptime(dateRanges[0], "%Y-%m-%d"), datetime.strptime(dateRanges[1], "%Y-%m-%d")] delta_time = max(dateRange) - min(dateRange) #Need to use min(dateRange).month to account for different length month #Note that timedelta returns a number of days delta_datetime = (datetime(1, min(dateRange).month, 1) + delta_time - timedelta(days=1)) #min y/m/d are 1 months = ((delta_datetime.year - 1) * 12 + delta_datetime.month - min(dateRange).month) print months 

No importa en qué orden ingrese las fechas, y toma en cuenta la diferencia en la duración de los meses.

Esto funciona…

 from datetime import datetime as dt from dateutil.relativedelta import relativedelta def number_of_months(d1, d2): months = 0 r = relativedelta(d1,d2) if r.years==0: months = r.months if r.years>=1: months = 12*r.years+r.months return months #example number_of_months(dt(2017,9,1),dt(2016,8,1)) 
 from datetime import datetime def diff_month(start_date,end_date): qty_month = ((end_date.year - start_date.year) * 12) + (end_date.month - start_date.month) d_days = end_date.day - start_date.day if d_days >= 0: adjust = 0 else: adjust = -1 qty_month += adjust return qty_month diff_month(datetime.date.today(),datetime(2019,08,24)) #Examples: #diff_month(datetime(2018,02,12),datetime(2019,08,24)) = 18 #diff_month(datetime(2018,02,12),datetime(2018,08,10)) = 5 

Parece que las respuestas no son satisfactorias y desde entonces he usado mi propio código que es más fácil de entender.

 from datetime import datetime from dateutil import relativedelta date1 = datetime.strptime(str('2017-01-01'), '%Y-%m-%d') date2 = datetime.strptime(str('2019-03-19'), '%Y-%m-%d') difference = relativedelta.relativedelta(date2, date1) months = difference.months years = difference.years # add in the number of months (12) for difference in years months += 12 * difference.years months 

Podrías usar algo como:

 import datetime days_in_month = 365.25 / 12 # represent the average of days in a month by year month_diff = lambda end_date, start_date, precision=0: round((end_date - start_date).days / days_in_month, precision) start_date = datetime.date(1978, 12, 15) end_date = datetime.date(2012, 7, 9) month_diff(end_date, start_date) # should show 403.0 months