Come ottenere l'ultimo giorno del mese?


634

Esiste un modo per utilizzare la libreria standard di Python per determinare facilmente (ovvero una chiamata di funzione) l'ultimo giorno di un determinato mese?

Se la libreria standard non lo supporta, il pacchetto dateutil lo supporta?

Risposte:


1083

Non l'avevo notato prima quando stavo guardando la documentazione per il calendarmodulo , ma un metodo chiamato monthrangefornisce queste informazioni:

monthrange (anno, mese)
    Restituisce il giorno della settimana del primo giorno del mese e il numero di giorni del mese, per l'anno e il mese specificati.

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

così:

calendar.monthrange(year, month)[1]

sembra il modo più semplice di andare.

Per essere chiari, monthrangesupporta anche gli anni bisestili:

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

La mia risposta precedente funziona ancora, ma è chiaramente non ottimale.


Questa non è una risposta corretta alla domanda originale! E se l'ultimo giorno del mese è un sabato / domenica?
Mahdi,

8
@mahdi: è corretto, il secondo numero è il "numero di giorni del mese" == "l'ultimo giorno", indipendentemente dal tipo di giorno che è.
Ricky

Ritorna il giorno sbagliato per l'anno 1800 febbraio. Non capisco
sword1st

8
@ sword1st, vedo 28come la risposta, che è corretta, usando le regole per il calendario gregoriano
Blair Conrad,

Non posso essere l'unico a pensare monthrangesia un nome confuso. Penseresti che sarebbe tornato (first_day, last_day), non(week_day_of_first_day, number_of_days)
Flimm il

146

Se non si desidera importare il calendarmodulo, una semplice funzione in due passaggi può essere anche:

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)

Uscite:

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

81

EDIT: vedi la risposta di @Blair Conrad per una soluzione più pulita


>>> import datetime
>>> datetime.date(2000, 2, 1) - datetime.timedelta(days=1)
datetime.date(2000, 1, 31)

43
In realtà chiamerei questo pulitore, tranne per il fatto che fallisce a dicembre quando today.month + 1 == 13e ottieni un ValueError.
fletom,

4
Puoi risolverlo usando da (today.month % 12) + 1dato 12 % 12da 0
bluesmoon il

Qualche strano interruttore dell'ora legale su un 31 di un mese (penso che non esista oggi, ma il futuro potrebbe essere diverso) potrebbe rendere il 31 di quel mese con una durata di sole 23 ore, quindi la sottrazione di un giorno ti consente termina alle 23:00:00 del 30. Questo è un caso strano, certo, ma dimostra che l'approccio non è corretto.
Alfe,

50

Questo in realtà è abbastanza semplice con dateutil.relativedelta(pacchetto python-datetutil per pip). day=31restituirà sempre l'ultimo giorno del mese.

Esempio:

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)

7
Personalmente mi piace relativedelta (mesi = + 1, secondi = -1) sembra più ovvio quello che sta succedendo
BenH

Hai torto. datetime(2014, 2, 1) + relativedelta(days=31)datetime(2014, 3, 4, 0, 0)...
Emmanuel il

9
hai usato giorni = anziché giorno =
Vince Spicer il

@BenH Questo si basa sulla speranza (non sono sicuro che sia specificato) che prima monthsvenga aggiunto, poi secondssottratto. Dal momento che sono passati perché **kwargsnon farei affidamento su di esso e fare una serie di operazioni separate: il now + relative(months=+1) + relative(day=1) + relative(days=-1)che è inequivocabile.
Alfe,

last_day = (<datetime_object> + relativedelta(day=31)).day
user12345,

44

EDIT: vedi la mia altra risposta . Ha un'implementazione migliore di questa, che lascio qui nel caso qualcuno fosse interessato a vedere come si potrebbe "rotolare il proprio" calcolatore.

@ John Millikin dà una buona risposta, con l'ulteriore complicazione del calcolo del primo giorno del mese successivo.

Quanto segue non è particolarmente elegante, ma per capire l'ultimo giorno del mese in cui vive una determinata data, potresti provare:

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)

Sembra sciocco ma come posso ottenere il primo giorno del mese in modo simile in questo modo.
Kishan,

10
Non è sempre 1?
Blair Conrad,

Sì, ma ero confuso, stavo assomigliando a qualcosa del genere: start_date = date (datetime.now (). Anno, datetime.now (). Mese, 1)
Kishan,

