Apa cara termudah untuk mengurangi satu bulan dari tanggal dengan Python?

100

Jika saja timedelta memiliki argumen bulan di konstruktornya. Jadi, apa cara termudah untuk melakukan ini?

EDIT: Saya tidak berpikir terlalu keras tentang ini seperti yang ditunjukkan di bawah ini. Sungguh yang saya inginkan adalah setiap hari di bulan lalu karena akhirnya saya akan meraih tahun dan bulan saja. Jadi berdasarkan objek datetime, apa cara termudah untuk mengembalikan objek datetime yang termasuk dalam bulan sebelumnya ?

Bialecki
sumber

Jawaban:

63

Coba ini:

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)

Edit Dikoreksi untuk menangani hari itu juga.

Sunting Lihat juga jawaban dari kebingungan yang menunjukkan perhitungan yang lebih sederhana untuk d:

d = min(date.day, calendar.monthrange(y, m)[1])
Duncan
sumber
Jika saya memilikinya dengan benar ((date.month+delta-1) % 12)+1bekerja dan berarti if not m: m = 12bisa dihapus
Charles L.
@ dan-klasson dapatkah Anda menjelaskan?
Jean-François Fabre
@ Jean-FrançoisFabre Yang pertama tidak sederhana. Juga, seseorang dapat dengan mudah melakukan ini dengan datetime dan timedelta.
dan-klasson
Ya, setelah OP diedit, menjadi jelas bahwa apa yang mereka inginkan dapat dilakukan hanya dengan dt - timedelta(days=dt.day)Seperti yang diutarakan semula meskipun kedengarannya mereka ingin dapat mengurangi jumlah bulan yang sewenang-wenang dan itu sedikit lebih sulit.
Duncan
Saya pikir perhitungan tahun kabisat tidak benar - seharusnya memang demikian 29 if y%4==0 and (not y%100==0 or y%400 == 0) else 28. misal, 1600 adalah tahun kabisat sedangkan 1700 bukan
Shawn Loewen
184

Anda dapat menggunakan dateutilmodul pihak ketiga (entri PyPI di sini ).

import datetime
import dateutil.relativedelta

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

keluaran:

2013-02-28 00:00:00
amoe
sumber
Saya bingung dengan param monthdan monthsdi dateutil. Kurangi dengan param monthtidak bekerja tetapi monthsberhasil 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: Saya pikir penggunaan monthsakan membuat perbedaan dalam 1 bulan. Menggunakan monthakan menyetel bulan ke 1 (mis. Januari), terlepas dari bulan input.
Frank Meulenaar
70

Setelah pertanyaan asli diedit ke "objek datetime mana pun di bulan sebelumnya", Anda dapat melakukannya dengan mudah dengan mengurangi 1 hari dari hari pertama bulan itu.

from datetime import datetime, timedelta

def a_day_in_previous_month(dt):
   return dt.replace(day=1) - timedelta(days=1)
Cory
sumber
4
Anda dapat menggunakandt.replace(day=1) - timedelta(1)
jfs
3
ataudt - timedelta(days=dt.day)
SergiyKolesnikov
23

Variasi pada jawaban Duncan (saya tidak memiliki reputasi yang cukup untuk berkomentar), yang menggunakan calendar.monthrange untuk menyederhanakan penghitungan hari terakhir bulan secara dramatis:

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 tentang rentang bulan dari Dapatkan Hari Terakhir Bulan Ini dengan Python

kebingungan
sumber
23

Solusi panda yang di-vectorisasi sangat sederhana:

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

Corey Levinson
sumber
Terima kasih! Ini membantu saya dengan mudah menambahkan / mengurangi satu bulan ke / dari set tanggal saya!
Mateus da Silva Teixeira
Ini bagus jika Anda bisa menggunakan pandas. Terima kasih!
igorkf
17

Jika saja timedelta memiliki argumen bulan di konstruktornya. Jadi, apa cara termudah untuk melakukan ini?

Apa yang Anda inginkan hasilnya jika Anda mengurangi satu bulan dari, misalnya, tanggal 30 Maret? Itulah masalahnya dengan menambah atau mengurangi bulan: bulan memiliki panjang yang berbeda! Dalam beberapa aplikasi, pengecualian sesuai dalam kasus seperti itu, dalam kasus lain "hari terakhir bulan sebelumnya" boleh digunakan (tapi itu benar-benar aritmatika yang gila, saat mengurangi satu bulan kemudian menambahkan satu bulan secara keseluruhan bukan berarti tidak ada operasi!) , di tempat lain, tetapi Anda ingin menambahkan beberapa indikasi tentang fakta sebagai tambahan tanggal, misalnya, "Saya mengatakan 28 Feb tapi saya benar-benar ingin 30 Feb jika ada", sehingga menambahkan atau mengurangi bulan lain untuk yang dapat mengatur segalanya dengan benar (dan yang terakhir jelas membutuhkan kelas khusus yang menyimpan data plus s / thing lainnya).

Tidak ada solusi nyata yang dapat ditoleransi untuk semua aplikasi, dan Anda belum memberi tahu kami apa kebutuhan spesifik aplikasi Anda untuk semantik operasi yang buruk ini, jadi tidak banyak lagi bantuan yang dapat kami berikan di sini.

