Was ist der einfachste Weg, einen Monat von einem Datum in Python zu subtrahieren?

99

Wenn nur timedelta ein Monatsargument im Konstruktor hätte. Was ist der einfachste Weg, dies zu tun?

EDIT: Ich habe nicht zu viel darüber nachgedacht, wie unten ausgeführt wurde. Eigentlich wollte ich jeden Tag im letzten Monat, denn irgendwann werde ich nur noch das Jahr und den Monat holen. Was ist also bei einem Datum / Uhrzeit-Objekt der einfachste Weg, ein Datum / Uhrzeit-Objekt zurückzugeben, das in den Vormonat fällt ?

Bialecki
quelle

Antworten:

62

Versuche dies:

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)

Bearbeiten Korrigiert, um auch den Tag zu bewältigen.

Bearbeiten Siehe auch die Antwort aus der Verwirrung, die eine einfachere Berechnung für Folgendes aufzeigt d:

d = min(date.day, calendar.monthrange(y, m)[1])
Duncan
quelle
Wenn ich es richtig habe, ((date.month+delta-1) % 12)+1funktioniert es und bedeutet, dass die if not m: m = 12Dose entfernt werden kann
Charles L.
@ dan-klasson kannst du das näher erläutern?
Jean-François Fabre
@ Jean-FrançoisFabre Der erste ist nicht einfach. Dies kann auch leicht mit datetime und timedelta geschehen.
Dan-Klasson
Ja, nachdem das OP bearbeitet wurde, wird klar, dass das, was sie wollen, nur durch dt - timedelta(days=dt.day)Wie ursprünglich formuliert gemacht werden kann, obwohl es sich anhörte, als wollten sie in der Lage sein, eine beliebige Anzahl von Monaten zu subtrahieren, und das ist etwas schwieriger.
Duncan
182

Sie können das Drittanbieter- dateutilModul verwenden (PyPI-Eintrag hier ).

import datetime
import dateutil.relativedelta

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

Ausgabe:

2013-02-28 00:00:00
Amöe
quelle
Ich bin verwirrt mit dem Parameter monthund monthsin Dateutil. Subtrahieren mit param monthfunktioniert nicht, aber monthsfunktioniert 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: Ich denke, die Verwendung monthswird einen Unterschied von 1 Monat machen. Mit monthwird der Monat auf 1 gesetzt (z. B. Januar), unabhängig vom Monat der Eingabe.
Frank Meulenaar
70

Nachdem die ursprüngliche Frage zu "einem beliebigen Datum / Uhrzeit-Objekt im vorherigen Monat" bearbeitet wurde, können Sie dies ganz einfach tun, indem Sie 1 Tag vom ersten des Monats abziehen.

from datetime import datetime, timedelta

def a_day_in_previous_month(dt):
   return dt.replace(day=1) - timedelta(days=1)
Cory
quelle
4
Sie könnten verwendendt.replace(day=1) - timedelta(1)
JFS
3
oderdt - timedelta(days=dt.day)
SergiyKolesnikov
23

Eine Variation von Duncans Antwort (ich habe nicht genügend Ruf, um sie zu kommentieren), die calendar.monthrange verwendet, um die Berechnung des letzten Tages des Monats dramatisch zu vereinfachen:

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)

Info zu monthrange von Get Last Day of the Month in Python

Verwirrung
quelle
22

Eine vektorisierte Pandas-Lösung ist sehr einfach:

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

Corey Levinson
quelle
Danke dir! Es hat mir geholfen, einfach einen Monat zu / von meinen Daten hinzuzufügen / zu subtrahieren!
Mateus da Silva Teixeira
Das ist schön, wenn Sie verwenden können pandas. Vielen Dank!
igorkf
17

Wenn nur timedelta ein Monatsargument im Konstruktor hätte. Was ist der einfachste Weg, dies zu tun?

Was soll das Ergebnis sein, wenn Sie einen Monat beispielsweise von einem Datum abziehen, das der 30. März ist? Das ist das Problem beim Addieren oder Subtrahieren von Monaten: Monate haben unterschiedliche Längen! In einigen Anwendungen ist in solchen Fällen eine Ausnahme angebracht, in anderen ist "der letzte Tag des Vormonats" in Ordnung (aber das ist wirklich verrückte Arithmetik, wenn man einen Monat subtrahiert und dann einen Monat addiert) dies insgesamt keine Operation!) In anderen Fällen möchten Sie zusätzlich zu dem Datum einige Hinweise auf die Tatsache behalten, z. B. "Ich sage den 28. Februar, aber ich möchte wirklich den 30. Februar, wenn er existiert", damit Sie einen weiteren Monat hinzufügen oder davon abziehen das kann die Dinge wieder in Ordnung bringen (und letzteres erfordert offensichtlich eine benutzerdefinierte Klasse, die Daten plus s / thing else enthält).

Es kann keine wirkliche Lösung geben, die für alle Anwendungen tolerierbar ist, und Sie haben uns nicht mitgeteilt, welche Anforderungen Ihre spezifische App an die Semantik dieser erbärmlichen Operation stellt. Daher können wir hier nicht viel mehr Hilfe anbieten.

Alex Martelli
quelle
Du hast absolut recht. Ich habe nur darüber gescherzt, dass Timedelta ein Argument dafür hat. Ich frage mich, ob die gleiche Logik gilt, wenn man stunden- oder minutenlang keine Argumente hat. Technisch gesehen ist ein Tag nicht genau 24 Stunden.
Bialecki
PHP erledigt dies, indem es dem 28. Februar 2 Tage hinzufügt, und es geht bis zum 2. März. Es gibt Konventionen dafür, lassen Sie mich sie ganz schnell finden
NullUserException
Noch interessanter wird es, wenn Sie Daten für den Geldwechsel berechnen: z. B. müssen Sie möglicherweise ein Datum finden, das 3 oder 6 Monate in der Zukunft liegt, aber es muss auch ein Arbeitstag in beiden Ländern sein (nicht Freitag in muslimischen Ländern). und kein Feiertag in beiden Ländern.
Duncan
@ Bialecki: datetimeModule gehen davon aus, dass ein Tag genau 24 Stunden (keine Schaltsekunden ) ist
jfs
8

