Jaki jest najprostszy sposób na odjęcie miesiąca od daty w Pythonie?

102

Gdyby tylko timedelta miał argument miesiąca w swoim konstruktorze. Jaki jest najprostszy sposób na zrobienie tego?

EDYCJA: Nie myślałem o tym zbyt intensywnie, jak wskazano poniżej. Naprawdę chciałem każdego dnia w ostatnim miesiącu, ponieważ w końcu zamierzam złapać tylko rok i miesiąc. Zatem biorąc pod uwagę obiekt daty i godziny, jaki jest najprostszy sposób zwrócenia dowolnego obiektu daty i godziny, który przypada w poprzednim miesiącu ?

Białeckiego
źródło

Odpowiedzi:

64

Spróbuj tego:

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%100==0 or 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)

Edycja Poprawiona, aby obsłużyć również dzień.

Edytuj Zobacz także odpowiedź z zdziwienia, która wskazuje na prostsze obliczenie d:

d = min(date.day, calendar.monthrange(y, m)[1])
Duncan
źródło
Jeśli mam to dobrze, to ((date.month+delta-1) % 12)+1działa i oznacza, że if not m: m = 12puszka zostanie usunięta
Charles L.
@ dan-klasson czy możesz to rozwinąć?
Jean-François Fabre
@ Jean-FrançoisFabre Pierwsza z nich nie jest prosta. Można to również łatwo zrobić za pomocą datetime i timedelta.
dan-klasson
Tak, po edycji PO staje się jasne, że to, czego chcą, można zrobić po prostu przez dt - timedelta(days=dt.day)Jak pierwotnie sformułowano, chociaż brzmiało to tak, jakby chcieli mieć możliwość odjęcia dowolnej liczby miesięcy, a to jest nieco trudniejsze.
Duncan
Myślę, że obliczenia lat przestępnych są niepoprawne - powinno być 29 if y%4==0 and (not y%100==0 or y%400 == 0) else 28. np. 1600 to rok przestępny, a 1700 nie
Shawn Loewen
188

Możesz skorzystać z dateutilmodułu strony trzeciej (wpis PyPI tutaj ).

import datetime
import dateutil.relativedelta

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

wynik:

2013-02-28 00:00:00
amoe
źródło
Jestem zdezorientowany z param monthi monthsdatutilem. Odejmowanie za pomocą parametru monthnie działa, ale monthsdziała 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
2
@SiminJie: Myślę, że używanie monthsbędzie miało wpływ na 1 miesiąc. Użycie monthspowoduje ustawienie miesiąca na 1 (np. Styczeń), niezależnie od miesiąca wprowadzenia.
Frank Meulenaar
71

Po edycji oryginalnego pytania na „dowolny obiekt daty i godziny w poprzednim miesiącu” możesz to zrobić dość łatwo, odejmując 1 dzień od pierwszego dnia miesiąca.

from datetime import datetime, timedelta

def a_day_in_previous_month(dt):
   return dt.replace(day=1) - timedelta(days=1)
Cory
źródło
4
mógłbyś użyć dt.replace(day=1) - timedelta(1)
jfs
3
lubdt - timedelta(days=dt.day)
SergiyKolesnikov
25

Wektoryzowane rozwiązanie pandy jest bardzo proste:

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

Corey Levinson
źródło
Dziękuję Ci! Pomogło mi to łatwo dodać / odjąć miesiąc do / od mojego zestawu dat!
Mateus da Silva Teixeira
To miło, jeśli możesz użyć pandas. Dzięki!
igorkf
24

Odmiana odpowiedzi Duncana (nie mam wystarczającej reputacji, aby komentować), która wykorzystuje calendar.monthrange do radykalnego uproszczenia obliczeń ostatniego dnia miesiąca:

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)

Informacje o przedziale miesięcznym z Pobierz ostatni dzień miesiąca w Pythonie

zakłopotanie
źródło
17

Gdyby tylko timedelta miał argument miesiąca w swoim konstruktorze. Jaki jest najprostszy sposób na zrobienie tego?