4
Ah. today = datetime.date.today(); start_date = today.replace(day=1). Vorresti evitare di chiamare datetime.now due volte, nel caso in cui l'hai chiamato poco prima di mezzanotte del 31 dicembre e poi subito dopo mezzanotte. Otterrai 01-01-2016 anziché 01-12-2016 o 01-01-2017.
Blair Conrad,

Questo è molto semplice da capire e restituisce un'istanza datetime, che può essere utile in molti casi. Un altro vantaggio è che funziona se l'input dateè un'istanza di datetime.date, datetime.datetimee anche pandas.Timestamp.
Guilherme Salomé,

27

Usandolo dateutil.relativedeltaotterrai la data dell'ultimo mese in questo modo:

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

L'idea è di ottenere il primo giorno del mese e utilizzare relativedeltaper andare avanti di 1 mese e 1 giorno indietro in modo da ottenere l'ultimo giorno del mese desiderato.


13

Un'altra soluzione sarebbe quella di fare qualcosa del genere:

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

E usa la funzione in questo modo:

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

5
Troppo complesso, infrange la terza regola dello zen di Python.
markuz,

11
from datetime import timedelta
(any_day.replace(day=1) + timedelta(days=32)).replace(day=1) - timedelta(days=1)

Questo è ciò di cui sono fatti i bug;) Prova con 31 gennaio
LeartS

@LeartS: funziona per me. Che cosa succede quando ci provi?
Collin Anderson,

1
Funziona. any_day è il 31 gennaio, sostituiamo il giorno con 1, quindi 1 gennaio, aggiungiamo 32 giorni, otteniamo il 2 febbraio, sostituiamo di nuovo con il giorno = 1 e otteniamo il 1 ° febbraio. Sottraiamo un giorno e otteniamo il 31 gennaio. Non vedo qual è il problema. Che giorno hai?
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

9

se si desidera utilizzare una libreria esterna, consultare http://crsmithdev.com/arrow/

Puoi quindi ottenere l'ultimo giorno del mese con:

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

Questo restituisce un oggetto data che puoi quindi fare la tua manipolazione.


9

Per ottenere l'ultima data del mese facciamo qualcosa del genere:

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

Ora per spiegare cosa stiamo facendo qui lo suddivideremo in due parti:

in primo luogo sta ottenendo il numero di giorni del mese corrente per cui utilizziamo la gamma mensile che Blair Conrad ha già menzionato la sua soluzione:

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

il secondo è ottenere l'ultima data stessa che facciamo con l'aiuto di sostituire ad es

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

e quando li combiniamo come indicato in alto otteniamo una soluzione dinamica.


Sebbene questo codice possa aiutare a risolvere il problema, non spiega perché e / o come risponde alla domanda. Fornire questo contesto aggiuntivo migliorerebbe significativamente il suo valore educativo a lungo termine. Si prega di modificare la risposta di aggiungere spiegazioni, tra cui quello che si applicano le limitazioni e le assunzioni.
Toby Speight,

Questo sembra il più semplice .. se stai cercando di dargli due righe puoi ottenere una bella date.replace(day=day)così tutti sanno cosa sta succedendo.
Adam Starrh,

9

In Python 3.7 è presente la calendar.monthlen(year, month)funzione non documentata :

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

È equivalente alla chiamata documentatacalendar.monthrange(year, month)[1] .


1
Questo non sembra funzionare su Python 3.8.1: AttributeError: il modulo 'calendar' non ha attributi 'monthlen'
jeppoo1

1
@ jeppoo1: sì, è contrassegnato come privato in bpo-28292 - previsto per una funzione non documentata.
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)

5

Usa i 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

1
puoi anche implementarlo usando il pd.series.dt.is_month_end link
Pablo

1
L'oggetto datas Pandas ha un metodo specifico per questo:now=datetime.datetime.now(); pd_now = pd.to_datetime(now); print(pd_now.days_in_month)
Roei Bahumi

3

Per me è il modo più semplice:

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)

3

Il modo più semplice (senza dover importare il calendario) è ottenere il primo giorno del mese successivo e quindi sottrarre un giorno da esso.

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

Produzione:

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

PS: questo codice viene eseguito più velocemente rispetto import calendarall'approccio; vedi sotto:

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)

PRODUZIONE:

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

