Qual é a maneira mais simples de subtrair um mês de uma data em Python?

99

Se ao menos timedelta tivesse um argumento de mês em seu construtor. Então, qual é a maneira mais simples de fazer isso?

EDIT: Eu não estava pensando muito sobre isso como foi apontado abaixo. Na verdade, o que eu queria era qualquer dia do último mês porque, eventualmente, vou pegar apenas o ano e o mês. Portanto, dado um objeto datetime, qual é a maneira mais simples de retornar qualquer objeto datetime que caia no mês anterior ?

Bialecki
fonte

Respostas:

62

Experimente isto:

def monthdelta(date, delta):
    m, y = (date.month+delta) % 12, date.year + ((date.month)+delta-1) // 12
    if not m: m = 12
    d = min(date.day, [31,
        29 if y%4==0 and not y%400==0 else 28,31,30,31,30,31,31,30,31,30,31][m-1])
    return date.replace(day=d,month=m, year=y)

>>> for m in range(-12, 12):
    print(monthdelta(datetime.now(), m))


2009-08-06 16:12:27.823000
2009-09-06 16:12:27.855000
2009-10-06 16:12:27.870000
2009-11-06 16:12:27.870000
2009-12-06 16:12:27.870000
2010-01-06 16:12:27.870000
2010-02-06 16:12:27.870000
2010-03-06 16:12:27.886000
2010-04-06 16:12:27.886000
2010-05-06 16:12:27.886000
2010-06-06 16:12:27.886000
2010-07-06 16:12:27.886000
2010-08-06 16:12:27.901000
2010-09-06 16:12:27.901000
2010-10-06 16:12:27.901000
2010-11-06 16:12:27.901000
2010-12-06 16:12:27.901000
2011-01-06 16:12:27.917000
2011-02-06 16:12:27.917000
2011-03-06 16:12:27.917000
2011-04-06 16:12:27.917000
2011-05-06 16:12:27.917000
2011-06-06 16:12:27.933000
2011-07-06 16:12:27.933000
>>> monthdelta(datetime(2010,3,30), -1)
datetime.datetime(2010, 2, 28, 0, 0)
>>> monthdelta(datetime(2008,3,30), -1)
datetime.datetime(2008, 2, 29, 0, 0)

Editar corrigido para controlar o dia também.

Editar Veja também a resposta de perplexidade, que aponta um cálculo mais simples para d:

d = min(date.day, calendar.monthrange(y, m)[1])
Duncan
fonte
Se entendi direito, ((date.month+delta-1) % 12)+1funciona e significa que if not m: m = 12pode ser removido
Charles L.
@dan-klasson você pode explicar?
Jean-François Fabre
@ Jean-FrançoisFabre O primeiro não é simples. Além disso, pode-se fazer isso facilmente com datetime e timedelta.
dan-klasson
Sim, depois que o OP foi editado, ficou claro que o que eles querem pode ser feito apenas por dt - timedelta(days=dt.day)As formulado originalmente, embora pareça que eles querem ser capazes de subtrair um número arbitrário de meses e isso é um pouco mais difícil.
Duncan
183

Você pode usar o dateutilmódulo de terceiros (entrada PyPI aqui ).

import datetime
import dateutil.relativedelta

d = datetime.datetime.strptime("2013-03-31", "%Y-%m-%d")
d2 = d - dateutil.relativedelta.relativedelta(months=1)
print d2

resultado:

2013-02-28 00:00:00
amoe
fonte
Estou confuso com o param monthe monthsno dateutil. Subtrair com param monthnão funciona, mas monthsfunciona In [23]: created_datetime__lt - relativedelta(months=1) Out[23]: datetime.datetime(2016, 11, 29, 0, 0, tzinfo=<StaticTzInfo 'Etc/GMT-8'>) In [24]: created_datetime__lt - relativedelta(month=1) Out[24]: datetime.datetime(2016, 1, 29, 0, 0, tzinfo=<StaticTzInfo 'Etc/GMT-8'>)
Simin Jie
1
@SiminJie: Acho que usar monthsfará uma diferença de 1 mês. Usar monthirá definir o mês como 1 (por exemplo, janeiro), independentemente do mês da entrada.
Frank Meulenaar
70

