Jak uzyskać ostatni dzień miesiąca?

632

Czy istnieje sposób użycia standardowej biblioteki Pythona do łatwego ustalenia (tj. Jednego wywołania funkcji) ostatniego dnia danego miesiąca?

Jeśli standardowa biblioteka tego nie obsługuje, to czy pakiet dateutil obsługuje to?

Cristian
źródło

Odpowiedzi:

1082

Nie zauważyłem tego wcześniej, kiedy przeglądałem dokumentację calendarmodułu , ale metoda o nazwie monthrangezapewnia następujące informacje:

monthrange (rok, miesiąc)
    Zwraca dzień tygodnia pierwszego dnia miesiąca i liczbę dni w miesiącu dla określonego roku i miesiąca.

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

więc:

calendar.monthrange(year, month)[1]

wydaje się najprostszą drogą.

Dla jasności monthrangeobsługuje także lata przestępne:

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

Moja poprzednia odpowiedź wciąż działa, ale jest wyraźnie nieoptymalna.

Blair Conrad
źródło
To nie jest poprawna odpowiedź na pierwotne pytanie! Co jeśli ostatni dzień miesiąca to sobota / niedziela.
Mahdi
8
@mahdi: jest poprawne, druga liczba to „liczba dni w miesiącu” == „ostatni dzień”, niezależnie od tego, jaki to dzień.
RickyA
Zwraca zły dzień dla 1800 roku lutego. Nie rozumiem
miecz1
8
@ sword1st, widzę 28jako odpowiedź, która jest poprawna, przy użyciu reguł kalendarza gregoriańskiego
Blair Conrad
Nie mogę być jedynym, który myśli, że monthrangeto mylące imię. Można by pomyśleć, że wróci (first_day, last_day), a nie(week_day_of_first_day, number_of_days)
Flimm
146

Jeśli nie chcesz importować calendarmodułu, prostą funkcją dwuetapową może być również:

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)

Wyjścia:

>>> 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
sierpień
źródło
81

EDYCJA: Zobacz odpowiedź @Blair Conrad na czystsze rozwiązanie


>>> import datetime
>>> datetime.date(2000, 2, 1) - datetime.timedelta(days=1)
datetime.date(2000, 1, 31)
John Millikin
źródło
43
Nazwałbym to czystszym, z wyjątkiem faktu, że zawodzi w grudniu, kiedy today.month + 1 == 13i dostajesz ValueError.
fletom
4
Możesz rozwiązać ten problem, używając 0, (today.month % 12) + 1ponieważ 12 % 12daje 0
bluesmoon
Jakiś dziwny przełącznik zmiany czasu na 31 dnia miesiąca (myślę, że dziś nie istnieje, ale przyszłość może być inna) może sprawić, że 31 dnia tego miesiąca będzie trwał zaledwie 23 godziny, więc odjęcie jednego dnia pozwoli ci kończy się o godzinie 23:00 w dniu 30. To dziwny przypadek, jasne, ale pokazuje, że podejście nie jest rozsądne.
Alfe
50

W rzeczywistości jest to dość łatwe dateutil.relativedelta(pakiet python-datetutil dla pip). day=31zawsze zawsze zwraca ostatni dzień miesiąca.

Przykład:

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
źródło
7
Osobiście lubię relativedelta (miesiące = + 1, sekundy = -1) wydaje się bardziej oczywiste, co się dzieje
BenH
Jesteś w błędzie. datetime(2014, 2, 1) + relativedelta(days=31)daje datetime(2014, 3, 4, 0, 0)...
Emmanuel
9
użyłeś dni = zamiast dnia =
Vince Spicer
@BenH To jest oparte na nadziei (nie jestem pewien, czy jest to określone), że najpierw monthsjest dodawane, a następnie secondsodejmowane. Ponieważ są one przekazywane, ponieważ **kwargsnie polegałbym na tym i wykonałbym szereg oddzielnych operacji: now + relative(months=+1) + relative(day=1) + relative(days=-1)co jest jednoznaczne.
Alfe
last_day = (<datetime_object> + relativedelta(day=31)).day
user12345 18.10.19
44

EDYCJA: zobacz moją drugą odpowiedź . Ma lepszą implementację niż ta, którą zostawiam tutaj na wypadek, gdyby ktoś chciał zobaczyć, jak można „wyrzucić własny” kalkulator.

@John Millikin daje dobrą odpowiedź, z dodatkową komplikacją obliczania pierwszego dnia następnego miesiąca.

