Melhor maneira de encontrar os meses entre duas datas

91

Tenho a necessidade de encontrar com precisão os meses entre duas datas em python. Tenho uma solução que funciona, mas não é muito boa (como no elegante) ou 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

Então, apenas para explicar, o que estou fazendo aqui é pegar as duas datas e convertê-las do formato iso em objetos datetime em Python. Em seguida, faço um loop adicionando uma semana ao objeto de data e hora de início e verifico se o valor numérico do mês é maior (a menos que o mês seja dezembro, então ele verifica se a data é menor). Se o valor for maior, eu o anexo à lista de meses e continuo repetindo até chegar à data de término.

Funciona perfeitamente, só não parece uma boa maneira de fazer ...

Joshkunz
fonte
Você está perguntando o NÚMERO de meses entre duas datas ou quais são os meses reais?
Charles Hooper,
na minha solução: não estou incrementando por "um número de segundos de um mês". Estou apenas incrementando o número 1 para 2 e de 2 para 3 mais tarde.
polaridade
Só queria que você soubesse que, embora não tenha gostado da minha resposta porque ela "tinha um loop", você selecionou uma resposta que tem DOIS loops. As compreensões de lista ainda são loops.
Charles Hooper,

Respostas:

0

Atualização 20/04/2018: parece que OP @Joshkunz estava pedindo para descobrir quais meses estão entre duas datas, em vez de "quantos meses" estão entre duas datas. Portanto, não sei por que @JohnLaRooy recebeu votos positivos mais de 100 vezes. @Joshkunz indicou no comentário sob a pergunta original que queria as datas reais [ou os meses], em vez de calcular o número total de meses .

Então, parecia que a pergunta era desejada, por entre duas datas 2018-04-11para2018-06-01

Apr 2018, May 2018, June 2018 

E se for entre 2014-04-11a 2018-06-01? Então a resposta seria

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

É por isso que tenho o seguinte pseudocódigo há muitos anos. Ele apenas sugeriu usar os dois meses como pontos finais e fazer um loop através deles, incrementando em um mês de cada vez. @Joshkunz mencionou que queria os "meses" e também mencionou que queria as "datas", sem saber exatamente, era difícil escrever o código exato, mas a ideia é usar um loop simples para percorrer os pontos finais, e incrementando um mês de cada vez.

A resposta há 8 anos em 2010:

Se adicionar por uma semana, ele executará aproximadamente 4,35 vezes o trabalho necessário. Por que não apenas:

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)

apenas código pseduo por enquanto, não testei, mas acho que a ideia na mesma linha funcionará.

não polaridade
fonte
1
Ok, vejo alguns problemas com meses como fevereiro se a incrementação for feita por mês. Em vez de semanas.
Joshkunz
Não estou incrementando "o equivalente a um mês ou número de segundos". Estou apenas aumentando o número 1para 2e de 2para 3mais tarde.
polaridade de
194

Comece definindo alguns casos de teste, então você verá que a função é muito simples e não precisa de loops

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

Você deve adicionar alguns casos de teste à sua pergunta, pois há muitos casos potenciais para cobrir - há mais de uma maneira de definir o número de meses entre duas datas.

John La Rooy
fonte
2
dá resultado errado. resulta em 1 mês entre "2015-04-30" e "2015-05-01", que na verdade é apenas 1 dia.
Rao
21
@Rao. é por isso que eu disse "há mais de uma maneira de definir o número de meses entre duas datas". A questão ainda carece de uma definição formal. É também por isso que sugiro que os casos de teste sejam fornecidos junto com a definição.
John La Rooy
2
Eu recomendo adicionar abs () ao redor das subtrações para permitir que d1 seja menor que d2: return abs (d1.ano - d2.ano) * 12 + abs (d1.mês - d2.mês)
Lukas Schulze
Tem certeza, @LukasSchulze? Se d1 for menor que d2, você terá que subtrair esse número do primeiro de qualquer maneira, certo?
Lilith-Elina
Mas, no meu caso, não importa se d1 <= d2 ou d2 <= d1. Você está interessado apenas na diferença, não importa qual das duas datas é menor / maior.
Lukas Schulze de
40