Após a edição da pergunta original para "qualquer objeto datetime no mês anterior", você pode fazer isso facilmente subtraindo 1 dia do primeiro dia do mês.

from datetime import datetime, timedelta

def a_day_in_previous_month(dt):
   return dt.replace(day=1) - timedelta(days=1)
Cory
fonte
4
você poderia usardt.replace(day=1) - timedelta(1)
jfs
3
oudt - timedelta(days=dt.day)
SergiyKolesnikov
23

Uma variação da resposta de Duncan (não tenho reputação suficiente para comentar), que usa calendar.monthrange para simplificar drasticamente o cálculo do último dia do mês:

import calendar
def monthdelta(date, delta):
    m, y = (date.month+delta) % 12, date.year + ((date.month)+delta-1) // 12
    if not m: m = 12
    d = min(date.day, calendar.monthrange(y, m)[1])
    return date.replace(day=d,month=m, year=y)

Informações sobre o intervalo do mês desde Obter o último dia do mês em Python

perplexidade
fonte
22

Uma solução vetorizada de pandas é muito simples:

df['date'] - pd.DateOffset(months=1)

Corey Levinson
fonte
Obrigado! Isso me ajudou facilmente a adicionar / subtrair um mês de / para meu conjunto de datas!
Mateus da Silva Teixeira
Isso é bom se você puder usar pandas. Obrigado!
igorkf
17

Se ao menos timedelta tivesse um argumento de mês em seu construtor. Então, qual é a maneira mais simples de fazer isso?

Qual você deseja que seja o resultado ao subtrair um mês de, digamos, uma data que é 30 de março? Esse é o problema de adicionar ou subtrair meses: os meses têm durações diferentes! Em alguns aplicativos, uma exceção é apropriada em tais casos, em outros, "o último dia do mês anterior" está OK para usar (mas isso é aritmética realmente maluca, quando subtrair um mês e depois adicionar um mês não é uma operação geral!) , em outros ainda, você desejará manter além da data alguma indicação sobre o fato, por exemplo, "Estou dizendo 28 de fevereiro, mas eu realmente gostaria de 30 de fevereiro se existisse", de modo que adicionar ou subtrair outro mês para que pode consertar as coisas novamente (e o último obviamente requer uma classe customizada contendo dados e outras coisas).

Não pode haver solução real que seja tolerável para todos os aplicativos, e você não nos disse quais são as necessidades específicas do seu aplicativo para a semântica desta operação miserável, então não há muito mais ajuda que possamos fornecer aqui.

Alex Martelli
fonte
Você está absolutamente correto. Estava apenas brincando sobre a timedelta ter um argumento para isso. Eu me pergunto se a mesma lógica se aplica a não ter argumentos por horas ou minutos. Tecnicamente, um dia não é exatamente 24 horas.
Bialecki
O PHP lida com isso adicionando 2 dias a 28 de fevereiro e vai até 2 de março. Existem convenções para isso, deixe-me encontrá-las bem rápido
NullUserException
Fica ainda mais interessante se você estiver calculando datas para o câmbio de moeda: por exemplo, você pode precisar encontrar uma data, digamos, 3 ou 6 meses no futuro, mas também deve ser um dia útil em ambos os países (não sexta-feira nos países muçulmanos) e não feriado bancário em nenhum dos países.
Duncan
@Bialecki: datetimemodules assume que um dia é exatamente 24 horas (sem segundos bissextos)
jfs
8

Acho que a maneira mais simples é usar DateOffset do Pandas assim:

import pandas as pd
date_1 = pd.to_datetime("2013-03-31", format="%Y-%m-%d") - pd.DateOffset(months=1)

O resultado será um objeto datetime

FranciscoRodríguezCuenca
fonte
2
Essa é a solução mais intuitiva
Kots
5

