Qual è il modo più semplice per sottrarre un mese da una data in Python?


99

Se solo timedelta avesse un argomento del mese nel suo costruttore. Allora qual è il modo più semplice per farlo?

EDIT: non stavo pensando troppo a questo come è stato sottolineato di seguito. Quello che volevo davvero era un giorno nell'ultimo mese perché alla fine prenderò solo l'anno e il mese. Quindi, dato un oggetto datetime, qual è il modo più semplice per restituire qualsiasi oggetto datetime che cade nel mese precedente ?

Risposte:


62

Prova questo:

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)

Modifica Corretto per gestire anche la giornata.

Modifica Vedi anche la risposta perplesso che indica un calcolo più semplice per d:

d = min(date.day, calendar.monthrange(y, m)[1])

Se ho ragione ((date.month+delta-1) % 12)+1funziona e significa che la if not m: m = 12lattina viene rimossa
Charles L.

@ dan-klasson puoi approfondire?
Jean-François Fabre

@ Jean-FrançoisFabre Il primo non è semplice. Inoltre, è possibile farlo facilmente con datetime e timedelta.
dan-klasson

Sì, dopo la modifica dell'OP diventa chiaro che ciò che vogliono può essere fatto solo da dt - timedelta(days=dt.day)Come originariamente formulato anche se sembrava che volessero essere in grado di sottrarre un numero arbitrario di mesi e questo è un po 'più difficile.
Duncan

183

È possibile utilizzare il dateutilmodulo di terze parti (voce PyPI qui ).

import datetime
import dateutil.relativedelta

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

produzione:

2013-02-28 00:00:00

Sono confuso con il parametro monthe monthsin dateutil. La sottrazione con param monthnon funziona ma monthsfunziona 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: penso che l'utilizzo monthsfarà la differenza di 1 mese. L'utilizzo monthimposterà il mese su 1 (es. Gennaio), indipendentemente dal mese di input.
Frank Meulenaar

70

Dopo la modifica della domanda originale a "qualsiasi oggetto datetime nel mese precedente", puoi farlo abbastanza facilmente sottraendo 1 giorno dal primo del mese.

from datetime import datetime, timedelta

def a_day_in_previous_month(dt):
   return dt.replace(day=1) - timedelta(days=1)

4
potresti usaredt.replace(day=1) - timedelta(1)
jfs

3
oppuredt - timedelta(days=dt.day)
SergiyKolesnikov

23

Una variazione sulla risposta di Duncan (non ho una reputazione sufficiente per commentare), che utilizza calendar.monthrange per semplificare notevolmente il calcolo dell'ultimo giorno del mese:

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)

Informazioni sull'intervallo di mesi da Ottieni l'ultimo giorno del mese in Python


22

Una soluzione panda vettorializzata è molto semplice:

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


Grazie! Mi ha aiutato ad aggiungere / sottrarre facilmente un mese al / dal mio set di date!
Mateus da Silva Teixeira

Questo è bello se puoi usare pandas. Grazie!
igorkf

17

Se solo timedelta avesse un argomento del mese nel suo costruttore. Allora qual è il modo più semplice per farlo?

