¿Cuál es la forma más sencilla de restar un mes de una fecha en Python?

99

Si solo timedelta tuviera un argumento de mes en su constructor. Entonces, ¿cuál es la forma más sencilla de hacer esto?

EDITAR: No estaba pensando demasiado en esto como se señaló a continuación. Realmente lo que quería era cualquier día del último mes porque eventualmente voy a tomar solo el año y el mes. Entonces, dado un objeto de fecha y hora, ¿cuál es la forma más sencilla de devolver cualquier objeto de fecha y hora que se encuentre en el mes anterior ?

Bialecki
fuente

Respuestas:

62

Prueba esto:

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 Corregido para manejar el día también.

Editar Vea también la respuesta de la perplejidad que señala un cálculo más simple para d:

d = min(date.day, calendar.monthrange(y, m)[1])
Duncan
fuente
Si lo tengo ((date.month+delta-1) % 12)+1obras y medios la if not m: m = 12pueden quedar eliminado
Charles L.
@ dan-klasson, ¿puedes dar más detalles?
Jean-François Fabre
@ Jean-FrançoisFabre El primero no es sencillo. Además, uno puede hacer esto fácilmente con datetime y timedelta.
dan-klasson
Sí, después de que se editó el OP, queda claro que lo que quieren se puede hacer simplemente dt - timedelta(days=dt.day)como se expresó originalmente, aunque parecía que querían poder restar un número arbitrario de meses y eso es un poco más difícil.
Duncan
183

Puede utilizar el dateutilmódulo de terceros (entrada PyPI aquí ).

import datetime
import dateutil.relativedelta

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

salida:

2013-02-28 00:00:00
ame
fuente
Estoy confundido con el parámetro monthy monthsen dateutil. Restar con param monthno funciona pero 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: Creo que usarlo monthshará una diferencia de 1 mes. El uso monthestablecerá el mes en 1 (por ejemplo, enero), independientemente del mes de la entrada.
Frank Meulenaar
70

Después de la edición de la pregunta original a "cualquier objeto de fecha y hora del mes anterior", puede hacerlo con bastante facilidad restando 1 día del primero del mes.

from datetime import datetime, timedelta

def a_day_in_previous_month(dt):
   return dt.replace(day=1) - timedelta(days=1)
Cory
fuente
4
podría usardt.replace(day=1) - timedelta(1)
jfs
3
odt - timedelta(days=dt.day)
SergiyKolesnikov
23

Una variación de la respuesta de Duncan (no tengo suficiente reputación para comentar), que usa calendar.monthrange para simplificar drásticamente el cálculo del último día del mes:

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)

Información sobre el rango de meses desde Obtener el último día del mes en Python

perplejidad
fuente
22

Una solución de pandas vectorizada es muy simple:

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

Corey Levinson
fuente
¡Gracias! ¡Me ayudó a agregar / restar fácilmente un mes a mi conjunto de fechas!
Mateus da Silva Teixeira
Esto es bueno si puedes usar pandas. ¡Gracias!
igorkf
17

Si solo timedelta tuviera un argumento de mes en su constructor. Entonces, ¿cuál es la forma más sencilla de hacer esto?

¿Cuál quiere que sea el resultado cuando resta un mes de, digamos, una fecha que es el 30 de marzo? Ese es el problema de sumar o restar meses: ¡los meses tienen diferentes duraciones! En algunas aplicaciones, una excepción es apropiada en tales casos, en otras está bien usar "el último día del mes anterior" (pero eso es una aritmética realmente loca, cuando restar un mes y luego agregar un mes no es en general una no operación). , en otros, sin embargo, querrá mantener, además de la fecha, alguna indicación sobre el hecho, por ejemplo, "Digo el 28 de febrero, pero realmente me gustaría el 30 de febrero si existiera", de modo que sumar o restar otro mes para que puede arreglar las cosas de nuevo (y este último obviamente requiere una clase personalizada que contenga datos más algo más).

No puede haber una solución real que sea tolerable para todas las aplicaciones, y no nos ha dicho cuáles son las necesidades específicas de su aplicación para la semántica de esta lamentable operación, por lo que no hay mucha más ayuda que podamos brindar aquí.