Se tudo o que você deseja é qualquer dia do último mês, a coisa mais simples que você pode fazer é subtrair o número de dias da data atual, o que lhe dará o último dia do mês anterior.

Por exemplo, começando com qualquer data:

>>> import datetime                                                                                                                                                                 
>>> today = datetime.date.today()                                                                                                                                                   
>>> today
datetime.date(2016, 5, 24)

Subtraindo os dias da data atual, obtemos:

>>> last_day_previous_month = today - datetime.timedelta(days=today.day)
>>> last_day_previous_month
datetime.date(2016, 4, 30)

Isso é o suficiente para sua necessidade simplificada de qualquer dia do último mês.

Mas agora que você o tem, também pode obter qualquer dia do mês, incluindo o mesmo dia em que começou (ou seja, mais ou menos o mesmo que subtrair um mês):

>>> same_day_last_month = last_day_previous_month.replace(day=today.day)
>>> same_day_last_month
datetime.date(2016, 4, 24)

Claro, você precisa ter cuidado com o 31º em um mês de 30 dias ou os dias que faltam a partir de fevereiro (e cuidar dos anos bissextos), mas isso também é fácil de fazer:

>>> a_date = datetime.date(2016, 3, 31)                                                                                                                                             
>>> last_day_previous_month = a_date - datetime.timedelta(days=a_date.day)
>>> a_date_minus_month = (
...     last_day_previous_month.replace(day=a_date.day)
...     if a_date.day < last_day_previous_month.day
...     else last_day_previous_month
... )
>>> a_date_minus_month
datetime.date(2016, 2, 29)
LeoRochael
fonte
1

Eu uso isso para os anos fiscais do governo em que o quarto trimestre começa em 1º de outubro. Observe que eu converto a data em trimestres e também desfaço.

import pandas as pd

df['Date'] = '1/1/2020'
df['Date'] = pd.to_datetime(df['Date'])              #returns 2020-01-01
df['NewDate'] = df.Date - pd.DateOffset(months=3)    #returns 2019-10-01 <---- answer

# For fun, change it to FY Quarter '2019Q4'
df['NewDate'] = df['NewDate'].dt.year.astype(str) + 'Q' + df['NewDate'].dt.quarter.astype(str)

# Convert '2019Q4' back to 2019-10-01
df['NewDate'] = pd.to_datetime(df.NewDate)
Arthur D. Howland
fonte
0

Aqui está um código para fazer exatamente isso. Ainda não experimentei ...

def add_one_month(t):
    """Return a `datetime.date` or `datetime.datetime` (as given) that is
    one month earlier.

    Note that the resultant day of the month might change if the following
    month has fewer days:

        >>> add_one_month(datetime.date(2010, 1, 31))
        datetime.date(2010, 2, 28)
    """
    import datetime
    one_day = datetime.timedelta(days=1)
    one_month_later = t + one_day
    while one_month_later.month == t.month:  # advance to start of next month
        one_month_later += one_day
    target_month = one_month_later.month
    while one_month_later.day < t.day:  # advance to appropriate day
        one_month_later += one_day
        if one_month_later.month != target_month:  # gone too far
            one_month_later -= one_day
            break
    return one_month_later

def subtract_one_month(t):
    """Return a `datetime.date` or `datetime.datetime` (as given) that is
    one month later.

    Note that the resultant day of the month might change if the following
    month has fewer days:

        >>> subtract_one_month(datetime.date(2010, 3, 31))
        datetime.date(2010, 2, 28)
    """
    import datetime
    one_day = datetime.timedelta(days=1)
    one_month_earlier = t - one_day
    while one_month_earlier.month == t.month or one_month_earlier.day > t.day:
        one_month_earlier -= one_day
    return one_month_earlier
sajal
fonte
0

Dada uma tupla (ano, mês), em que mês vai de 1 a 12, tente o seguinte:

