Como obter o último dia do mês?

633

Existe uma maneira de usar a biblioteca padrão do Python para determinar facilmente (ou seja, uma chamada de função) o último dia de um determinado mês?

Se a biblioteca padrão não suportar isso, o pacote dateutil suporta isso?

Cristian
fonte

Respostas:

1083

Eu não percebi isso antes quando estava examinando a documentação do calendarmódulo , mas um método chamado monthrangefornece essas informações:

monthrange (ano, mês)
    Retorna o dia da semana do primeiro dia do mês e o número de dias no mês, para o ano e mês especificados.

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

tão:

calendar.monthrange(year, month)[1]

parece ser o caminho mais simples a seguir.

Só para esclarecer, também monthrangesuporta anos bissextos:

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

Minha resposta anterior ainda funciona, mas é claramente abaixo do ideal.

Blair Conrad
fonte
Esta não é uma resposta correta para a pergunta original! E se o último dia do mês for um sábado / sol.
Mahdi
8
@mahdi: está correto, o segundo número é o "número de dias no mês" == "o último dia", independentemente do tipo de dia que seja.
RickyA
É retornado dia errado para o ano 1800 fevereiro. Eu não entendi isso
sword1st
8
@ sword1st, vejo 28como a resposta, o que é correto, usando as regras para o calendário gregoriano
Blair Conrad
Não posso ser o único que pensa monthrangeé um nome confuso. Você pensaria que retornaria (first_day, last_day), não(week_day_of_first_day, number_of_days)
Flimm
146

Se você não deseja importar o calendarmódulo, uma função simples de duas etapas também pode ser:

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)

Saídas:

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

EDIT: Veja a resposta de @Blair Conrad para uma solução mais limpa


>>> import datetime
>>> datetime.date(2000, 2, 1) - datetime.timedelta(days=1)
datetime.date(2000, 1, 31)
John Millikin
fonte
43
Na verdade, eu chamaria isso de limpador, exceto pelo fato de que ele falha em dezembro quando today.month + 1 == 13e você recebe um ValueError.
fletom 18/14 /
4
Você pode resolver isso usando (today.month % 12) + 1since since 12 % 120
bluesmoon
Alguma opção estranha de horário de verão no dia 31 de um mês (acho que isso não existe hoje, mas o futuro pode ser diferente) pode renderizar o dia 31 desse mês com apenas 23 horas, de modo que subtrair um dia permite que você termina às 23:00:00 do dia 30. É um caso esquisito, claro, mas mostra que a abordagem não é sólida.
Alfe
50

Isso é realmente muito fácil com dateutil.relativedelta(pacote python-datetutil for pip). day=31sempre retornará sempre o último dia do mês.

Exemplo:

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
fonte
7
Eu pessoalmente gosto de relativedelta (meses = + 1, segundos = -1) parece mais óbvio o que está acontecendo #
BenH 27/03
Você está errado. datetime(2014, 2, 1) + relativedelta(days=31)datetime(2014, 3, 4, 0, 0)...
Emmanuel
9
você usou dias = em vez de dia =
Vince Spicer
@ BenH Isso é baseado na esperança (não tenho certeza se está especificado) de que primeiro monthsseja adicionado e depois secondssubtraído. Uma vez que eles são aprovados, **kwargseu não confiaria nisso e faria uma série de operações separadas: o now + relative(months=+1) + relative(day=1) + relative(days=-1)que é inequívoco.
Alfe
last_day = (<datetime_object> + relativedelta(day=31)).day
user12345
44

EDIT: veja minha outra resposta . Ele tem uma implementação melhor do que esta, que deixo aqui para o caso de alguém estar interessado em ver como alguém pode "rolar sua própria" calculadora.

@ John Millikin dá uma boa resposta, com a complicação adicional de calcular o primeiro dia do próximo mês.