Jaki ma być wynik, jeśli odejmiemy miesiąc od, powiedzmy, 30 marca? Na tym polega problem z dodawaniem lub odejmowaniem miesięcy: miesiące mają różną długość! W niektórych aplikacjach wyjątek jest odpowiedni w takich przypadkach, w innych można użyć „ostatniego dnia poprzedniego miesiąca” (ale to naprawdę szalona arytmetyka, gdy odejmowanie miesiąca to dodanie miesiąca nie oznacza ogólnie braku działania!) , w innych zechcesz oprócz daty zachować pewne wskazówki dotyczące faktu, np. „Mówię o 28 lutego, ale naprawdę chciałbym, aby 30 lutego istniał”, aby dodać lub odjąć kolejny miesiąc do które mogą ponownie naprawić sytuację (a ta ostatnia oczywiście wymaga niestandardowej klasy przechowującej dane i inne elementy).

Nie może istnieć żadne prawdziwe rozwiązanie, które byłoby tolerowane dla wszystkich aplikacji, a nie powiedziałeś nam, jakie są potrzeby Twojej aplikacji w zakresie semantyki tej nieszczęsnej operacji, więc nie możemy tutaj zapewnić dużo więcej pomocy.

Alex Martelli
źródło
Masz całkowitą rację. Żartowałem tylko, że Timedelta ma na to argument. Zastanawiam się, czy ta sama logika obowiązuje w przypadku braku argumentów przez godziny lub minuty. Technicznie rzecz biorąc, dzień to nie dokładnie 24 godziny.
Białecki
PHP radzi sobie z tym , dodając 2 dni do 28 lutego i przechodzi do 2 marca. Są na to konwencje, pozwólcie, że znajdę je naprawdę szybko
NullUserException,
Jeszcze ciekawiej robi się, gdy obliczasz daty wymiany walut: np. Możesz potrzebować znaleźć datę, powiedzmy 3 lub 6 miesięcy w przyszłości, ale musi to być również dzień roboczy w obu krajach (nie piątek w krajach muzułmańskich) a nie święto państwowe w żadnym kraju.
Duncan
@Bialecki: datetimemoduły zakładają, że doba ma dokładnie 24 godziny (bez sekund przestępnych)
jfs
9

Myślę, że najprostszym sposobem jest użycie DateOffset z Pandas w następujący sposób:

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

Rezultatem będzie obiekt datetime

FranciscoRodríguezCuenca
źródło
2
To najbardziej intuicyjne rozwiązanie
Kots
6

Jeśli chcesz tylko dowolnego dnia w ostatnim miesiącu, najprostszą rzeczą, jaką możesz zrobić, jest odjęcie liczby dni od bieżącej daty, co da ci ostatni dzień poprzedniego miesiąca.

Na przykład zaczynając od dowolnej daty:

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

Odejmując dni z bieżącej daty otrzymujemy:

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

To wystarczy na uproszczoną potrzebę dowolnego dnia w ostatnim miesiącu.

Ale teraz, gdy go masz, możesz również otrzymać dowolny dzień w miesiącu, w tym ten sam dzień, od którego zacząłeś (czyli mniej więcej to samo, co odejmowanie miesiąca):

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

Oczywiście musisz uważać na 31. dnia 30-dniowego miesiąca lub dni brakujące od lutego (i zadbać o lata przestępne), ale to też jest łatwe:

>>> 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
źródło
1

Używam tego w rządowych latach budżetowych, w których czwarty kwartał rozpoczyna się 1 października. Uwaga: Konwertuję datę na kwartały i również ją cofam.

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
źródło
1

Zwraca ostatni dzień ostatniego miesiąca:

>>> import datetime
>>> datetime.datetime.now() - datetime.timedelta(days=datetime.datetime.now().day)
datetime.datetime(2020, 9, 30, 14, 13, 15, 67582)

Zwraca ten sam dzień w zeszłym miesiącu:

>>> x = datetime.datetime.now() - datetime.timedelta(days=datetime.datetime.now().day)
>>> x.replace(day=datetime.datetime.now().day)
datetime.datetime(2020, 9, 7, 14, 22, 14, 362421)
Rayhan Mia
źródło
0

Oto kod, który to robi. Sam tego nie wypróbowałem ...

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
źródło
0

Biorąc pod uwagę krotkę (rok, miesiąc), w której miesiąc trwa od 1 do 12, spróbuj tego:

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

Zakłada się, że w każdym roku jest 12 miesięcy.

PaulMcG
źródło
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
źródło
0

Użyłem następującego kodu, aby cofnąć się n miesięcy od określonej daty:

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

Na przykład: data wprowadzenia = 28/12/2015 Oblicz 6 miesięcy poprzednią datę.

I) OBLICZ MIESIĄC: W tym kroku podana zostanie data_początkowa jako 30/06/2015.
Zwróć uwagę, że po kroku obliczania miesiąca otrzymasz ostatni dzień wymaganego miesiąca.

II) KALKULUJ DZIEŃ: Warunek, jeśli (data_początkowa.dzień> twoja_data.dzień) sprawdza, czy dzień z input_date występuje w wymaganym miesiącu. Dotyczy to warunku, w którym data wprowadzenia to 31 (lub 30), a wymagany miesiąc ma mniej niż 31 (lub 30 w przypadku lutego) dni. Obsługuje również przypadek roku przestępnego (dla lutego). Po tym kroku otrzymasz wynik jako 28/06/2015

Jeśli ten warunek nie jest spełniony, data_początkowa pozostaje ostatnią datą poprzedniego miesiąca. Więc jeśli podasz 31/12/2015 jako datę wejściową i chcesz 6 miesięcy wcześniejszą datę, otrzymasz 30/06/2015

ashay93k
źródło
0

Możesz użyć poniższej funkcji, aby uzyskać datę przed / po X miesiącu.

od daty importu typu datetime

def next_month (data_dana, miesiąc):
    rrrr = int (((podana_data.rok * 12 + podana_data.miesiąc) + miesiąc) / 12)
    mm = int (((podana_data.rok * 12 + podana_data.miesiąc) + miesiąc)% 12)

    jeśli mm == 0:
        rrrr - = 1
        mm = 12

    return dana_data.replace (rok = rrrr, miesiąc = mm)


if __name__ == "__main__":
    dzisiaj = date.today ()
    drukuj (dzisiaj)

    dla mm w [-12, -1, 0, 1, 2, 12, 20]:
        next_date = next_month (dzisiaj, mm)
        drukuj (następna_data)
Nandlal Yadav
źródło
0

Myślę, że ta odpowiedź jest całkiem czytelna:

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
źródło
0

Jedna wkładka?

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

onekiloparsec
źródło
0

Najprostszy sposób, który wypróbowałem Właśnie teraz

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
źródło
0

Jakiś czas temu natknąłem się na następujący algorytm, który działa bardzo dobrze dla zwiększania i zmniejszania miesięcy albo na datelub datetime.

UWAGA : To się nie powiedzie, jeśli daynie będzie dostępne w nowym miesiącu. Używam tego na obiektach daty, gdzie day == 1zawsze.

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)

W przypadku Pythona 2.7 zmień wartość //12na, /12ponieważ zakłada się dzielenie liczb całkowitych.

Niedawno użyłem tego w pliku domyślnym, gdy skrypt zaczął pobierać te przydatne globale:

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
źródło
-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)

Proste rozwiązanie, bez specjalnych bibliotek.

Cristian Florin Ionescu
źródło
2
Nie zadziała, gdy data to mniej więcej 31 marca.
firecast
1
Lub jak 1 stycznia
LeandroHumb
-3

Możesz to zrobić w dwóch wierszach w ten sposób:

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

pamiętaj import

from datetime import datetime
Omri Jacobsz
źródło
1
A jeśli to Jan?
Siergiej