¿Cómo obtener el último día del mes?


634

¿Hay alguna manera de usar la biblioteca estándar de Python para determinar fácilmente (es decir, una llamada de función) el último día de un mes determinado?

Si la biblioteca estándar no admite eso, ¿el paquete dateutil lo admite?

Respuestas:


1083

No noté esto antes cuando estaba mirando la documentación del calendarmódulo , pero un método llamado monthrangeproporciona esta información:

rango de meses (año, mes)
    Devuelve el día de la semana del primer día del mes y el número de días del mes, para el año y mes especificados.

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

entonces:

calendar.monthrange(year, month)[1]

Parece el camino más sencillo.

Para ser claros, también es monthrangecompatible con los años bisiestos:

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

Mi respuesta anterior aún funciona, pero es claramente subóptima.


¡Esta no es una respuesta correcta a la pregunta original! ¿Qué pasa si el último día del mes es un sábado / domingo?
Mahdi

8
@mahdi: es correcto, el segundo número es el "nr de días en el mes" == "el último día", independientemente del tipo de día que sea.
RickyA

Vuelve el día equivocado para el año 1800 de febrero. No lo entiendo
espada1 de

8
@ sword1st, veo 28como la respuesta, que es correcta, usando las reglas para el calendario gregoriano
Blair Conrad

No puedo ser el único que piensa que monthrangees un nombre confuso. Pensarías que volvería (first_day, last_day), no(week_day_of_first_day, number_of_days)
Flimm

146

Si no desea importar el calendarmódulo, una simple función de dos pasos también puede 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)

Salidas:

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

EDITAR: Vea la respuesta de @Blair Conrad para una solución más limpia


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

43
En realidad, llamaría a este limpiador, excepto por el hecho de que falla en diciembre cuando today.month + 1 == 13y obtienes un ValueError.
fletom

44
Puede resolver eso usando (today.month % 12) + 1dado que 12 % 12da 0
bluesmoon

Un cambio de horario de verano extraño el día 31 de un mes (creo que eso no existe hoy, pero el futuro podría ser diferente) podría generar el día 31 de ese mes con una duración de solo 23 horas, por lo que restar un día te permite termina a las 23:00:00 el día 30. Es un caso extraño, claro, pero muestra que el enfoque no es sólido.
Alfe

50

Esto es realmente bastante fácil con dateutil.relativedelta(paquete python-datetutil para pip). day=31siempre volverá el último día del mes.

Ejemplo:

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)

77
Personalmente, me gusta el relativodelta (meses = + 1, segundos = -1) parece más obvio lo que está sucediendo
BenH

Te equivocas. datetime(2014, 2, 1) + relativedelta(days=31)da datetime(2014, 3, 4, 0, 0)...
Emmanuel

99
usaste days = en lugar de day =
Vince Spicer

@BenH Eso se basa en la esperanza (no estoy seguro de que se especifique) de que primero monthsse agrega, luego se secondsresta. Dado que se aprobaron **kwargs, no confiaría en eso y realizaría una serie de operaciones separadas: lo now + relative(months=+1) + relative(day=1) + relative(days=-1)cual es inequívoco.
Alfe

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

44

EDITAR: vea mi otra respuesta . Tiene una mejor implementación que esta, que dejo aquí solo en caso de que alguien esté interesado en ver cómo uno puede "hacer su propia" calculadora.

@John Millikin da una buena respuesta, con la complicación adicional de calcular el primer día del próximo mes.

Lo siguiente no es particularmente elegante, pero para averiguar el último día del mes en el que vive una fecha determinada, puedes intentar:

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)

Suena tonto, pero ¿cómo obtengo el primer día del mes de manera similar?
Kishan

10
¿No es siempre 1?
Blair Conrad

Sí, pero estaba confundido, estaba mirando algo como esto: start_date = date (datetime.now (). Year, datetime.now (). Month, 1)
Kishan