Quale sarà il risultato quando sottrai un mese, ad esempio, da una data che è il 30 marzo? Questo è il problema con l'aggiunta o la sottrazione di mesi: i mesi hanno lunghezze diverse! In alcune applicazioni un'eccezione è appropriata in questi casi, in altri "l'ultimo giorno del mese precedente" è OK da usare (ma questa è un'aritmetica veramente folle, quando sottrarre un mese e poi aggiungere un mese non è nel complesso una non operazione!) , in altri ancora ti consigliamo di mantenere, oltre alla data, alcune indicazioni sul fatto, ad es. "Sto dicendo il 28 febbraio ma vorrei davvero il 30 febbraio se esistesse", in modo che aggiungendo o sottraendo un altro mese a che può rimettere le cose a posto (e quest'ultimo ovviamente richiede una classe personalizzata che contenga un dato più s / qualcos'altro).

Non può esserci una vera soluzione tollerabile per tutte le applicazioni e non ci hai detto quali sono le esigenze della tua app specifica per la semantica di questa disgraziata operazione, quindi non c'è molto più aiuto che possiamo fornire qui.


Hai assolutamente ragione. Stavo solo scherzando sul fatto che Timedelta avesse una discussione per questo. Mi chiedo se la stessa logica valga per non avere argomenti per ore o minuti. Tecnicamente un giorno non è esattamente 24 ore.
Bialecki

PHP lo gestisce aggiungendo 2 giorni al 28 febbraio e va al 2 marzo. Ci sono convenzioni per questo, fammi trovare molto velocemente
NullUserException

Diventa ancora più interessante se stai calcolando le date per il cambio di valuta: ad esempio potresti aver bisogno di trovare una data diciamo 3 o 6 mesi in futuro, ma deve anche essere un giorno lavorativo in entrambi i paesi (non venerdì nei paesi musulmani) e non un giorno festivo in nessuno dei due paesi.
Duncan

@Bialecki: i datetimemoduli presuppongono che un giorno sia esattamente 24 ore (senza secondi intercalari )
jfs

8

Penso che il modo più semplice sia usare DateOffset da Pandas in questo modo:

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

Il risultato sarà un oggetto datetime


2
Questa è la soluzione più intuitiva
Kots

5

Se tutto quello che vuoi è un giorno nell'ultimo mese, la cosa più semplice che puoi fare è sottrarre il numero di giorni dalla data corrente, che ti darà l'ultimo giorno del mese precedente.

Ad esempio, a partire da qualsiasi data:

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

Sottraendo i giorni della data corrente otteniamo:

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

Questo è sufficiente per il tuo bisogno semplificato di qualsiasi giorno nell'ultimo mese.

Ma ora che ce l'hai, puoi anche ottenere qualsiasi giorno del mese, incluso lo stesso giorno in cui hai iniziato (cioè più o meno lo stesso che sottraendo un mese):

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

Certo, devi stare attento con il 31 su un mese di 30 giorni o ai giorni mancanti da febbraio (e prenditi cura degli anni bisestili), ma è anche facile:

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

1

Lo uso per gli anni fiscali governativi in ​​cui il quarto trimestre inizia il 1 ° ottobre. Nota che converto la data in trimestri e la annullo pure.

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)

0

Ecco del codice per farlo. Non l'ho provato io stesso ...

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

0

Data una tupla (anno, mese), dove il mese va da 1 a 12, prova questo:

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

Presuppone che ci siano 12 mesi all'anno.


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)

0

Ho usato il seguente codice per tornare indietro di n mesi da una data specifica:

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

Ad esempio: data di input = 28/12/2015 Calcola la data precedente di 6 mesi.

I) CALCOLA MESE: Questo passaggio ti darà la data di inizio il 30/06/2015.
Nota che dopo il passaggio del mese di calcolo otterrai l'ultimo giorno del mese richiesto.

II) CALCOLA GIORNO: Condizione if (start_date.day> your_date.day) controlla se il giorno da input_date è presente nel mese richiesto. Gestisce la condizione in cui la data di input è 31 (o 30) e il mese richiesto ha meno di 31 (o 30 in caso di feb) giorni. Gestisce anche il caso degli anni bisestili (per febbraio). Dopo questo passaggio otterrai il risultato come 28/06/2015

Se questa condizione non è soddisfatta, start_date rimane l'ultima data del mese precedente. Quindi, se fornisci il 31/12/2015 come data di input e desideri la data precedente di 6 mesi, otterrai il 30/06/2015


0

È possibile utilizzare la funzione indicata di seguito per ottenere la data prima / dopo X mese.

dalla data di importazione datetime

def next_month (given_date, month):
    yyyy = int (((given_date.year * 12 + given_date.month) + month) / 12)
    mm = int (((data_data.anno * 12 + data_data.mese) + mese)% 12)

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

    return given_date.replace (year = yyyy, month = mm)


se __name__ == "__main__":
    oggi = date.today ()
    stampa (oggi)

    per mm in [-12, -1, 0, 1, 2, 12, 20]:
        next_date = next_month (oggi, mm)
        print (next_date)

0

Penso che questa risposta sia abbastanza leggibile:

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

0

Una fodera?

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


0

Modo più semplice che ho provato proprio ora

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)

0

Qualche tempo fa mi sono imbattuto nel seguente algoritmo che funziona molto bene per aumentare e diminuire i mesi su a dateo datetime.

CAVEAT : Questo fallirà se daynon è disponibile nel nuovo mese. Lo uso su oggetti data dove 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)

Per Python 2.7 cambia il //12in solo /12poiché è implicita la divisione di interi.

Di recente l'ho usato in un file di default quando uno script ha iniziato a ottenere questi utili globali:

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)

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

Soluzione semplice, nessuna necessità di librerie speciali.


2
Non funzionerà quando la data è qualcosa come il 31 marzo.
Firecast

1
O come il 1 ° gennaio
LeandroHumb il

-3

Potresti farlo in due righe come questa:

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

ricorda le importazioni

from datetime import datetime

1
E se fosse Jan?
Sergei
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.