Alex Martelli
sumber
Anda benar sekali. Hanya bercanda tentang timedelta yang berdebat untuk itu. Saya bertanya-tanya apakah logika yang sama berlaku untuk tidak berdebat selama berjam-jam atau menit. Secara teknis sehari tidak tepat 24 jam.
Bialecki
PHP menanganinya dengan menambahkan 2 hari hingga 28 Februari, dan berlanjut hingga 2 Maret. Ada
kesepakatan
Ini menjadi lebih menarik jika Anda menghitung tanggal untuk pertukaran mata uang: misalnya Anda mungkin perlu menemukan tanggal mengatakan 3 atau 6 bulan di masa depan, tetapi juga harus menjadi hari kerja di kedua negara (bukan hari Jumat di negara-negara Muslim) dan bukan hari libur bank di kedua negara.
Duncan
@Bialecki: datetimemodules mengasumsikan bahwa satu hari adalah tepat 24 jam (tanpa detik kabisat)
jfs
9

Saya pikir cara sederhananya adalah menggunakan DateOffset dari Pandas seperti ini:

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

Hasilnya akan menjadi objek datetime

FranciscoRodríguezCuenca
sumber
2
Itu adalah solusi paling intuitif
Kots
5

Jika Anda hanya menginginkan hari apa saja dalam sebulan terakhir, hal paling sederhana yang dapat Anda lakukan adalah mengurangi jumlah hari dari tanggal sekarang, yang akan memberi Anda hari terakhir bulan sebelumnya.

Misalnya, dimulai dengan tanggal berapa pun:

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

Mengurangkan hari dari tanggal saat ini yang kita dapatkan:

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

Ini cukup untuk kebutuhan Anda yang disederhanakan setiap hari dalam sebulan terakhir.

Tetapi sekarang setelah Anda memilikinya, Anda juga bisa mendapatkan hari apa pun dalam sebulan, termasuk hari yang sama saat Anda memulai (yaitu kurang lebih sama dengan mengurangi satu bulan):

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

Tentu saja, Anda perlu berhati-hati dengan tanggal 31 pada bulan 30 hari atau hari-hari yang hilang dari Februari (dan menjaga tahun kabisat), tetapi itu juga mudah dilakukan:

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

Saya menggunakan ini untuk tahun fiskal pemerintah di mana Q4 dimulai 1 Oktober. Perhatikan bahwa saya mengubah tanggal menjadi empat dan membatalkannya juga.

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

Berikut adalah beberapa kode untuk melakukannya. Belum mencobanya sendiri ...

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

Diberikan tupel (tahun, bulan), di mana bulan berjalan dari 1-12, coba ini:

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

Diasumsikan ada 12 bulan dalam setiap tahun.

PaulMcG
sumber
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)
sialan
sumber
0

Saya menggunakan kode berikut untuk kembali n Bulan dari Tanggal tertentu:

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

Misalnya: masukan tanggal = 28/12/2015 Hitung 6 bulan tanggal sebelumnya.

I) HITUNG BULAN: Langkah ini akan memberi Anda start_date sebagai 30/06/2015.
Perhatikan bahwa setelah langkah menghitung bulan Anda akan mendapatkan hari terakhir dari bulan yang diperlukan.

II) HITUNG HARI: Kondisi jika (start_date.day> your_date.day) memeriksa apakah hari dari input_date ada di bulan yang diperlukan. Ini menangani kondisi di mana tanggal input adalah 31 (atau 30) dan bulan yang diperlukan memiliki kurang dari 31 (atau 30 dalam kasus feb) hari. Ini menangani kasus tahun kabisat juga (Untuk Feb). Setelah langkah ini Anda akan mendapatkan hasil sebagai 28/06/2015

Jika kondisi ini tidak terpenuhi, start_date tetap menjadi tanggal terakhir bulan sebelumnya. Jadi jika Anda memberikan 31/12/2015 sebagai tanggal input dan menginginkan tanggal 6 bulan sebelumnya, maka Anda akan diberi 30/06/2015

ashay93k
sumber
0

Anda dapat menggunakan fungsi yang diberikan di bawah ini untuk mendapatkan tanggal sebelum / sesudah X bulan.

dari tanggal impor datetime

def next_month (given_date, bulan):
    yyyy = int (((given_date.year * 12 + given_date.month) + month) / 12)
    mm = int (((tanggal_berita.tahun * 12 + tanggal_bulan) + bulan)% 12)

    jika mm == 0:
        tttt - = 1
        mm = 12

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


jika __name__ == "__main__":
    hari ini = date.today ()
    cetak (hari ini)

    untuk mm dalam [-12, -1, 0, 1, 2, 12, 20]:
        next_date = next_month (hari ini, mm)
        cetak (tanggal_berikutnya)
Nandlal Yadav
sumber
0

Saya rasa jawaban ini cukup mudah dibaca:

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

Satu kapal?

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

onekiloparsec
sumber
0

Cara termudah yang baru saja saya coba

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

Beberapa waktu lalu saya menemukan algoritma berikut yang bekerja sangat baik untuk menambah dan mengurangi bulan pada a dateatau datetime.

PERHATIAN : Ini akan gagal jika daytidak tersedia di bulan baru. Saya menggunakan ini pada objek tanggal di mana day == 1selalu.

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)

Untuk Python 2.7 ubah //12menjadi hanya /12karena pembagian integer tersirat.

Saya baru-baru ini menggunakan ini dalam file default ketika skrip mulai mendapatkan global yang berguna ini:

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

Solusi sederhana, tidak perlu perpustakaan khusus.

Cristian Florin Ionescu
sumber
2
Tidak akan berhasil jika tanggalnya sekitar 31 Maret.
firecast
1
Atau seperti tanggal 1 Januari
LeandroHumb
-3

Anda bisa melakukannya dalam dua baris seperti ini:

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

ingat impor

from datetime import datetime
Omri Jacobsz
sumber
1
Bagaimana jika itu Jan?
Sergei