Bagaimana cara mendapatkan hari terakhir dalam sebulan?

634

Apakah ada cara menggunakan pustaka standar Python untuk dengan mudah menentukan (yaitu satu panggilan fungsi) hari terakhir dari bulan yang diberikan?

Jika perpustakaan standar tidak mendukung itu, apakah paket dateutil mendukung ini?

Cristian
sumber

Jawaban:

1083

Saya tidak memperhatikan ini sebelumnya ketika saya melihat dokumentasi untuk calendarmodul , tetapi metode yang disebut monthrangememberikan informasi ini:

monthrange (year, month)
    Mengembalikan hari kerja di hari pertama bulan itu dan jumlah hari dalam sebulan, untuk tahun dan bulan yang ditentukan.

>>> import calendar
>>> calendar.monthrange(2002,1)
(1, 31)
>>> calendar.monthrange(2008,2)
(4, 29)
>>> calendar.monthrange(2100,2)
(0, 28)

begitu:

calendar.monthrange(year, month)[1]

sepertinya cara termudah untuk pergi.

Supaya jelas, monthrangemendukung tahun kabisat juga:

>>> from calendar import monthrange
>>> monthrange(2012, 2)
(2, 29)

Jawaban saya sebelumnya masih berfungsi, tetapi jelas suboptimal.

Blair Conrad
sumber
Ini bukan jawaban yang benar untuk pertanyaan awal! Bagaimana jika hari terakhir bulan itu adalah Sabtu / Minggu.
Mahdi
8
@mahdi: benar, angka kedua adalah "nr of days in the month" == "the last day", terlepas dari apa hari itu.
RickyA
Ini mengembalikan hari yang salah untuk tahun 1800 februari. Saya tidak mengerti
sword1st
8
@ sword1st, saya melihat 28sebagai jawaban, yang benar, menggunakan aturan untuk kalender Gregorian
Blair Conrad
Saya tidak bisa menjadi satu-satunya yang berpikir monthrangeadalah nama yang membingungkan. Anda akan berpikir itu akan kembali (first_day, last_day), tidak(week_day_of_first_day, number_of_days)
Flimm
146

Jika Anda tidak ingin mengimpor calendarmodul, fungsi dua langkah sederhana juga bisa:

import datetime

def last_day_of_month(any_day):
    next_month = any_day.replace(day=28) + datetime.timedelta(days=4)  # this will never fail
    return next_month - datetime.timedelta(days=next_month.day)

Output:

>>> for month in range(1, 13):
...     print last_day_of_month(datetime.date(2012, month, 1))
...
2012-01-31
2012-02-29
2012-03-31
2012-04-30
2012-05-31
2012-06-30
2012-07-31
2012-08-31
2012-09-30
2012-10-31
2012-11-30
2012-12-31
augustomen
sumber
81

EDIT: Lihat jawaban @Blair Conrad untuk solusi yang lebih bersih


>>> import datetime
>>> datetime.date(2000, 2, 1) - datetime.timedelta(days=1)
datetime.date(2000, 1, 31)
John Millikin
sumber
43
Saya sebenarnya akan memanggil pembersih ini, kecuali kenyataan bahwa itu gagal pada bulan Desember kapan today.month + 1 == 13dan Anda mendapatkan ValueError.
fletom
4
Anda dapat mengatasinya dengan menggunakan (today.month % 12) + 1sejak 12 % 12memberikan 0
bluesmoon
Beberapa saklar siang hari yang aneh pada tanggal 31 sebulan (saya pikir itu tidak ada hari ini, tetapi masa depan mungkin berbeda) mungkin membuat tanggal 31 bulan itu dengan panjang hanya 23 jam, sehingga mengurangi satu hari memungkinkan Anda berakhir pada pukul 23:00 pada tanggal 30. Itu kasus yang aneh, tentu saja, tetapi itu menunjukkan bahwa pendekatannya tidak masuk akal.
Alfe
50

Ini sebenarnya cukup mudah dengan dateutil.relativedelta(paket python-datetutil untuk pip).day=31akan selalu selalu kembali pada hari terakhir bulan itu.

Contoh:

from datetime import datetime
from dateutil.relativedelta import relativedelta

