La mejor forma de encontrar los meses entre dos fechas

91

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

Entonces, solo para explicar, lo que estoy haciendo aquí es tomar las dos fechas y convertirlas del formato iso en objetos de fecha y hora de Python. Luego hago un ciclo agregando 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, entonces verifica si la fecha es menor), si el valor es mayor, lo agrego a la lista de meses y seguir avanzando hasta que llegue a mi fecha de finalización.

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

Joshkunz
fuente
¿Está preguntando el NÚMERO de meses entre dos fechas o cuáles son los meses reales?
Charles Hooper
en mi solución: no estoy incrementado por "el valor de un mes de número de segundos". Simplemente estoy incrementando el número 1 a 2, y luego de 2 a 3 más adelante.
nopolaridad
Solo quería que supiera que aunque no le gustó mi respuesta porque "tenía un bucle", seleccionó una respuesta que tiene DOS bucles. Las listas por comprensión siguen siendo bucles.
Charles Hooper

Respuestas:

0

Actualización 2018-04-20: parece que OP @Joshkunz estaba preguntando qué meses están entre dos fechas, en lugar de "cuántos meses" hay entre dos fechas. Así que no estoy seguro de por qué @JohnLaRooy es 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í que parecía que la pregunta quería, porque entre dos fechas 2018-04-11para2018-06-01

Apr 2018, May 2018, June 2018 

¿Y si está entre 2014-04-11a 2018-06-01? Entonces la respuesta sera

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, incrementándolos 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 bucle simple para recorrer los puntos finales, y aumentando un mes a la vez.

La respuesta hace 8 años en 2010:

Si agrega por una semana, aproximadamente funcionará 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 código pseduo por ahora, no lo he probado, pero creo que la idea en la misma línea funcionará.

no polaridad
fuente
1
Ok, veo algunos problemas con meses como febrero si el incremento se realiza por mes, en lugar de por semanas.
Joshkunz
No estoy incrementado por "el valor de un mes en número de segundos". Simplemente estoy incrementando el número 1a 2, y luego de 2a 3más adelante.
nopolaridad
194

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 potenciales que cubrir; hay más de una forma de definir el número de meses entre dos fechas.

John La Rooy
fuente
2
da un resultado incorrecto. resulta 1 mes entre "2015-04-30" y "2015-05-01", que en realidad es solo 1 día.
Rao
21
@Rao. por eso dije "hay más de una forma de definir el número de meses entre dos fechas". La pregunta aún carece de una definición formal. Esta es también la razón por la que sugiero que se proporcionen casos de prueba junto con la definición.
John La Rooy
2
Recomiendo agregar abs () alrededor de las sustracciones para permitir que d1 sea más pequeño que d2: return abs (d1.year - d2.year) * 12 + abs (d1.month - d2.month)
Lukas Schulze
¿Estás seguro, @LukasSchulze? Si d1 es menor que d2, de todos modos tendrías que restar ese número al primero, ¿verdad?
Lilith-Elina
Pero en mi caso, no importa si d1 <= d2 o d2 <= d1. Solo está interesado en la diferencia, sin importar cuál de las dos fechas sea menor / mayor.
Lukas Schulze
40