Questo codice presuppone che si desideri la data dell'ultimo giorno del mese (ovvero, non solo la parte GG, ma l'intera data AAAAMMGG)


perché non vorresti import calendar?
Adam Venturella,

1
Perché è più veloce. Ho modificato la mia risposta sopra per includerla.
Vishal,

@Vishal hai capito bene il concetto ma la riga seguente non era: `` `dt.datetime (thisDate.year, (thisDate + relativedelta (months = 1)). Month, 1) - dt.timedelta (days = 1)` `` soprattutto se il mese è alla fine dell'anno. prova `` last_date_of_month = \ first_date_of_month + relativedelta (months = 1) - relativedelta (days = 1) `` '' invece
XV validated

3

Ecco un'altra risposta. Non sono richiesti pacchetti extra.

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

Ottieni il primo giorno del mese successivo e sottrai un giorno da esso.


2

Puoi calcolare tu stesso la data di fine. la semplice logica è sottrarre un giorno dalla data di inizio del mese prossimo. :)

Quindi scrivi un metodo personalizzato,

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

Calling,

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

Restituirà la data di fine di questo mese. Passa qualsiasi data a questa funzione. ti restituisce la data di fine di quel mese.



1

Questa è la soluzione più semplice per me utilizzando solo la libreria datetime standard:

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)

0

Questo non affronta la domanda principale, ma un bel trucco per ottenere l'ultimo giorno della settimana in un mese è usare calendar.monthcalendar, che restituisce una matrice di date, organizzate con il lunedì come prima colonna fino alla domenica come ultima.

# 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

Il tutto [0:-2]è radere le colonne del fine settimana e buttarle via. Le date che cadono al di fuori del mese sono indicate da 0, quindi il massimo le ignora effettivamente.

L'uso di numpy.ravelnon è strettamente necessario, ma odio basarmi sulla semplice convenzione che numpy.ndarray.maxappiattirà l'array se non gli viene detto su quale asse calcolare.


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

Produzione:

31



Questo stamperà l'ultimo giorno di qualunque sia il mese corrente. In questo esempio è stato il 15 maggio 2016. Pertanto, l'output potrebbe essere diverso, tuttavia l'output sarà pari al numero di giorni del mese corrente. Ottimo se vuoi controllare l'ultimo giorno del mese eseguendo un cron job giornaliero.

Così:

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

Produzione:

False

A meno che non sia l'ultimo giorno del mese.


0

Preferisco così

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)

0

Se vuoi creare la tua piccola funzione, questo è un buon punto di partenza:

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

Per questo devi conoscere le regole per gli anni bisestili:

  • ogni quattro anni
  • ad eccezione di ogni 100 anni
  • ma di nuovo ogni 400 anni

1
Certo, ma le librerie di sistema hanno già questi dati e se le regole dovessero essere modificate con decreto, l'aggiornamento delle librerie è un problema di qualcun altro.
Jasen,

Bene, possibile, ma altamente improbabile, e anche se - ti
morderebbe

Queste regole non funzionano per 500 anni nel passato Non ho fiducia che rimarranno per migliaia di anni nel futuro,
Jasen

0

Se passi in un intervallo di date, puoi utilizzare questo:

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

0

Nel codice qui sotto 'get_last_day_of_month (dt)' ti darà questo, con la data in formato stringa come 'AAAA-MM-GG'.

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 )

0

Il modo più semplice è usare datetimee un po 'di matematica per la data, ad esempio sottrarre un giorno dal primo giorno del mese successivo:

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

In alternativa, è possibile utilizzare calendar.monthrange()per ottenere il numero di giorni in un mese (tenendo conto degli anni bisestili) e aggiornare la data di conseguenza:

import calendar, datetime

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

Un rapido benchmark mostra che la prima versione è notevolmente più veloce:

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)

0

Ecco una versione lunga (facile da capire) ma si occupa degli anni bisestili.

saluti, 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]

puoi eliminare else: passe puoi anche sbarazzarti if year % 400 == 0: leap_year_flag = 1di piccole modifiche
canbax il

-1

Ecco una soluzione lambda in pitone:

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)

La next_monthlambda trova la rappresentazione tupla del primo giorno del mese successivo e passa al successivo anno. La month_endlambda trasforma una data ( dte) in una tupla, si applica next_monthe crea una nuova data. Quindi la "fine del mese" è solo il primo giorno meno del mese successivo timedelta(days=1).

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.