date_in_feb = datetime.datetime(2013, 2, 21)
print datetime.datetime(2013, 2, 21) + relativedelta(day=31)  # End-of-month
>>> datetime.datetime(2013, 2, 28, 0, 0)
Vince Spicer
sumber
7
Saya pribadi suka relativedelta (bulan = +1, detik = -1) tampaknya lebih jelas apa yang terjadi
BenH
Anda salah. datetime(2014, 2, 1) + relativedelta(days=31)memberi datetime(2014, 3, 4, 0, 0)...
Emmanuel
9
Anda menggunakan hari = bukan hari =
Vince Spicer
@ Ben Itu didasarkan pada harapan (saya tidak yakin itu ditentukan) bahwa pertama monthsditambahkan, kemudian secondsdikurangi. Karena mereka disahkan karena **kwargssaya tidak akan bergantung pada itu dan melakukan serangkaian operasi terpisah: now + relative(months=+1) + relative(day=1) + relative(days=-1)yang tidak ambigu.
Alfe
last_day = (<datetime_object> + relativedelta(day=31)).day
user12345
44

EDIT: lihat jawaban saya yang lain . Ini memiliki implementasi yang lebih baik daripada yang ini, yang saya tinggalkan di sini kalau-kalau ada seseorang yang tertarik melihat bagaimana seseorang bisa "menggulung Anda sendiri" kalkulator.

@John Millikin memberikan jawaban yang baik, dengan komplikasi tambahan dalam menghitung hari pertama bulan berikutnya.

Berikut ini tidak terlalu elegan, tetapi untuk mencari tahu hari terakhir bulan itu pada tanggal tertentu, Anda dapat mencoba:

def last_day_of_month(date):
    if date.month == 12:
        return date.replace(day=31)
    return date.replace(month=date.month+1, day=1) - datetime.timedelta(days=1)

>>> last_day_of_month(datetime.date(2002, 1, 17))
datetime.date(2002, 1, 31)
>>> last_day_of_month(datetime.date(2002, 12, 9))
datetime.date(2002, 12, 31)
>>> last_day_of_month(datetime.date(2008, 2, 14))
datetime.date(2008, 2, 29)
Blair Conrad
sumber
Kedengarannya konyol tetapi bagaimana saya mendapatkan hari pertama di bulan yang sama seperti ini.
Kishan
10
Tidak selalu 1?
Blair Conrad
Ya tapi saya bingung saya mencari sesuatu seperti ini: start_date = date (datetime.now (). Tahun, datetime.now (). Bulan, 1)
Kishan
4
Ah. today = datetime.date.today(); start_date = today.replace(day=1). Anda ingin menghindari menelepon datetime. Sekarang dua kali, kalau-kalau Anda menyebutnya sebelum tengah malam pada tanggal 31 dan kemudian setelah tengah malam. Anda akan mendapatkan 2016-01-01 bukannya 2016-12-01 atau 2017-01-01.
Blair Conrad
Ini sangat mudah untuk memahami dan mengembalikan contoh datetime, yang mungkin berguna dalam banyak kasus. Keuntungan lain adalah ia berfungsi jika input dateadalah instance dari datetime.date, datetime.datetimedan juga pandas.Timestamp.
Guilherme Salomé
27

Menggunakan dateutil.relativedeltaAnda akan mendapatkan tanggal terakhir bulan seperti ini:

from dateutil.relativedelta import relativedelta
last_date_of_month = datetime(mydate.year, mydate.month, 1) + relativedelta(months=1, days=-1)

Idenya adalah untuk mendapatkan hari pertama bulan itu dan gunakan relativedeltauntuk pergi 1 bulan ke depan dan 1 hari kembali sehingga Anda akan mendapatkan hari terakhir bulan yang Anda inginkan.

Satish Reddy
sumber
13

Solusi lain adalah melakukan sesuatu seperti ini:

from datetime import datetime

def last_day_of_month(year, month):
    """ Work out the last day of the month """
    last_days = [31, 30, 29, 28, 27]
    for i in last_days:
        try:
            end = datetime(year, month, i)
        except ValueError:
            continue
        else:
            return end.date()
    return None

Dan gunakan fungsi seperti ini:

>>> 
>>> last_day_of_month(2008, 2)
datetime.date(2008, 2, 29)
>>> last_day_of_month(2009, 2)
datetime.date(2009, 2, 28)
>>> last_day_of_month(2008, 11)
datetime.date(2008, 11, 30)
>>> last_day_of_month(2008, 12)
datetime.date(2008, 12, 31)
Ulf Gjerdingen
sumber
5
Terlalu rumit, melanggar aturan ketiga zen dari python.
markuz
11
from datetime import timedelta
(any_day.replace(day=1) + timedelta(days=32)).replace(day=1) - timedelta(days=1)
Collin Anderson
sumber
Inilah yang membuat bug;) Coba dengan 31 Januari
LeartS
@LeartS: ini berfungsi untuk saya. Apa yang terjadi saat kamu mencobanya?
Collin Anderson
1
Berhasil. any_day adalah 31 Januari, kami mengganti hari dengan 1, jadi 1 Jan, tambahkan 32 hari, kami mendapatkan 2 Februari, ganti dengan hari = 1 lagi dan kami mendapatkan 1 Februari. Kurangi satu hari dan kami dapatkan 31 Januari. Saya tidak melihat apa masalahnya? Hari apa yang kamu dapat?
Collin Anderson
9
>>> import datetime
>>> import calendar
>>> date  = datetime.datetime.now()

>>> print date
2015-03-06 01:25:14.939574

>>> print date.replace(day = 1)
2015-03-01 01:25:14.939574

>>> print date.replace(day = calendar.monthrange(date.year, date.month)[1])
2015-03-31 01:25:14.939574
Vatsal
sumber
9

jika Anda bersedia menggunakan perpustakaan eksternal, lihat http://crsmithdev.com/arrow/

Anda dapat memperoleh hari terakhir bulan itu dengan:

import arrow
arrow.utcnow().ceil('month').date()

Ini mengembalikan objek tanggal yang kemudian dapat Anda lakukan manipulasi Anda.

Blake
sumber
9

Untuk mendapatkan tanggal terakhir bulan ini kami melakukan sesuatu seperti ini:

from datetime import date, timedelta
import calendar
last_day = date.today().replace(day=calendar.monthrange(date.today().year, date.today().month)[1])

Sekarang untuk menjelaskan apa yang kita lakukan di sini kita akan memecahnya menjadi dua bagian:

pertama adalah mendapatkan jumlah hari dalam bulan ini yang kami gunakan untuk mengatur rentang bulan yang sudah disebutkan oleh Blair Conrad solusinya:

calendar.monthrange(date.today().year, date.today().month)[1]

kedua adalah mendapatkan tanggal terakhir itu sendiri yang kami lakukan dengan bantuan ganti mis

>>> date.today()
datetime.date(2017, 1, 3)
>>> date.today().replace(day=31)
datetime.date(2017, 1, 31)

dan ketika kita menggabungkannya seperti yang disebutkan di atas kita mendapatkan solusi yang dinamis.

Siddharth K
sumber
Meskipun kode ini dapat membantu menyelesaikan masalah, kode ini tidak menjelaskan mengapa dan / atau bagaimana ia menjawab pertanyaan. Memberikan konteks tambahan ini akan secara signifikan meningkatkan nilai pendidikan jangka panjangnya. Harap edit jawaban Anda untuk menambahkan penjelasan, termasuk batasan dan asumsi apa yang berlaku.
Toby Speight
Ini tampaknya yang paling mudah .. jika Anda ingin memberikannya dua baris Anda bisa mendapatkan yang bagus date.replace(day=day)sehingga semua orang tahu apa yang terjadi.
Adam Starrh
9

Di Python 3.7 ada fungsi tidak berdokumencalendar.monthlen(year, month) :

>>> calendar.monthlen(2002, 1)
31
>>> calendar.monthlen(2008, 2)
29
>>> calendar.monthlen(2100, 2)
28

Ini setara dengan panggilan yang didokumentasikancalendar.monthrange(year, month)[1] .

jfs
sumber
1
Ini sepertinya tidak berfungsi di Python 3.8.1: AttributeError: module 'calendar' tidak memiliki atribut 'monthlen'
jeppoo1
1
@ jeppoo1: ya, ini ditandai pribadi di bpo-28292 - diharapkan untuk fungsi tidak berdokumen.
jfs
5
import datetime

