with two tutorial number months libreria htmlcalendar example dates calendario calculate python datetime monthcalendar date-math

python - two - La mejor forma de encontrar los meses entre dos fechas



python calendar tutorial with example (26)

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

Primero, se calcula el número total de meses entre las dos fechas, inclusive. Luego crea una lista usando la primera fecha como base y realiza la aritmética de modulación para crear los objetos de fecha.

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 else: while tmpTime.month >= lastMonth: tmpTime += oneWeek tmpTime = tmpTime.replace(day=1) months.append(tmpTime) lastMonth = tmpTime.month

Así que para explicarlo, lo que estoy haciendo aquí es tomar las dos fechas y convertirlas de formato iso en objetos python datetime. Luego recorro agregando una semana al objeto datetime 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), si el valor es mayor, lo anexo a la lista de meses y seguir avanzando hasta llegar a mi fecha de finalización.

Funciona perfectamente, simplemente no parece una buena manera de hacerlo ...


Asumiendo que quería saber la "fracción" del mes en que estaban las fechas, lo cual hice, entonces necesita hacer un poco más de trabajo.

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 resuelve el número de meses entre dos fechas inclusive, incluida la fracción de cada mes en que se encuentra la fecha. Esto significa que puede calcular cuántos meses hay entre 2015-01-20 y 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 el número de días en febrero puede cambiar de un año a otro.

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


Comience por definir algunos casos de prueba, 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.


De hecho, necesitaba hacer algo bastante similar en este momento

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

Estoy seguro de que puede ser mejorado por alguien que sabe lo que están haciendo, pero espero que ayude ...

El valor devuelto tiene el siguiente aspecto (generando 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 eliminarse):

#! /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)


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 tratar de definir un mes como "un período de 29, 28, 30 o 31 días (según el año)". Pero si lo haces, tienes un problema adicional por resolver.

Si bien normalmente está claro que el 15 de junio + 1 mes debe ser el 15 de julio, no suele quedar claro si el 30 de enero + 1 mes es febrero o marzo. En este último caso, se le puede obligar a calcular la fecha como el 30 de febrero y luego "corregirla" al 2 de marzo. Pero cuando lo hagas, verá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).


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


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


Hay una solución simple basada en años de 360 ​​días, donde todos los meses tienen 30 días. Se adapta a la mayoría de los casos de uso donde, 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))


He aquí 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.


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


Obtenga el mes final (relativo al año y mes del mes de inicio, por ejemplo: enero de 2011 = 13 si su fecha de inicio comienza en 2010 oct) y luego genere los tiempos de fecha comenzando el mes de inicio y ese mes final de la siguiente manera:

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 están en 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)]



Podría 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


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

Entonces, finalmente, debe verificar si los días son más grandes que 15 para agregar un contador de +1 al mes. o mejor, agregue otro elif con un contador de medio mes.

De esta otra respuesta de 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 di cuenta de que eso no funciona si agrega más días del año en curso, y eso es inesperado.


Prueba 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 así:

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


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 qué orden ingrese las fechas, y tiene en cuenta la diferencia en las duraciones de los meses.


Suponiendo que upperDate siempre es posterior a lowerDate y ambos son datetime.date objects:

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 lo he probado a fondo, pero parece que debería hacer el truco.


También puede usar la biblioteca de arrow . 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 se imprimirá:

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

¡Espero que esto ayude!


Un trazador de líneas para encontrar una lista de fechas y horas, 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)]


Una solución algo embellecida 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)


Usted puede calcular fácilmente esto 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)]


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 el shell 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)]


¡Este post lo clava! Use 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


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

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

Apr 2018, May 2018, June 2018

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

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

Es por eso que tuve el siguiente pseudo código hace muchos años. Simplemente sugirió utilizar los dos meses como puntos finales y recorrerlos, incrementándose en un mes por 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 ciclo simple para recorrer los puntos finales, y incrementando un mes a la vez.

La respuesta hace 8 años en 2010:

Si se 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 el momento, no lo he probado, pero creo que la idea en la misma línea funcionará.


#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)]<p> lastyearmonths = [mn for mn in range(1, EndDate.month+1)]<p> months = [mn for mn in range(1, 13)]<p> numberofyearsbetween = EndDate.year - BeginDate.year - 1<p> return firstyearmonths + months * numberofyearsbetween + lastyearmonths<p> #example BD = datetime.datetime.strptime("2000-35", ''%Y-%j'') ED = datetime.datetime.strptime("2004-200", ''%Y-%j'') MonthsBetweenDates(BD, ED)


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