Uma linha para encontrar uma lista de datas e horas, incrementada por mês, entre duas datas.

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
fonte
Este! Isso se ajusta a muitas situações especiais sofisticadas, graças ao uso de rrule. Observe que os valores de saída são datetimes para que você possa convertê-los no que quiser (incluindo strings) para corresponder ao que os outros estão mostrando.
Ezekiel Kruglick
Isso falha quando strt_dt é 2001-1-29, devido a nenhum dia bissexto naquele ano.
CS de
2
O OP pediu uma lista dos meses entre as datas de início e término. Faltou fevereiro usando seu método no meu exemplo. Claro, sua solução ainda pode ser recuperada ajustando a data de início para o primeiro dia do mês, por exemplo.
CS de
2
É uma boa solução e me poupa muito tempo. Podemos torná-lo ainda melhor fornecendo a data de início[dt for dt in rrule(MONTHLY, bymonthday=10,dtstart=strt_dt, until=end_dt)]
Pengju Zhao
1
Esteja ciente disso. Na verdade, eles têm uma nota na documentação dizendo que rrule pode ter "comportamento surpreendente quando, por exemplo, a data de início ocorre no final do mês" (consulte a nota em dateutil.readthedocs.io/en/stable/rrule.html ). Uma maneira de evitá-lo é substituir as datas pelo primeiro dia do mês: a start_date_first = start_date.replace(day=1), end_date_first = end_date.replace(day=1)seguir, a regra conta os meses corretamente.
Alla Sorokina
36

Isso funcionou para mim -

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
fonte
remova desnecessários em str()torno de literais de string.
jfs
7
Não vale nada que se o delta for superior a 1 ano, r.monthscomeçará de 0
jbkkd
2
normalmente eu faço r.months * (r.years + 1), já que isso se ajusta para o que @jbkkd estava falando
triunenature
9
@triunenature isso parece errado, com certeza deveria ser algo como r.months + (r.years * 12)
Moataz Elmasry
2
Sim, isso não funciona se as datas tiverem mais de um ano de diferença.
HansG600
11

Você pode calcular isso facilmente usando rrule do 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))))

Darei à você:

 [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
fonte
10

Obtenha o mês de término (em relação ao ano e mês do mês de início, ex: janeiro de 2011 = 13 se sua data de início começar em outubro de 2010) e, em seguida, gere os datetimes começando no mês de início e aquele mês final da seguinte forma:

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

se ambas as datas estiverem no mesmo ano, também pode ser simplesmente escrito como:

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

Este post acerta! 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
srodriguex
fonte
1
@edouard Como você pode ver no exemplo que dei, as datas estão em anos diferentes. No entanto, o str()elenco que fiz é completamente desnecessário.
srodriguex
5
isso não funciona se as datas cobrirem mais de um ano, você precisa adicionarrelativedelta.relativedelta(date2, date1).years * 12
muon
delta.years*12 + delta.months
user2682863
7

Minha solução simples:

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
fonte
solução pouco apreciada
reavivar
4

Definir um "mês", como 1 / 12 ano, em seguida, faça o seguinte:

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

Você pode tentar definir um mês como "um período de 29, 28, 30 ou 31 dias (dependendo do ano)". Mas você faz isso, você tem um problema adicional para resolver.

Embora seja geralmente claro que 15 de junho th + 1 mês deve ser 15 de julho th , não é geralmente não está claro se 30 de janeiro th + 1 mês é em fevereiro ou março. Neste último caso, você pode ser obrigado a calcular a data como 30 de fevereiro th , em seguida, "correta" que a 2 de Março nd . Mas quando você faz isso, você verá que 02 de março nd - 1 mês é claramente 02 de fevereiro nd . Portanto, reductio ad absurdum (esta operação não está bem definida).

Seth
fonte
4

Existe uma solução simples baseada em anos de 360 ​​dias, onde todos os meses têm 30 dias. Ele se encaixa na maioria dos casos de uso em que, dadas duas datas, você precisa calcular o número de meses completos mais os dias 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
fonte
4

Uma solução um pouco embelezada 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
fonte
4

Você também pode usar a biblioteca de setas . Este é um exemplo simples:

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

Isso irá imprimir:

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

Espero que isto ajude!

Luis Alberto Santana
fonte
4
from dateutil import relativedelta

relativedelta.relativedelta(date1, date2)

months_difference = (r.years * 12) + r.months
Yatabani
fonte
3

Experimente algo assim. Atualmente, inclui o mês se ambas as datas forem no mesmo mês.

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

A saída se parece com:

>>> start = datetime.now() - timedelta(days=120)
>>> end = datetime.now()
>>> months_between(start,end)
[6, 7, 8, 9, 10]
Charles Hooper
fonte
Porém, isso ainda segue a mesma abordagem de looping, então não vejo necessariamente o benefício ...
Joshkunz
1
Não vejo como isso seja um problema. Loops não são seus inimigos.
Charles Hooper,
Bem, isso tem que ser feito sempre que houver uma consulta ajax. Eu sei que os loops não são o inimigo, mas parece que eles são uma solução lenta para um problema que deveria ser resolvido de uma forma muito mais fácil.
Joshkunz
3

Veja como fazer isso com o 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 ele começa com o mês após a data de início fornecida.

conner.xyz
fonte
2

Isso pode ser feito usando datetime.timedelta, onde o número de dias para pular para o próximo mês pode ser obtido por calender.monthrange. monthrange retorna o dia da semana (0-6 ~ Seg-Dom) e o número de dias (28-31) para um determinado ano e mês.
Por exemplo: monthrange (2017, 1) retorna (6,31).

Aqui está o script usando essa lógica para iterar entre dois 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
fonte
Você poderia explicar como isso resolve o problema? Encorajamos as pessoas a adicionar contexto às suas respostas; obrigado.
Gi0rgi0s
1
Adicionada uma explicação
pankaj kumar
2

Muitas pessoas já lhe deram boas respostas para resolver isso, mas eu não li nenhuma usando compreensão de lista, então eu dou o que usei para um caso de uso semelhante:


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
fonte
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
fonte
1

assim como a rangefunção, quando o mês for 13 , vá para o próximo ano

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

exemplo em 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)]
WeizhongTu
fonte
1