now = datetime.datetime.now()
start_month = datetime.datetime(now.year, now.month, 1)
date_on_next_month = start_month + datetime.timedelta(35)
start_next_month = datetime.datetime(date_on_next_month.year, date_on_next_month.month, 1)
last_day_month = start_next_month - datetime.timedelta(1)
Анатолий Панин
sumber
5

Gunakan panda!

def isMonthEnd(date):
    return date + pd.offsets.MonthEnd(0) == date

isMonthEnd(datetime(1999, 12, 31))
True
isMonthEnd(pd.Timestamp('1999-12-31'))
True
isMonthEnd(pd.Timestamp(1965, 1, 10))
False
Steve Schulist
sumber
1
Anda juga dapat menerapkan menggunakan pd.series.dt.is_month_end tautan
Pablo
1
Objek datetime panda memiliki metode khusus untuk itu:now=datetime.datetime.now(); pd_now = pd.to_datetime(now); print(pd_now.days_in_month)
Roei Bahumi
3

Bagi saya itu cara paling sederhana:

selected_date = date(some_year, some_month, some_day)

if selected_date.month == 12: # December
     last_day_selected_month = date(selected_date.year, selected_date.month, 31)
else:
     last_day_selected_month = date(selected_date.year, selected_date.month + 1, 1) - timedelta(days=1)
KravAn
sumber
3

Cara termudah (tanpa harus mengimpor kalender), adalah untuk mendapatkan hari pertama bulan berikutnya, dan kemudian kurangi satu hari darinya.

import datetime as dt
from dateutil.relativedelta import relativedelta

thisDate = dt.datetime(2017, 11, 17)

last_day_of_the_month = dt.datetime(thisDate.year, (thisDate + relativedelta(months=1)).month, 1) - dt.timedelta(days=1)
print last_day_of_the_month

Keluaran:

datetime.datetime(2017, 11, 30, 0, 0)

PS: Kode ini berjalan lebih cepat dibandingkan dengan import calendarpendekatan; Lihat di bawah:

import datetime as dt
import calendar
from dateutil.relativedelta import relativedelta

someDates = [dt.datetime.today() - dt.timedelta(days=x) for x in range(0, 10000)]

start1 = dt.datetime.now()
for thisDate in someDates:
    lastDay = dt.datetime(thisDate.year, (thisDate + relativedelta(months=1)).month, 1) - dt.timedelta(days=1)

print ('Time Spent= ', dt.datetime.now() - start1)


start2 = dt.datetime.now()
for thisDate in someDates:
    lastDay = dt.datetime(thisDate.year, 
                          thisDate.month, 
                          calendar.monthrange(thisDate.year, thisDate.month)[1])

print ('Time Spent= ', dt.datetime.now() - start2)

KELUARAN:

Time Spent=  0:00:00.097814
Time Spent=  0:00:00.109791

Kode ini mengasumsikan bahwa Anda menginginkan tanggal hari terakhir bulan itu (yaitu, bukan hanya bagian DD, tetapi seluruh tanggal YYYYMMDD)

Vishal
sumber
mengapa kamu tidak mau import calendar?
Adam Venturella
1
Karena lebih cepat. Saya telah memodifikasi jawaban saya di atas untuk memasukkan ini.
Vishal
@Vishal Anda mendapatkan konsep yang benar tetapi baris berikut tidak: `` `dt.datetime (thisDate.year, (thisDate + relativedelta (bulan = 1)). Bulan, 1) - dt.timedelta (hari = 1)` `` terutama jika bulannya di akhir tahun. coba `` `last_date_of_month = \ first_date_of_month + relativedelta (bulan = 1) - relativedelta (hari = 1)` `` sebagai gantinya
XValidated
3

Ini jawaban lain. Tidak diperlukan paket tambahan.

datetime.date(year + int(month/12), month%12+1, 1)-datetime.timedelta(days=1)

Dapatkan hari pertama bulan berikutnya dan kurangi satu hari darinya.

kevswanberg
sumber
2

Anda dapat menghitung sendiri tanggal akhirnya. Logikanya sederhana adalah mengurangi satu hari dari tanggal mulai bulan depan. :)

Jadi, tulis metode khusus,

import datetime