Poniższe informacje nie są szczególnie eleganckie, ale aby dowiedzieć się, jaki jest ostatni dzień miesiąca, w którym przypada data, możesz spróbować:

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
źródło
Brzmi głupio, ale jak mogę uzyskać pierwszy dzień miesiąca w podobny sposób.
Kishan,
10
Czy to nie zawsze 1?
Blair Conrad,
Tak, ale byłem zdezorientowany, szukałem czegoś takiego: data_początkowa = data (datetime.now (). Rok, datetime.now (). Miesiąc, 1)
Kishan
4
Ach today = datetime.date.today(); start_date = today.replace(day=1). Chciałbyś uniknąć dzwonienia do datetime.now dwa razy, na wypadek, gdyby zadzwoniłeś tuż przed północą 31 grudnia, a potem tuż po północy. Dostaniesz 01.01.2016 zamiast 01.12.2016 lub 01.01.2017.
Blair Conrad,
Jest to bardzo proste do zrozumienia i zwraca instancję daty i godziny, co może być przydatne w wielu przypadkach. Kolejną zaletą jest to, że działa, jeśli dane wejściowe datesą instancją datetime.date, datetime.datetimea także pandas.Timestamp.
Guilherme Salomé
26

Za pomocą dateutil.relativedeltaotrzymasz ostatnią datę miesiąca w ten sposób:

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

Chodzi o to, aby uzyskać pierwszy dzień miesiąca i relativedeltaprzejść do następnego miesiąca i 1 dnia wstecz, aby uzyskać ostatni dzień miesiąca, który chciałeś.

Satish Reddy
źródło
13

Innym rozwiązaniem byłoby zrobienie czegoś takiego:

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

I użyj następującej funkcji:

>>> 
>>> 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
źródło
5
Zbyt skomplikowane, łamie trzecią zasadę zen Pythona.
markuz
11
from datetime import timedelta
(any_day.replace(day=1) + timedelta(days=32)).replace(day=1) - timedelta(days=1)
Collin Anderson
źródło
Z tego powstają błędy;) Spróbuj z 31 stycznia
LeartS
@LeartS: działa dla mnie. Co się stanie, gdy spróbujesz?
Collin Anderson,
1
To działa. any_day to 31 stycznia, zamieniamy dzień na 1, więc 1 stycznia, dodajemy 32 dni, otrzymujemy 2 lutego, zamieniamy ponownie na dzień = 1 i otrzymujemy 1 lutego. Odejmij jeden dzień i otrzymamy 31 stycznia. Nie widzę w czym jest problem. Jaki masz dzień
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
źródło
9

jeśli chcesz skorzystać z zewnętrznej biblioteki, sprawdź http://crsmithdev.com/arrow/

U możesz uzyskać ostatni dzień miesiąca dzięki:

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

Zwraca to obiekt daty, który możesz następnie manipulować.

Blake
źródło
9

Aby uzyskać ostatnią datę miesiąca, robimy coś takiego:

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

Teraz, aby wyjaśnić, co tu robimy, podzielimy to na dwie części:

po pierwsze, podaje liczbę dni bieżącego miesiąca, dla których używamy przedziału miesiąca, o którym Blair Conrad wspomniał już o swoim rozwiązaniu:

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

drugim jest sama data, którą robimy za pomocą zamiany np

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

a kiedy je połączymy, jak wspomniano na górze, otrzymujemy dynamiczne rozwiązanie.

Siddharth K.
źródło
Chociaż ten kod może pomóc w rozwiązaniu problemu, nie wyjaśnia, dlaczego i / lub jak odpowiada na pytanie. Zapewnienie tego dodatkowego kontekstu znacznie poprawiłoby jego długoterminową wartość edukacyjną. Proszę edytować swoje odpowiedzi, aby dodać wyjaśnienie, w tym, co stosuje się ograniczenia i założenia.
Toby Speight
Wydaje się to najprostsze ... jeśli chcesz dać dwie linie, możesz uzyskać fajny, date.replace(day=day)aby wszyscy wiedzieli, co się dzieje.
Adam Starrh
9

W Pythonie 3.7 znajduje się nieudokumentowana calendar.monthlen(year, month)funkcja :

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

Jest to odpowiednik udokumentowanego calendar.monthrange(year, month)[1]połączenia .

jfs
źródło
1
Nie działa to w Pythonie 3.8.1: Błąd atrybutu: moduł „kalendarz” nie ma atrybutu „miesiąc”
jeppoo1
1
@ jeppoo1: tak, jest oznaczony jako prywatny w bpo-28292 - oczekiwany dla funkcji nieudokumentowanej.
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)
Анатолий Панин
źródło
5

Używaj pand!

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
źródło
1
możesz również wdrożyć za pomocą pd.series.dt.is_month_end linku
Pablo
1
Obiekt Pandas datetime ma do tego specjalną metodę:now=datetime.datetime.now(); pd_now = pd.to_datetime(now); print(pd_now.days_in_month)
Roei Bahumi
3

Dla mnie to najprostszy sposób:

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

Najprostszym sposobem (bez konieczności importowania kalendarza) jest pobranie pierwszego dnia następnego miesiąca, a następnie odjęcie od niego jednego dnia.

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

Wynik:

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

PS: Ten kod działa szybciej niż import calendarpodejście; patrz poniżej:

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)

WYNIK:

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

Ten kod zakłada, że ​​chcesz mieć datę ostatniego dnia miesiąca (tj. Nie tylko część DD, ale całą datę RRRRMMDD)