>>> from datetime import datetime
>>> today = datetime.today()
>>> today
datetime.datetime(2010, 8, 6, 10, 15, 21, 310000)
>>> thismonth = today.year, today.month
>>> thismonth
(2010, 8)
>>> lastmonth = lambda (yr,mo): [(y,m+1) for y,m in (divmod((yr*12+mo-2), 12),)][0]
>>> lastmonth(thismonth)
(2010, 7)
>>> lastmonth( (2010,1) )
(2009, 12)

Presume-se que haja 12 meses em cada ano.

PaulMcG
fonte
0
def month_sub(year, month, sub_month):
    result_month = 0
    result_year = 0
    if month > (sub_month % 12):
        result_month = month - (sub_month % 12)
        result_year = year - (sub_month / 12)
    else:
        result_month = 12 - (sub_month % 12) + month
        result_year = year - (sub_month / 12 + 1)
    return (result_year, result_month)

>>> month_sub(2015, 7, 1)    
(2015, 6)
>>> month_sub(2015, 7, -1)
(2015, 8)
>>> month_sub(2015, 7, 13)
(2014, 6)
>>> month_sub(2015, 7, -14)
(2016, 9)
damn_c
fonte
0

Usei o seguinte código para voltar n meses a partir de uma data específica:

your_date =  datetime.strptime(input_date, "%Y-%m-%d")  #to convert date(2016-01-01) to timestamp
start_date=your_date    #start from current date

#Calculate Month
for i in range(0,n):    #n = number of months you need to go back
    start_date=start_date.replace(day=1)    #1st day of current month
    start_date=start_date-timedelta(days=1) #last day of previous month

#Calculate Day
if(start_date.day>your_date.day):   
    start_date=start_date.replace(day=your_date.day)            

print start_date

Por exemplo: data de entrada = 28/12/2015 Calcule 6 meses da data anterior.

I) CALCULAR MÊS: Esta etapa fornecerá a data_início como 30/06/2015.
Observe que, após a etapa de cálculo do mês, você obterá o último dia do mês necessário.

II) CALCULAR DIA: Condição if (start_date.day> your_date.day) verifica se o dia de input_date está presente no mês requerido. Isso lida com condições em que a data de entrada é 31 (ou 30) e o mês necessário tem menos de 31 (ou 30 no caso de fev) dias. Ele lida com casos de ano bissexto também (para fevereiro). Após esta etapa, você obterá o resultado 28/06/2015

Se esta condição não for satisfeita, a data_de_início permanece como a última data do mês anterior. Portanto, se você fornecer 31/12/2015 como data de entrada e quiser uma data de 6 meses anteriores, ele fornecerá 30/06/2015

ashay93k
fonte
0

Você pode usar a função fornecida abaixo para obter a data antes / depois de X meses.

da data e hora da importação

def next_month (given_date, month):
    aaaa = int (((data_dada.ano * 12 + data_dada.mês) + mês) / 12)
    mm = int (((data_de_data.ano * 12 + data_de_data.mês) + mês)% 12)

    se mm == 0:
        aaaa - = 1
        mm = 12

    retornar data_de_dada.replace (ano = aaaa, mês = mm)


if __name__ == "__main__":
    hoje = data.today ()
    imprimir (hoje)

    para mm em [-12, -1, 0, 1, 2, 12, 20]:
        next_date = next_month (hoje, mm)
        imprimir (next_date)
Nandlal Yadav
fonte
0

Acho que esta resposta é bastante legível:

def month_delta(dt, delta):
    year_delta, month = divmod(dt.month + delta, 12)

    if month == 0:
        # convert a 0 to december
        month = 12
        if delta < 0:
            # if moving backwards, then it's december of last year
            year_delta -= 1

    year = dt.year + year_delta

    return dt.replace(month=month, year=year)

for delta in range(-20, 21):
    print(delta, "->", month_delta(datetime(2011, 1, 1), delta))