O seguinte não é particularmente elegante, mas para descobrir o último dia do mês em que uma determinada data reside, você pode tentar:

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
fonte
Parece bobagem, mas como faço para obter o primeiro dia do mês de maneira semelhante?
Kishan
10
Nem sempre é 1?
Blair Conrad
Sim, mas eu estava confuso eu estava procurando algo como isto: start_date = data (.. Datetime.now () ano, datetime.now () mês, 1)
Kishan
4
Ah today = datetime.date.today(); start_date = today.replace(day=1). Você gostaria de evitar ligar para datetime.now duas vezes, caso o tenha telefonado antes da meia-noite de 31 de dezembro e depois da meia-noite. Você receberá 01-01-2016 em vez de 01/12/2016 ou 01-01-2017.
Blair Conrad
Isso é muito simples de entender e retorna uma instância de data e hora, que pode ser útil em muitos casos. Outra vantagem é que funciona se a entrada datefor uma instância de datetime.date, datetime.datetimee também pandas.Timestamp.
Guilherme Salomé
26

Se dateutil.relativedeltavocê usar a última data do mês, será assim:

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

A idéia é obter o primeiro dia do mês e usar relativedelta1 mês à frente e 1 dia de volta para obter o último dia do mês que você deseja.

Satish Reddy
fonte
13

Outra solução seria fazer algo assim:

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 use a função assim:

>>> 
>>> 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
fonte
5
Muito complexo, quebra a terceira regra do zen do python.
Markuz
11
from datetime import timedelta
(any_day.replace(day=1) + timedelta(days=32)).replace(day=1) - timedelta(days=1)
Collin Anderson
fonte
É disso que os erros são feitos;) Tente com 31 de janeiro
LeartS
@ HeartS: funciona para mim. O que acontece quando você tenta?
Collin Anderson
1
Funciona. any_day é 31 de janeiro, substituímos dia por 1, portanto, 1 de janeiro, adicionamos 32 dias, obtemos 2 de fevereiro, substituímos por dia = 1 novamente e obtemos 1 de fevereiro. Subtraia um dia e obtemos 31 de janeiro. qual é o problema Que dia você recebe?
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
fonte
9

se você estiver disposto a usar uma biblioteca externa, consulte http://crsmithdev.com/arrow/

Você pode obter o último dia do mês com:

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

Isso retorna um objeto de data que você pode fazer sua manipulação.

Blake
fonte
9

Para obter a última data do mês, fazemos algo assim:

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

Agora, para explicar o que estamos fazendo aqui, vamos dividi-lo em duas partes:

primeiro é obter o número de dias do mês atual para o qual usamos o intervalo do mês que Blair Conrad já mencionou sua solução:

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

segundo é obter a última data em si, o que fazemos com a ajuda de substituir, por exemplo

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

e quando os combinamos conforme mencionado na parte superior, obtemos uma solução dinâmica.

Siddharth K
fonte
Embora esse código possa ajudar a resolver o problema, ele não explica por que e / ou como responde à pergunta. Fornecer esse contexto adicional melhoraria significativamente seu valor educacional a longo prazo. Por favor edite sua resposta para adicionar explicação, incluindo o que limitações e premissas se aplicam.
Toby Speight
Isso parece o mais direto. Se você quiser dar duas linhas, poderá obter uma boa, date.replace(day=day)para que todos saibam o que está acontecendo.
precisa saber é o seguinte
9

No Python 3.7, há a calendar.monthlen(year, month)função não documentada :

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

É equivalente à chamada documentadacalendar.monthrange(year, month)[1] .

jfs
fonte
1
Isso parece não funcionar no Python 3.8.1: AttributeError: o módulo 'calendar' não tem nenhum atributo 'monthlen'
jeppoo1 21/02
1
@ jeppoo1: sim, está marcado como privado no bpo-28292 - esperado para uma função não documentada.
jfs 21/02
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)
Анатолий Панин
fonte
5

Use pandas!

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
fonte
1
você também pode implementados utilizando pd.series.dt.is_month_end ligação
Pablo
1
Pandas datetime objeto tem um método específico para que:now=datetime.datetime.now(); pd_now = pd.to_datetime(now); print(pd_now.days_in_month)
Roei Bahumi
3

Para mim, é a maneira mais simples:

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

A maneira mais fácil (sem precisar importar o calendário) é obter o primeiro dia do próximo mês e subtrair um dia dele.

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

Resultado:

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

PS: esse código roda mais rápido em comparação com a import calendarabordagem; ver abaixo:

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)

RESULTADO:

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