Alex Martelli
fuente
Estás absolutamente en lo correcto. Solo estaba bromeando sobre que Timedelta tenía un argumento a favor. Me pregunto si se aplica la misma lógica para no tener discusiones durante horas o minutos. Técnicamente, un día no es exactamente 24 horas.
Bialecki
PHP maneja eso agregando 2 días al 28 de febrero, y va al 2 de marzo. Hay convenciones para esto, déjame encontrarlas muy rápido
NullUserException
Se vuelve aún más interesante si está calculando fechas para el cambio de moneda: por ejemplo, es posible que deba encontrar una fecha, digamos 3 o 6 meses en el futuro, pero también debe ser un día hábil en ambos países (no el viernes en los países musulmanes) y no un feriado bancario en ninguno de los dos países.
Duncan
@Bialecki: los datetimemódulos asumen que un día son exactamente 24 horas (sin segundos
intercalares
8

Creo que la forma sencilla es usar DateOffset de Pandas así:

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

El resultado será un objeto de fecha y hora

FranciscoRodríguezCuenca
fuente
2
Esa es la solución más intuitiva
Kots
5

Si todo lo que desea es cualquier día del último mes, lo más simple que puede hacer es restar el número de días de la fecha actual, lo que le dará el último día del mes anterior.

Por ejemplo, comenzando con cualquier fecha:

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

Restando los días de la fecha actual obtenemos:

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

Esto es suficiente para su necesidad simplificada de cualquier día del último mes.

Pero ahora que lo tiene, también puede obtener cualquier día del mes, incluido el mismo día con el que comenzó (es decir, más o menos lo mismo que restar un mes):

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

Por supuesto, debe tener cuidado con el 31 en un mes de 30 días o los días que faltan de febrero (y cuidar los años bisiestos), pero eso también es fácil de hacer:

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

Utilizo esto para los años fiscales del gobierno donde el cuarto trimestre comienza el 1 de octubre. Tenga en cuenta que convierto la fecha en trimestres y también la deshago.

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
fuente
0

Aquí hay un código para hacer precisamente eso. No lo he probado yo mismo ...

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
fuente
0

Dada una tupla (año, mes), donde el mes va del 1 al 12, intente esto:

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

Supone que hay 12 meses en cada año.

PaulMcG
fuente
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)
maldita sea
fuente
0

Usé el siguiente código para retroceder n meses desde una fecha 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 ejemplo: fecha de entrada = 28/12/2015 Calcular la fecha anterior de 6 meses.

I) CALCULAR MES: Este paso le dará la fecha de inicio como 30/06/2015.
Tenga en cuenta que después del paso de cálculo del mes obtendrá el último día del mes requerido.

II) CALCULATE DAY: Condición si (start_date.day> your_date.day) comprueba si el día de input_date está presente en el mes requerido. Esto maneja la condición donde la fecha de entrada es 31 (o 30) y el mes requerido tiene menos de 31 (o 30 en el caso de feb) días. También maneja casos de años bisiestos (para febrero). Después de este paso, obtendrá el resultado 28/06/2015

Si no se cumple esta condición, start_date sigue siendo la última fecha del mes anterior. Entonces, si da 31/12/2015 como fecha de entrada y desea 6 meses de fecha anterior, le dará 30/06/2015

ashay93k
fuente
0

Puede utilizar la función dada a continuación para obtener la fecha antes / después de X mes.

desde la fecha de importación datetime

def next_month (fecha_proporcionada, mes):
    aaaa = int (((fecha_proporcionada.año * 12 + fecha_propuesta.mes) + mes) / 12)
    mm = int (((fecha_proporcionada.año * 12 + fecha_propuesta.mes) + mes)% 12)

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

    return fecha_de_seguridad.reemplazar (año = aaaa, mes = mm)


if __name__ == "__main__":
    hoy = fecha.today ()
    imprimir (hoy)

    para mm en [-12, -1, 0, 1, 2, 12, 20]:
        next_date = next_month (hoy, mm)
        imprimir (próxima_fecha)
Nandlal Yadav
fuente
0

Creo que esta respuesta es bastante legible:

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
Bolígrafo Ben
fuente
0

Un trazador de líneas ?

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

onekiloparsec
fuente
0

La forma más sencilla que he probado ahora

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
fuente
0

Hace algún tiempo me encontré con el siguiente algoritmo que funciona muy bien para aumentar y disminuir los meses ya sea en una dateo datetime.

AVISO : Esto fallará si dayno está disponible en el nuevo mes. Lo uso en objetos de fecha donde day == 1siempre.

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, cambie //12a solo /12ya que está implícita la división de enteros.

Recientemente usé esto en un archivo de valores predeterminados cuando un script comenzó a obtener estos globales útiles:

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

Solución simple, sin necesidad de bibliotecas especiales.

Cristian Florin Ionescu
fuente
2
No funcionará cuando la fecha sea algo así como el 31 de marzo.
Firecast
1
O como el 1 de enero
LeandroHumb
-3

Podrías hacerlo en dos líneas como esta:

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

recuerda las importaciones

from datetime import datetime
Omri Jacobsz
fuente
1
¿Y si es Jan?
Sergei