44
Ah today = datetime.date.today(); start_date = today.replace(day=1). Debería evitar llamar a datetime.now dos veces, en caso de que lo haya llamado antes de la medianoche del 31 de diciembre y luego justo después de la medianoche. Obtendría 2016-01-01 en lugar de 2016-12-01 o 2017-01-01.
Blair Conrad

Esto es muy sencillo de entender y devuelve una instancia de fecha y hora, que puede ser útil en muchos casos. Otra ventaja es que funciona si la entrada datees una instancia de datetime.date, datetime.datetimey también pandas.Timestamp.
Guilherme Salomé el

27

Al dateutil.relativedeltausarlo obtendría la última fecha del mes de esta manera:

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

La idea es obtener el primer día del mes y usar relativedeltapara ir 1 mes adelante y 1 día atrás para obtener el último día del mes que desea.


13

Otra solución sería hacer algo como esto:

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

Y usa la función así:

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

55
Demasiado complejo, rompe la tercera regla del zen de Python.
markuz 01 de

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

Esto es de lo que están hechos los errores;) Pruebe con el 31 de enero
LeartS

@LeartS: funciona para mí. ¿Qué pasa cuando lo intentas?
Collin Anderson el

1
Funciona. any_day es el 31 de enero, reemplazamos el día con 1, así que el 1 de enero, sumamos 32 días, obtenemos el 2 de febrero, reemplazamos con el día = 1 nuevamente y obtenemos el 1 de febrero. Resta un día y obtenemos el 31 de enero. No veo cuál es el problema Que dia te toca
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

Si está dispuesto a usar una biblioteca externa, visite http://crsmithdev.com/arrow/

U puede obtener el último día del mes con:

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

Esto devuelve un objeto de fecha que luego puede hacer su manipulación.


9

Para obtener la última fecha del mes, hacemos algo como esto:

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

Ahora, para explicar lo que estamos haciendo aquí, lo dividiremos en dos partes:

primero es obtener el número de días del mes actual para el que usamos el rango de meses que Blair Conrad ya mencionó su solución:

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

segundo es obtener la última fecha en sí, lo que hacemos con la ayuda de reemplazar, por ejemplo

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

y cuando los combinamos como se menciona arriba, obtenemos una solución dinámica.


Aunque este código puede ayudar a resolver el problema, no explica por qué y / o cómo responde la pregunta. Proporcionar este contexto adicional mejoraría significativamente su valor educativo a largo plazo. Por favor, editar su respuesta para agregar explicación, incluyendo lo que se aplican limitaciones y supuestos.
Toby Speight

Esto parece ser lo más sencillo ... si desea darle dos líneas, puede obtener un bonito date.replace(day=day)para que todos sepan lo que está sucediendo.
Adam Starrh

9

En Python 3.7 existe la calendar.monthlen(year, month)función no documentada :

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

Es equivalente a la calendar.monthrange(year, month)[1]llamada documentada .


1
Esto no parece funcionar en Python 3.8.1: AttributeError: el módulo 'calendar' no tiene el atributo 'monthlen'
jeppoo1

1
@ jeppoo1: sí, está marcado como privado en bpo-28292 , esperado para una función no documentada.
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 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

1
también se puede implementar usando el pd.series.dt.is_month_end enlace
Pablo

1
El objeto de fecha y hora de Pandas tiene un método específico para eso:now=datetime.datetime.now(); pd_now = pd.to_datetime(now); print(pd_now.days_in_month)
Roei Bahumi el

3

Para mí es la forma más simple:

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

La forma más fácil (sin tener que importar el calendario) es obtener el primer día del mes siguiente y luego restarle un día.

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

Salida:

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

PD: este código se ejecuta más rápido en comparación con el import calendarenfoque; vea abajo:

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)

SALIDA:

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

Este código supone que desea la fecha del último día del mes (es decir, no solo la parte DD, sino toda la fecha AAAAMMDD)


¿Por qué no quieres import calendar?
Adam Venturella