Este código pressupõe que você deseja a data do último dia do mês (ou seja, não apenas a parte DD, mas a data AAAAMMDD inteira)

Vishal
fonte
por que você não iria querer import calendar?
Adam Venturella
1
Porque é mais rápido. Modifiquei minha resposta acima para incluir isso.
Vishal 28/11
@Vishal, você acertou o conceito, mas a seguinte linha não era: `` `dt.datetime (thisDate.year, (thisDate + relativedelta (months = 1)). Month, 1) - dt.timedelta (days = 1)` `` especialmente se o mês for no final do ano. tente `` `last_date_of_month = \ first_date_of_month + relativedelta (meses = 1) - relativedelta (days = 1)` `` `
XValidated
3

Aqui está outra resposta. Não são necessários pacotes extras.

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

Obtenha o primeiro dia do próximo mês e subtraia um dia dele.

Kevswanberg
fonte
2

Você pode calcular a data final você mesmo. a lógica simples é subtrair um dia a partir da data de início do próximo mês. :)

Então escreva um método personalizado,

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

Chamando,

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

Ele retornará a data final deste mês. Passe qualquer data para esta função. retorna a data final desse mês.

Vipul Vishnu av
fonte
1

Esta é a solução mais simples para mim, usando apenas a biblioteca de data e hora padrão:

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

Isso não aborda a questão principal, mas um bom truque para obter o último dia da semana em um mês é usar calendar.monthcalendar, que retorna uma matriz de datas, organizada com segunda-feira como primeira coluna e domingo como último.

# 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

A [0:-2]coisa toda é raspar as colunas do fim de semana e jogá-las fora. As datas que ficam fora do mês são indicadas por 0, então o máximo as ignora efetivamente.

O uso de numpy.ravelnão é estritamente necessário, mas eu odeio confiar na mera convenção que numpy.ndarray.maxachatará a matriz se não for informado sobre qual eixo calcular sobre.

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

Resultado:

31



Isso imprimirá o último dia do mês atual. Neste exemplo, era 15 de maio de 2016. Portanto, sua produção pode ser diferente; no entanto, a produção será o número de dias que o mês atual for. Ótimo se você deseja verificar o último dia do mês executando um trabalho cron diário.

Assim:

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

Resultado:

False

A menos que seja o último dia do mês.

Audstanley
fonte
0

Eu prefiro assim

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

Se você deseja criar sua própria função pequena, este é um bom ponto de partida:

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

Para isso, você precisa conhecer as regras para os anos bissextos:

  • a cada quarto ano
  • com exceção de cada 100 anos
  • mas novamente a cada 400 anos
Mathause
fonte
1
Claro, mas as bibliotecas do sistema já possuem esses dados e, caso as regras sejam alteradas por decreto, a atualização das bibliotecas é problema de outra pessoa.
Jasen
Bem, possível, mas altamente improvável, e mesmo se - só te
morderia
Essas regras não funcionam há 500 anos no passado. Não tenho confiança de que permanecerão por milhares de anos no futuro.
Jasen
0

Se você passar em um período, poderá usar o seguinte:

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

No código abaixo, 'get_last_day_of_month (dt)' fornecerá isso, com a data no formato de string como '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 )
Pulkit Bansal
fonte
0

A maneira mais simples é usar datetimee alguma matemática de datas, por exemplo, subtrair um dia do primeiro dia do próximo mês:

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

Como alternativa, você pode usar calendar.monthrange()para obter o número de dias em um mês (considerando os anos bissextos) e atualizar a data de acordo:

import calendar, datetime

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

Uma referência rápida mostra que a primeira versão é notavelmente mais rápida:

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

Aqui está uma versão longa (fácil de entender), mas cuida dos anos bissextos.

Saúde, 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
fonte
você pode excluir else: passe também pode se livrar de if year % 400 == 0: leap_year_flag = 1pequenas modificações
canbax 22/04
-1

Aqui está uma solução baseada em 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)

O next_monthlambda encontra a representação da tupla do primeiro dia do próximo mês e passa para o próximo ano. O month_endlambda transforma uma data ( dte) em uma tupla, aplica next_monthe cria uma nova data. Então o "final do mês" é apenas o primeiro dia do mês seguinte timedelta(days=1).

Johannes Blaschke
fonte