-20 -> 2009-05-01 00:00:00
-19 -> 2009-06-01 00:00:00
-18 -> 2009-07-01 00:00:00
-17 -> 2009-08-01 00:00:00
-16 -> 2009-09-01 00:00:00
-15 -> 2009-10-01 00:00:00
-14 -> 2009-11-01 00:00:00
-13 -> 2009-12-01 00:00:00
-12 -> 2010-01-01 00:00:00
-11 -> 2010-02-01 00:00:00
-10 -> 2010-03-01 00:00:00
-9 -> 2010-04-01 00:00:00
-8 -> 2010-05-01 00:00:00
-7 -> 2010-06-01 00:00:00
-6 -> 2010-07-01 00:00:00
-5 -> 2010-08-01 00:00:00
-4 -> 2010-09-01 00:00:00
-3 -> 2010-10-01 00:00:00
-2 -> 2010-11-01 00:00:00
-1 -> 2010-12-01 00:00:00
0 -> 2011-01-01 00:00:00
1 -> 2011-02-01 00:00:00
2 -> 2011-03-01 00:00:00
3 -> 2011-04-01 00:00:00
4 -> 2011-05-01 00:00:00
5 -> 2011-06-01 00:00:00
6 -> 2011-07-01 00:00:00
7 -> 2011-08-01 00:00:00
8 -> 2011-09-01 00:00:00
9 -> 2011-10-01 00:00:00
10 -> 2011-11-01 00:00:00
11 -> 2012-12-01 00:00:00
12 -> 2012-01-01 00:00:00
13 -> 2012-02-01 00:00:00
14 -> 2012-03-01 00:00:00
15 -> 2012-04-01 00:00:00
16 -> 2012-05-01 00:00:00
17 -> 2012-06-01 00:00:00
18 -> 2012-07-01 00:00:00
19 -> 2012-08-01 00:00:00
20 -> 2012-09-01 00:00:00
BallpointBen
fonte
0

Um forro?

previous_month_date = (current_date - datetime.timedelta(days=current_date.day+1)).replace(day=current_date.day)

Onekiloparsec
fonte
0

Maneira mais simples que tentei agora

from datetime import datetime
from django.utils import timezone





current = timezone.now()
if current.month == 1:
     month = 12
else:
     month = current.month - 1
current = datetime(current.year, month, current.day)
Saad Mirza
fonte
0

Algum tempo atrás, encontrei o seguinte algoritmo que funciona muito bem para aumentar e diminuir meses em um dateou datetime.

AVISO : Isso falhará se daynão estiver disponível no novo mês. Eu uso isso em objetos de data, onde day == 1sempre.

Python 3.x:

def increment_month(d, add=1):
    return date(d.year+(d.month+add-1)//12, (d.month+add-1) % 12+1, 1)

Para Python 2.7, mude o //12para apenas /12porque a divisão inteira está implícita.

Recentemente, usei isso em um arquivo de padrões quando um script começou a obter esses globais úteis:

MONTH_THIS = datetime.date.today()
MONTH_THIS = datetime.date(MONTH_THIS.year, MONTH_THIS.month, 1)

MONTH_1AGO = datetime.date(MONTH_THIS.year+(MONTH_THIS.month-2)//12,
                           (MONTH_THIS.month-2) % 12+1, 1)

MONTH_2AGO = datetime.date(MONTH_THIS.year+(MONTH_THIS.month-3)//12,
                           (MONTH_THIS.month-3) % 12+1, 1)
Neil C. Obremski
fonte
-2
import datetime
date_str = '08/01/2018'
format_str = '%d/%m/%Y'
datetime_obj = datetime.datetime.strptime(date_str, format_str)   
datetime_obj.replace(month=datetime_obj.month-1)

Solução simples, sem necessidade de bibliotecas especiais.

Cristian Florin Ionescu
fonte
2
Não funcionará quando a data for algo como 31 de março.
firecast
1
Ou como 1 ° de janeiro
LeandroHumb de
-3

Você poderia fazer isso em duas linhas como esta:

now = datetime.now()
last_month = datetime(now.year, now.month - 1, now.day)

lembre-se das importações

from datetime import datetime
Omri Jacobsz
fonte
1
E se for Jan?
Sergei