Ich denke, der einfache Weg ist, DateOffset von Pandas wie folgt zu verwenden:

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

Das Ergebnis ist ein Datum / Uhrzeit-Objekt

FranciscoRodríguezCuenca
quelle
2
Das ist die intuitivste Lösung
Kots
5

Wenn Sie nur einen Tag im letzten Monat wünschen, können Sie am einfachsten die Anzahl der Tage vom aktuellen Datum abziehen, um den letzten Tag des Vormonats zu erhalten.

Zum Beispiel beginnend mit einem beliebigen Datum:

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

Wenn wir die Tage des aktuellen Datums abziehen, erhalten wir:

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

Dies reicht für Ihren vereinfachten Bedarf an jedem Tag im letzten Monat.

Aber jetzt, wo Sie es haben, können Sie auch jeden Tag im Monat erhalten, einschließlich des gleichen Tages, mit dem Sie begonnen haben (dh mehr oder weniger das gleiche wie das Subtrahieren eines Monats):

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

Natürlich müssen Sie mit dem 31. an einem 30-Tage-Monat oder den ab Februar fehlenden Tagen vorsichtig sein (und sich um Schaltjahre kümmern), aber das ist auch einfach zu tun:

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

Ich benutze dies für Regierungsjahre, in denen das vierte Quartal am 1. Oktober beginnt. Hinweis Ich konvertiere das Datum in Viertel und mache es auch rückgängig.

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

Hier ist ein Code , um genau das zu tun. Ich habe es nicht selbst ausprobiert ...

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

Versuchen Sie Folgendes bei einem (Jahr, Monat) Tupel, bei dem der Monat von 1 bis 12 reicht:

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

Angenommen, jedes Jahr gibt es 12 Monate.

PaulMcG
quelle
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)
verdammt_c
quelle
0

Ich habe den folgenden Code verwendet, um n Monate von einem bestimmten Datum zurück zu gehen:

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

Zum Beispiel: Eingabedatum = 28.12.2015 6 Monate vorheriges Datum berechnen.

I) MONAT BERECHNEN: In diesem Schritt erhalten Sie das Startdatum als 30/06/2015.
Beachten Sie, dass Sie nach dem Schritt "Monat berechnen" den letzten Tag des gewünschten Monats erhalten.

II) TAG BERECHNEN: Bedingung if (start_date.day> your_date.day) prüft, ob der Tag von input_date im erforderlichen Monat vorhanden ist. Dies behandelt Bedingungen, bei denen das Eingabedatum 31 (oder 30) ist und der erforderliche Monat weniger als 31 (oder 30 im Februar) Tage hat. Es behandelt auch Schaltjahrfälle (für Februar). Nach diesem Schritt erhalten Sie das Ergebnis als 28/06/2015

Wenn diese Bedingung nicht erfüllt ist, bleibt das Startdatum das letzte Datum des Vormonats. Wenn Sie also den 31.12.2015 als Eingabedatum angeben und 6 Monate vor dem Datum möchten, erhalten Sie den 30.06.2015

ashay93k
quelle
0

Sie können die unten angegebene Funktion verwenden, um das Datum vor / nach X Monat abzurufen.

ab Datum / Uhrzeit des Importdatums

def next_month (angegebenes_Datum, Monat):
    JJJJ = int (((angegebenes_Datum.Jahr * 12 + gegebenes_Datum.Monat) + Monat) / 12)
    mm = int (((angegebenes_Datum.Jahr * 12 + gegebenes_Datum.Monat) + Monat)% 12)

    wenn mm == 0:
        JJJJ - = 1
        mm = 12

    return Given_date.replace (Jahr = JJJJ, Monat = MM)


if __name__ == "__main__":
    heute = date.today ()
    drucken (heute)

    für mm in [-12, -1, 0, 1, 2, 12, 20]:
        next_date = next_month (heute, mm)
        print (nächstes_Datum)
Nandlal Yadav
quelle
0

Ich denke, diese Antwort ist gut lesbar:

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
KugelschreiberBen
quelle
0

Einzeiler ?

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

onekiloparsec
quelle
0

Einfachste Art und Weise, wie ich es gerade versucht habe

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

Vor einiger Zeit bin ich auf den folgenden Algorithmus gestoßen, der sehr gut zum Inkrementieren und Dekrementieren von Monaten auf einem dateoder einem funktioniert datetime.

CAVEAT : Dies schlägt fehl, wenn dayes im neuen Monat nicht verfügbar ist. Ich benutze diese an Datum Objekte wo day == 1immer.

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)

Ändern Sie für Python 2.7 das //12in, /12da die Ganzzahldivision impliziert ist.

Ich habe dies kürzlich in einer Standarddatei verwendet, als ein Skript begann, diese nützlichen globalen Elemente abzurufen:

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

Einfache Lösung, keine speziellen Bibliotheken erforderlich.

Cristian Florin Ionescu
quelle
2
Funktioniert nicht, wenn das Datum etwa der 31. März ist.
Firecast
1
Oder wie am 1. Januar
LeandroHumb
-3

Sie können dies in zwei Zeilen wie folgt tun:

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

Erinnere dich an die Importe

from datetime import datetime
Omri Jacobsz
quelle
1
Was ist, wenn es Jan ist?
Sergei