def end_date_of_a_month(date):


    start_date_of_this_month = date.replace(day=1)

    month = start_date_of_this_month.month
    year = start_date_of_this_month.year
    if month == 12:
        month = 1
        year += 1
    else:
        month += 1
    next_month_start_date = start_date_of_this_month.replace(month=month, year=year)

    this_month_end_date = next_month_start_date - datetime.timedelta(days=1)
    return this_month_end_date

Panggilan,

end_date_of_a_month(datetime.datetime.now().date())

Ini akan mengembalikan tanggal akhir bulan ini. Berikan tanggal apa pun ke fungsi ini. mengembalikan tanggal akhir bulan itu kepada Anda.

Vipul Wisnu av
sumber
1

Ini adalah solusi paling sederhana bagi saya hanya menggunakan perpustakaan datetime standar:

import datetime

def get_month_end(dt):
    first_of_month = datetime.datetime(dt.year, dt.month, 1)
    next_month_date = first_of_month + datetime.timedelta(days=32)
    new_dt = datetime.datetime(next_month_date.year, next_month_date.month, 1)
    return new_dt - datetime.timedelta(days=1)
Toby Petty
sumber
0

Ini tidak menjawab pertanyaan utama, tetapi satu trik bagus untuk mendapatkan hari kerja terakhir dalam sebulan adalah menggunakan calendar.monthcalendar, yang mengembalikan matriks tanggal, yang diselenggarakan dengan Senin sebagai kolom pertama hingga Minggu sebagai yang terakhir.

# Some random date.
some_date = datetime.date(2012, 5, 23)

# Get last weekday
last_weekday = np.asarray(calendar.monthcalendar(some_date.year, some_date.month))[:,0:-2].ravel().max()

print last_weekday
31

Seluruh [0:-2]hal yang mencukur kolom akhir pekan dan melemparkan mereka keluar. Tanggal yang jatuh di luar bulan ditandai dengan 0, sehingga maks secara efektif mengabaikannya.

Penggunaan numpy.raveltidak sepenuhnya diperlukan, tapi saya benci mengandalkan konvensi belaka yang numpy.ndarray.maxakan meratakan array jika tidak diberi tahu sumbu mana yang harus dihitung.

Ely
sumber
0
import calendar
from time import gmtime, strftime
calendar.monthrange(int(strftime("%Y", gmtime())), int(strftime("%m", gmtime())))[1]

Keluaran:

31



Ini akan mencetak hari terakhir dari bulan apa pun saat ini. Dalam contoh ini adalah 15 Mei 2016. Jadi output Anda mungkin berbeda, namun outputnya akan sama dengan hari saat bulan ini. Hebat jika Anda ingin memeriksa hari terakhir bulan itu dengan menjalankan tugas cron harian.

Begitu:

import calendar
from time import gmtime, strftime
lastDay = calendar.monthrange(int(strftime("%Y", gmtime())), int(strftime("%m", gmtime())))[1]
today = strftime("%d", gmtime())
lastDay == today

Keluaran:

False

Kecuali itu ADALAH hari terakhir bulan itu.

Audstanley
sumber
0

Saya lebih suka cara ini

import datetime
import calendar

date=datetime.datetime.now()
month_end_date=datetime.datetime(date.year,date.month,1) + datetime.timedelta(days=calendar.monthrange(date.year,date.month)[1] - 1)
MikA
sumber
0

Jika Anda ingin membuat fungsi kecil Anda sendiri, ini adalah titik awal yang baik:

def eomday(year, month):
    """returns the number of days in a given month"""
    days_per_month = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
    d = days_per_month[month - 1]
    if month == 2 and (year % 4 == 0 and year % 100 != 0 or year % 400 == 0):
        d = 29
    return d

Untuk ini, Anda harus tahu aturan untuk tahun kabisat:

  • setiap tahun keempat
  • dengan pengecualian setiap 100 tahun
  • tapi sekali lagi setiap 400 tahun
matematika
sumber
1
Tentu, tetapi pustaka sistem sudah memiliki data ini, dan haruskah peraturan diubah dengan keputusan memperbarui pustaka adalah masalah orang lain.
Jasen
Yah, mungkin, tetapi sangat tidak mungkin, dan bahkan jika - itu hanya akan menggigit Anda dalam waktu sekitar 2000 tahun ... en.wikipedia.org/wiki/Gregorian_calendar#Accuracy
mathause
Aturan-aturan ini tidak berfungsi selama 500 tahun di masa lalu. Saya tidak yakin mereka akan berdiri selama ribuan tahun di masa depan,
Jasen
0