Un trazador de líneas para encontrar una lista de fechas y horas, incrementadas 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)]
N1B4
fuente
¡Esta! Esto se ajusta a muchas situaciones especiales extravagantes gracias al uso de rrule. Tenga en cuenta que los valores de salida son fechas y horas para que pueda convertirlos en lo que quiera (incluidas las cadenas) para que coincidan con lo que muestran los demás.
Ezekiel Kruglick
Esto falla cuando strt_dt es 2001-1-29, debido a que no hay un día bisiesto ese año.
CS
2
OP pidió una lista de los meses entre las fechas de inicio y finalización. Se perdió febrero usando su método en mi ejemplo. Por supuesto, su solución aún se puede salvar ajustando la fecha de inicio al primero del mes, por ejemplo.
CS
2
Es una buena solución y me ahorra mucho tiempo. Podemos mejorarlo dando la fecha de inicio[dt for dt in rrule(MONTHLY, bymonthday=10,dtstart=strt_dt, until=end_dt)]
Pengju Zhao
1
Sea consciente de ello. De hecho, tienen una nota en la documentación que dice que rrule podría tener un "comportamiento sorprendente cuando, por ejemplo, la fecha de inicio se produce al final del mes" (consulte la Nota en dateutil.readthedocs.io/en/stable/rrule.html ). Una forma de evitarlo es reemplazar las fechas por el primer día del mes: start_date_first = start_date.replace(day=1), end_date_first = end_date.replace(day=1)luego, la regla cuenta los meses correctamente.
Alla Sorokina
36

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 + (12*r.years)
aamir23
fuente
eliminar innecesarios str()alrededor de cadenas literales.
jfs
7
No vale nada que si el delta es más de 1 año, r.monthscomenzará desde 0
jbkkd
2
Por lo general, hago r.mes * (r.years + 1), ya que eso se ajusta a lo que @jbkkd estaba hablando
triunenature
9
@triunenature esto se ve mal, seguramente debería ser algo como r.months + (r.years * 12)
Moataz Elmasry
2
Sí, esto no funciona si las fechas están separadas por más de un año.
HansG600
11

Puede calcular esto 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)]
Nazarii Gudzovatyi
fuente
10

Obtenga el mes final (en relación con el año y el mes del mes de inicio, por ejemplo: enero de 2011 = 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 así:

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 del mismo año, también se podría escribir simplemente como:

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

¡Esta publicación 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
srodriguex
fuente
1
@edouard Como pueden ver en el ejemplo que les di, las fechas están en años diferentes. No obstante, el str()reparto que hice es completamente innecesario.
srodriguex
5
esto no funciona si las fechas cubren más de un año, debe agregarrelativedelta.relativedelta(date2, date1).years * 12
muon
delta.years*12 + delta.months
user2682863
7

Mi sencilla solución:

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))
Hottabov
fuente
solución
subestimada
4

Definir un "mes" como 1 / 12 año, a continuación, haga lo siguiente:

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 si haces eso, tienes un problema adicional que resolver.

Aunque por lo general es claro que en junio 15 ° + 1 mes de debe ser de julio de 15 º , no normalmente no es claro si de enero de 30 de ju + 1 mes es en febrero o marzo. En este último caso, puede ser obligado a calcular la fecha en febrero 30 de ju , a continuación, "correcto" que el 2 de marzo nd . Pero cuando se hace eso, usted encontrará que de marzo de 2 nd - 1 mes es claramente de febrero de 2 nd . Ergo, reductio ad absurdum (esta operación no está bien definida).

Seth
fuente
4

Existe 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 en los que, dadas dos fechas, es necesario 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))
Daniel Reis
fuente
4

Algo una pequeña solución 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)
saaj
fuente
4

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!

Luis Alberto Santana
fuente
4
from dateutil import relativedelta

relativedelta.relativedelta(date1, date2)

months_difference = (r.years * 12) + r.months
yatabani
fuente
3

Prueba algo como esto. Actualmente incluye el mes si ambas fechas coinciden con 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]
Charles Hooper
fuente
Sin embargo, esto todavía tiene el mismo enfoque de bucle, por lo que no necesariamente veo el beneficio ...
Joshkunz
1
No veo cómo eso es un problema. Los bucles no son tu enemigo.
Charles Hooper
Bueno, esto se debe hacer cada vez que hay una consulta ajax. Sé que los bucles no son el enemigo, pero parece que son una solución lenta a un problema que debería resolverse de una manera mucho más fácil.
Joshkunz
3

Aquí se explica 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')

Note que comienza con el mes posterior a la fecha de inicio indicada.

conner.xyz
fuente
2

Se puede hacer usando datetime.timedelta, donde la cantidad de días para saltar al mes siguiente se puede obtener mediante calender.monthrange. monthrange devuelve el día de la semana (0-6 ~ Mon-Sun) y el número de días (28-31) para un año y mes determinados.
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])