1
Porque es más rápido He modificado mi respuesta anterior para incluir esto.
Vishal

@Vishal tiene el concepto correcto pero la siguiente línea no era: `` `dt.datetime (thisDate.year, (thisDate + relativedelta (months = 1)). Month, 1) - dt.timedelta (days = 1)` `` especialmente si el mes es al final del año. intente `` `last_date_of_month = \ first_date_of_month + relativedelta (months = 1) - relativedelta (days = 1)` `` en cambio
XValida el

3

Aquí hay otra respuesta. No se requieren paquetes adicionales.

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

Obtenga el primer día del próximo mes y reste un día.


2

Puede calcular la fecha de finalización usted mismo. La lógica simple es restar un día de la fecha_inicio del próximo mes. :)

Entonces escribe un 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

Vocación,

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

Volverá la fecha de finalización de este mes. Pase cualquier fecha a esta función. le devuelve la fecha de finalización de ese mes.



1

Esta es la solución más simple para mí usando solo la biblioteca estándar de fecha y hora:

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

Esto no aborda la pregunta principal, pero es un buen truco para obtener el último día de la semana en un mes calendar.monthcalendar, que devuelve una matriz de fechas, organizadas con el lunes como la primera columna y el domingo como la última.

# 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

Todo [0:-2]es afeitar las columnas del fin de semana y tirarlas. Las fechas que quedan fuera del mes se indican con 0, por lo que el máximo las ignora efectivamente.

El uso de numpy.ravelno es estrictamente necesario, pero odio confiar en la mera convención que numpy.ndarray.maxaplanará la matriz si no se le dice qué eje calcular.


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

Salida:

31



Esto imprimirá el último día de cualquier mes actual. En este ejemplo, fue el 15 de mayo de 2016. Por lo tanto, su producción puede ser diferente, sin embargo, la producción será tantos días como el mes actual. Genial si desea verificar el último día del mes ejecutando un trabajo cron diario.

Entonces:

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

Salida:

False

A menos que sea el último día del mes.


0

Prefiero asi

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

Si desea hacer su propia función pequeña, este es un buen punto 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 esto debes conocer las reglas para los años bisiestos:

  • cada cuarto año
  • con la excepción de cada 100 años
  • pero de nuevo cada 400 años

1
Claro, pero las bibliotecas del sistema ya tienen estos datos, y si las reglas se cambian por decreto, la actualización de las bibliotecas es un problema de otra persona.
Jasen

Bueno, posible, pero altamente improbable, e incluso si solo te mordiera en unos 2000 años ... en.wikipedia.org/wiki/Gregorian_calendar#Accuracy
mathause

Estas reglas no funcionan durante 500 años en el pasado No tengo confianza en que se mantendrán durante miles de años en el futuro,
Jasen

0

Si pasa en un rango de fechas, puede usar esto:

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

En el siguiente código, 'get_last_day_of_month (dt)' le dará esto, con la fecha en formato de cadena como 'AAAA-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 )

0

La forma más simple es usar datetimey algunas matemáticas de fechas, por ejemplo, restar un día del primer día del mes siguiente:

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

Alternativamente, puede usar calendar.monthrange()para obtener la cantidad de días en un mes (teniendo en cuenta los años bisiestos) y actualizar la fecha en consecuencia:

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 punto de referencia rápido muestra que la primera versión es notablemente más 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)

0

Aquí hay una versión larga (fácil de entender) pero se ocupa de los años bisiestos.

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

puede eliminar else: passy también puede deshacerse if year % 400 == 0: leap_year_flag = 1con modificaciones menores
canbax

-1

Aquí hay una solución basada en 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)

La next_monthlambda encuentra la representación de la tupla del primer día del mes siguiente y pasa al año siguiente. La month_endlambda transforma una fecha ( dte) en una tupla, aplica next_monthy crea una nueva fecha. Entonces el "fin de mes" es solo el primer día del mes siguiente menos timedelta(days=1).

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.