Vishal
źródło
dlaczego ty nie chcesz import calendar?
Adam Venturella,
1
Ponieważ jest szybszy. Zmodyfikowałem moją odpowiedź powyżej, aby ją uwzględnić.
Vishal
@Vishal masz poprawną koncepcję, ale następujący wiersz nie brzmiał: `` dt.datetime (thisDate.year, (thisDate + relativedelta (months = 1))). Month, 1) - dt.timedelta (days = 1) '' `` Zwłaszcza jeśli miesiąc przypada na koniec roku. spróbuj `` `last_date_of_month = \ first_date_of_month + relativedelta (months = 1) - relativedelta (days = 1) '' zamiast tego
XValidated
3

Oto kolejna odpowiedź. Nie są wymagane żadne dodatkowe pakiety.

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

Uzyskaj pierwszy dzień następnego miesiąca i odejmij od niego dzień.

Kevswanberg
źródło
2

Możesz sam obliczyć datę końcową. prosta logika polega na odjęciu dnia od daty_początkowej następnego miesiąca. :)

Napisz więc niestandardową metodę,

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

Powołanie,

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

Zwróci datę zakończenia tego miesiąca. Podaj dowolną datę do tej funkcji. zwraca datę zakończenia tego miesiąca.

Vipul Vishnu av
źródło
1

To jest dla mnie najprostsze rozwiązanie, używając tylko standardowej biblioteki datetime:

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

Nie rozwiązuje to głównego pytania, ale jedną fajną sztuczką, aby uzyskać ostatni dzień tygodnia w miesiącu, jest użycie calendar.monthcalendar, która zwraca macierz dat, zorganizowanych od poniedziałku jako pierwszej kolumny do niedzieli jako ostatniej.

# 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

Chodzi [0:-2]o to, aby ogolić kolumny weekendowe i wyrzucić je. Daty przypadające poza miesiącem są wskazywane przez 0, więc maksimum skutecznie je ignoruje.

Użycie numpy.ravelnie jest absolutnie konieczne, ale nienawidzę polegać na samej konwencji , numpy.ndarray.maxktóra spłaszczy tablicę, jeśli nie powie się, którą oś obliczyć.

Ely
źródło
0
import calendar
from time import gmtime, strftime
calendar.monthrange(int(strftime("%Y", gmtime())), int(strftime("%m", gmtime())))[1]

Wynik:

31



Spowoduje to wydrukowanie ostatniego dnia niezależnie od bieżącego miesiąca. W tym przykładzie był to 15 maja 2016 r. Twoje wyniki mogą być inne, ale wynik będzie tyle dni, ile wynosi bieżący miesiąc. Świetnie, jeśli chcesz sprawdzić ostatni dzień miesiąca, uruchamiając codzienną pracę crona.

Więc:

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

Wynik:

False

Chyba że jest to ostatni dzień miesiąca.

Audstanley
źródło
0

Wolę w ten sposób

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

Jeśli chcesz stworzyć własną małą funkcję, jest to dobry punkt wyjścia:

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

W tym celu musisz znać zasady dotyczące lat przestępnych:

  • co czwarty rok
  • z wyjątkiem każdych 100 lat
  • ale znowu co 400 lat
matematyka
źródło
1
Jasne, ale biblioteki systemowe mają już te dane, a jeśli reguły zostaną zmienione dekretem, aktualizacja bibliotek jest problemem kogoś innego.
Jasen
Cóż, możliwe, ale bardzo mało prawdopodobne, a nawet jeśli - ugryzłoby cię dopiero za około 2000 lat ... en.wikipedia.org/wiki/Gregorian_calendar#Accuracy
matematyka
Te zasady nie działają od 500 lat w przeszłość Nie mam pewności, że będą one stać tysiące lat w przyszłość,
Jasen
0

Jeśli podasz zakres dat, możesz użyć tego:

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

W poniższym kodzie „get_last_day_of_month (dt)” poda ci to, wraz z datą w formacie ciągu, np. „RRRR-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 )
Pulkit Bansal
źródło
0

Najprostszym sposobem jest użycie datetimepewnej matematyki dat, np. Odjęcie dnia od pierwszego dnia następnego miesiąca:

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

Alternatywnie możesz użyć, calendar.monthrange()aby uzyskać liczbę dni w miesiącu (biorąc pod uwagę lata przestępne) i odpowiednio zaktualizować datę:

import calendar, datetime

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

Szybki test porównawczy pokazuje, że pierwsza wersja jest zauważalnie szybsza:

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

Oto długa (łatwa do zrozumienia) wersja, która dba o lata przestępne.

na zdrowie, 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
źródło
możesz usunąć, else: passa także możesz się pozbyć if year % 400 == 0: leap_year_flag = 1z drobnymi modyfikacjami
canbax
-1

Oto oparte na rozwiązaniu 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)

next_monthLambda znajduje reprezentację krotny pierwszego dnia następnego miesiąca i przechodzi na następny rok. month_endLambda przekształca datę ( dte) do krotki, stosuje się next_monthi tworzy nową datę. Zatem „koniec miesiąca” to tylko pierwszy dzień następnego miesiąca minus timedelta(days=1).

Johannes Blaschke
źródło