Jika Anda melewati rentang tanggal, Anda dapat menggunakan ini:

def last_day_of_month(any_days):
    res = []
    for any_day in any_days:
        nday = any_day.days_in_month -any_day.day
        res.append(any_day + timedelta(days=nday))
    return res
JLord
sumber
0

Dalam kode di bawah ini 'get_last_day_of_month (dt)' akan memberi Anda ini, dengan tanggal dalam format string seperti 'YYYY-MM-DD'.

import datetime

def DateTime( d ):
    return datetime.datetime.strptime( d, '%Y-%m-%d').date()

def RelativeDate( start, num_days ):
    d = DateTime( start )
    return str( d + datetime.timedelta( days = num_days ) )

def get_first_day_of_month( dt ):
    return dt[:-2] + '01'

def get_last_day_of_month( dt ):
    fd = get_first_day_of_month( dt )
    fd_next_month = get_first_day_of_month( RelativeDate( fd, 31 ) )
    return RelativeDate( fd_next_month, -1 )
Bansal Pulkit
sumber
0

Cara paling sederhana adalah dengan menggunakan datetimedan beberapa matematika tanggal, mis. Kurangi satu hari dari hari pertama bulan berikutnya:

import datetime

def last_day_of_month(d: datetime.date) -> datetime.date:
    return (
        datetime.date(d.year + d.month//12, d.month % 12 + 1, 1) -
        datetime.timedelta(days=1)
    )

Atau, Anda dapat menggunakan calendar.monthrange()untuk mendapatkan jumlah hari dalam sebulan (dengan memperhitungkan tahun kabisat) dan memperbarui tanggal sesuai dengan itu:

import calendar, datetime

def last_day_of_month(d: datetime.date) -> datetime.date:
    return d.replace(day=calendar.monthrange(d.year, d.month)[1])

Patokan cepat menunjukkan bahwa versi pertama terasa lebih cepat:

In [14]: today = datetime.date.today()

In [15]: %timeit last_day_of_month_dt(today)
918 ns ± 3.54 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

In [16]: %timeit last_day_of_month_calendar(today)
1.4 µs ± 17.3 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
Eugene Yarmash
sumber
0

Ini adalah versi yang panjang (mudah dipahami) tetapi menangani tahun kabisat.

bersorak, JK

def last_day_month(year, month):
    leap_year_flag = 0
    end_dates = {
        1: 31,
        2: 28,
        3: 31,
        4: 30,
        5: 31,
        6: 30,
        7: 31,
        8: 31,
        9: 30,
        10: 31,
        11: 30,
        12: 31
    }

    # Checking for regular leap year    
    if year % 4 == 0:
        leap_year_flag = 1
    else:
        leap_year_flag = 0

    # Checking for century leap year    
    if year % 100 == 0:
        if year % 400 == 0:
            leap_year_flag = 1
        else:
            leap_year_flag = 0
    else:
        pass

    # return end date of the year-month
    if leap_year_flag == 1 and month == 2:
        return 29
    elif leap_year_flag == 1 and month != 2:
        return end_dates[month]
    else:
        return end_dates[month]
jp0d
sumber
Anda dapat menghapus else: passdan juga Anda dapat menghapus if year % 400 == 0: leap_year_flag = 1dengan modifikasi kecil
canbax
-1

Berikut ini adalah solusi berdasarkan python lambdas:

next_month = lambda y, m, d: (y, m + 1, 1) if m + 1 < 13 else ( y+1 , 1, 1)
month_end  = lambda dte: date( *next_month( *dte.timetuple()[:3] ) ) - timedelta(days=1)

The next_monthlambda menemukan representasi tuple dari hari pertama bulan berikutnya, dan berguling ke tahun depan. The month_endlambda mengubah tanggal ( dte) untuk tupel, berlaku next_monthdan menciptakan tanggal baru. Maka "akhir bulan" hanyalah hari pertama minus bulan depan timedelta(days=1).

Johannes Blaschke
sumber