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 dateutil . Ver Python: diferencia de 2 fechas en meses
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