Normalmente 90 dias NÃO são 3 meses literalmente, apenas uma referência.

Então, finalmente, você precisa verificar se os dias são maiores que 15 para adicionar +1 ao contador do mês. ou melhor, adicione outro elif com contador de meio mês.

A partir dessa outra resposta stackoverflow, finalmente terminei com isso:

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

Resultado:

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

Percebi que isso não funciona se você adicionar mais do que dias restantes no ano atual, e isso é inesperado.

m3nda
fonte
1

parece que as respostas são insatisfatórias e desde então uso meu próprio código, que é mais 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
fonte
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
fonte
0

Supondo que upperDate seja sempre posterior a lowerDate e ambos sejam 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 ) )

Eu não testei isso completamente, mas parece que deve funcionar.

Scott
fonte
0

Aqui está um 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

Este é o primeiro cálculo do número total de meses entre as duas datas, inclusive. Em seguida, ele cria uma lista usando a primeira data como base e executa a aritmética de módulos para criar os objetos de data.

Dan
fonte
0

Na verdade, eu precisava fazer algo bem parecido agora

Acabei escrevendo uma função que retorna uma lista de tuplas indicando o starteend de cada mês entre dois conjuntos de datas para que eu pudesse escrever algumas consultas SQL atrás dela para totais mensais de vendas etc.

Tenho certeza que pode ser melhorado por alguém que sabe o que está fazendo, mas espero que ajude ...

O valor retornado é o seguinte (gerando para hoje - 365 dias até hoje como exemplo)

[   (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 da seguinte forma (tem algumas coisas de depuração que podem ser removidas):

#! /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
fonte
0

Supondo que você queira saber a "fração" do mês em que as datas estavam, o que eu fiz, você precisa trabalhar um pouco mais.

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)

fornece um exemplo que calcula o número de meses entre duas datas inclusive, incluindo a fração de cada mês em que a data está. Isso significa que você pode calcular quantos meses estão entre 20/01/2015 e 14/02/2015 , onde a fração da data no mês de janeiro é determinada pelo número de dias em janeiro; ou tendo igualmente em conta que o número de dias em fevereiro pode mudar de ano para ano.

Para minha referência, este código também está no github - https://gist.github.com/andrewyager/6b9284a4f1cdb1779b10

Andrew Yager
fonte
0

Experimente isto:

 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

Não importa a ordem em que você insere as datas e leva em consideração a diferença na duração dos meses.

om_henners
fonte
Observe que isso não leva em consideração que suas datas sejam iguais. A maneira mais fácil seria if delta_time.days = 0: months = 0 else resto da rotina.
om_henners
0

Isso 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
fonte
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
fonte