'

pankaj kumar
fuente
¿Podría explicar cómo esto resuelve el problema? Alentamos a las personas a agregar contexto a sus respuestas; Gracias.
Gi0rgi0s
1
Se agregó una explicación
pankaj kumar
2

Muchas personas ya te han dado buenas respuestas para resolver esto, pero no he leído ninguna usando la comprensión de listas, así que te doy lo que usé para un caso de uso similar:


def compute_months(first_date, second_date):
    year1, month1, year2, month2 = map(
        int, 
        (first_date[:4], first_date[5:7], second_date[:4], second_date[5:7])
    )

    return [
        '{:0>4}-{:0>2}'.format(year, month)
        for year in range(year1, year2 + 1)
        for month in range(month1 if year == year1 else 1, month2 + 1 if year == year2 else 13)
    ]

>>> first_date = "2016-05"
>>> second_date = "2017-11"
>>> compute_months(first_date, second_date)
['2016-05',
 '2016-06',
 '2016-07',
 '2016-08',
 '2016-09',
 '2016-10',
 '2016-11',
 '2016-12',
 '2017-01',
 '2017-02',
 '2017-03',
 '2017-04',
 '2017-05',
 '2017-06',
 '2017-07',
 '2017-08',
 '2017-09',
 '2017-10',
 '2017-11']
H4kim
fuente
1
#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)
Mehmet Ercan
fuente
1

al igual que la rangefunción, cuando el mes es 13 , pasa al año siguiente

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 python shell

>>> 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)]
WeizhongTu
fuente
1

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

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

De esta otra respuesta de stackoverflow finalmente terminé 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 )

He notado que no funciona si agrega más de los días restantes en el año actual, y eso es inesperado.

m3nda
fuente
1

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
grandia
fuente
1
from datetime import datetime
from dateutil import relativedelta

def get_months(d1, d2):
    date1 = datetime.strptime(str(d1), '%Y-%m-%d')
    date2 = datetime.strptime(str(d2), '%Y-%m-%d')
    print (date2, date1)
    r = relativedelta.relativedelta(date2, date1)
    months = r.months +  12 * r.years
    if r.days > 0:
        months += 1
    print (months)
    return  months


assert  get_months('2018-08-13','2019-06-19') == 11
assert  get_months('2018-01-01','2019-06-19') == 18
assert  get_months('2018-07-20','2019-06-19') == 11
assert  get_months('2018-07-18','2019-06-19') == 12
assert  get_months('2019-03-01','2019-06-19') == 4
assert  get_months('2019-03-20','2019-06-19') == 3
assert  get_months('2019-01-01','2019-06-19') == 6
assert  get_months('2018-09-09','2019-06-19') == 10
Yogesh Kushwaha
fuente
0

Suponiendo que upperDate es siempre posterior a lowerDate y que ambos son 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 lo he probado a fondo, pero parece que debería funcionar.

Scott
fuente
0

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 aritmética de módulo para crear los objetos de fecha.

Dan
fuente
0

De hecho, necesitaba hacer algo bastante similar en este momento.

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

Estoy seguro de que puede mejorarlo alguien que sepa lo que está haciendo, pero espero que ayude ...

El valor devuelto se ve de la siguiente manera (generando 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))]

Codifique de la siguiente manera (tiene algunas cosas de depuración que se pueden eliminar):

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

Suponiendo que quisieras saber la "fracción" del mes en que estaban las fechas, lo cual hice, entonces necesitas 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 el 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 de 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

Andrew Yager
fuente
0

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 debería importar el orden en que ingrese las fechas, y tiene en cuenta la diferencia en la duración de los meses.

om_henners
fuente
Tenga en cuenta que esto no tiene en cuenta que sus fechas sean las mismas. La forma más fácil sería con if delta_time.days = 0: months = 0 si no, resto de la rutina.
om_henners
0

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))
George Pamfilis
fuente
0
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
Max Sandoval
fuente