Comment obtenir le dernier jour du mois?

634

Existe-t-il un moyen d'utiliser la bibliothèque standard de Python pour déterminer facilement (c'est-à-dire un appel de fonction) le dernier jour d'un mois donné?

Si la bibliothèque standard ne prend pas en charge cela, le package dateutil le prend-il en charge?

Cristian
la source

Réponses:

1083

Je ne l'avais pas remarqué plus tôt lorsque je regardais la documentation du calendarmodule , mais une méthode appelée monthrangefournit ces informations:

monthrange (année, mois)
    Renvoie le jour de la semaine du premier jour du mois et le nombre de jours du mois, pour l'année et le mois spécifiés.

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

donc:

calendar.monthrange(year, month)[1]

semble être la façon la plus simple de procéder.

Pour être clair, monthrangeprend également en charge les années bissextiles:

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

Ma réponse précédente fonctionne toujours, mais est clairement sous-optimale.

Blair Conrad
la source
Ce n'est pas une bonne réponse à la question d'origine! Et si le dernier jour du mois est un samedi / dimanche.
Mahdi
8
@mahdi: c'est correct, le deuxième nombre est le "nombre de jours dans le mois" == "le dernier jour", quel que soit le type de jour.
RickyA
C'est le mauvais jour de retour pour l'année 1800 février. Je ne comprends pas
sword1st
8
@ sword1st, je vois 28comme la réponse, qui est correcte, en utilisant les règles du calendrier grégorien
Blair Conrad
Je ne peux pas être le seul à penser que monthrangec'est un nom déroutant. On pourrait penser qu'il reviendrait (first_day, last_day), pas(week_day_of_first_day, number_of_days)
Flimm
146

Si vous ne souhaitez pas importer le calendarmodule, une simple fonction en deux étapes peut également être:

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)

Les sorties:

>>> 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
la source
81

EDIT: Voir la réponse de @Blair Conrad pour une solution plus propre


>>> import datetime
>>> datetime.date(2000, 2, 1) - datetime.timedelta(days=1)
datetime.date(2000, 1, 31)
John Millikin
la source
43
J'appellerais en fait ce nettoyeur, sauf pour le fait qu'il échoue en décembre lorsque today.month + 1 == 13vous obtenez un ValueError.
fletom
4
Vous pouvez résoudre ce problème en utilisant (today.month % 12) + 1puisque 12 % 12donne 0
bluesmoon
Un étrange changement d'heure d'été le 31 de mois (je pense que cela n'existe pas aujourd'hui, mais l'avenir pourrait être différent) pourrait rendre le 31 de ce mois avec une durée de seulement 23 heures, de sorte que la soustraction d'un jour vous permet fin à 23:00:00 le 30. C'est un cas anormal, bien sûr, mais cela montre que l'approche n'est pas saine.
Alfe
50

C'est en fait assez simple avec dateutil.relativedelta(paquet python-datetutil pour pip). day=31retournera toujours toujours le dernier jour du mois.

Exemple:

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
la source
7
Personnellement,
Vous vous trompez. datetime(2014, 2, 1) + relativedelta(days=31)donne datetime(2014, 3, 4, 0, 0)...
Emmanuel
9
vous avez utilisé jours = au lieu de jour =
Vince Spicer
@BenH Cela est basé sur l'espoir (je ne suis pas sûr qu'il soit spécifié) que d'abord le monthssoit ajouté, puis le secondssoustrait. Puisqu'ils sont passés, **kwargsje ne m'appuierais pas sur cela et je ferais une série d'opérations distinctes: now + relative(months=+1) + relative(day=1) + relative(days=-1)ce qui est sans ambiguïté.
Alfe
last_day = (<datetime_object> + relativedelta(day=31)).day
user12345
44

EDIT: voir mon autre réponse . Il a une meilleure implémentation que celle-ci, que je laisse ici juste au cas où quelqu'un serait intéressé à voir comment on pourrait "rouler sa propre calculatrice".

@John Millikin donne une bonne réponse, avec la complication supplémentaire du calcul du premier jour du mois suivant.

Ce qui suit n'est pas particulièrement élégant, mais pour comprendre le dernier jour du mois dans lequel se trouve une date donnée, vous pouvez essayer:

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
la source
Cela peut sembler idiot, mais comment obtenir le premier jour du mois de la même manière.
Kishan
10
N'est-ce pas toujours 1?
Blair Conrad
Oui, mais j'étais confus, je cherchais quelque chose comme ceci: start_date = date (datetime.now (). Year, datetime.now (). Month, 1)
Kishan
4
Ah. today = datetime.date.today(); start_date = today.replace(day=1). Vous voudriez éviter d'appeler datetime.now deux fois, au cas où vous l'auriez appelé juste avant minuit le 31 décembre, puis juste après minuit. Vous obtiendrez 2016-01-01 au lieu de 2016-12-01 ou 2017-01-01.
Blair Conrad
Ceci est très simple à comprendre et renvoie une instance datetime, ce qui peut être utile dans de nombreux cas. Un autre avantage est que cela fonctionne si l'entrée dateest une instance de datetime.date, datetime.datetimeet aussi pandas.Timestamp.
Guilherme Salomé
27

En utilisant dateutil.relativedeltavous obtiendrez la dernière date du mois comme ceci:

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

L'idée est d'obtenir le premier jour du mois et d'utiliser relativedeltapour avancer d'un mois et un jour en arrière afin d'obtenir le dernier jour du mois souhaité.

Satish Reddy
la source
13

Une autre solution serait de faire quelque chose comme ceci:

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

Et utilisez la fonction comme ceci:

>>> 
>>> 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
la source
5
Trop complexe, enfreint la troisième règle du zen du python.
markuz
11
from datetime import timedelta
(any_day.replace(day=1) + timedelta(days=32)).replace(day=1) - timedelta(days=1)
Collin Anderson
la source
C'est de cela que sont faits les bugs;) Essayez avec le 31 janvier
LeartS
@LeartS: cela fonctionne pour moi. Que se passe-t-il lorsque vous essayez?
Collin Anderson
1
Ça marche. any_day est le 31 janvier, nous remplaçons le jour par 1, donc le 1er janvier, ajoutez 32 jours, nous obtenons le 2 février, remplacez par le jour = 1 à nouveau et nous obtenons le 1er février. Soustrayez un jour et nous obtenons le 31 janvier. Je ne vois pas quel est le problème. Quel jour obtenez-vous?
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
la source
9

si vous souhaitez utiliser une bibliothèque externe, consultez http://crsmithdev.com/arrow/

Vous pouvez alors obtenir le dernier jour du mois avec:

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

Cela renvoie un objet date que vous pouvez ensuite faire votre manipulation.

Blake
la source
9

Pour obtenir la dernière date du mois, nous faisons quelque chose comme ceci:

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

Maintenant, pour expliquer ce que nous faisons ici, nous allons le diviser en deux parties:

le premier est le nombre de jours du mois en cours pour lesquels nous utilisons la plage mensuelle dont Blair Conrad a déjà mentionné sa solution:

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

deuxième obtient la dernière date elle-même que nous faisons avec l'aide de remplacer par exemple

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

et lorsque nous les combinons comme mentionné en haut, nous obtenons une solution dynamique.

Siddharth K
la source
Bien que ce code puisse aider à résoudre le problème, il n'explique pas pourquoi et / ou comment il répond à la question. Fournir ce contexte supplémentaire améliorerait considérablement sa valeur éducative à long terme. Veuillez modifier votre réponse pour ajouter des explications, y compris les limitations et hypothèses applicables.
Toby Speight
Cela semble le plus simple .. si vous êtes prêt à lui donner deux lignes, vous pouvez obtenir une belle date.replace(day=day)afin que tout le monde sache ce qui se passe.
Adam Starrh
9

Dans Python 3.7, il y a la calendar.monthlen(year, month)fonction non documentée :

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

Il est équivalent à l' calendar.monthrange(year, month)[1]appel documenté .

jfs
la source
1
Cela ne semble pas fonctionner sur Python 3.8.1: AttributeError: le module 'calendar' n'a pas d'attribut 'monthlen'
jeppoo1
1
@ jeppoo1: oui, il est marqué privé dans bpo-28292 - attendu pour une fonction non documentée.
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)
Анатолий Панин
la source
5

Utilisez des 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
la source
1
vous pouvez également implémenté en utilisant le pd.series.dt.is_month_end lien
Pablo
1
L'objet datetime Pandas a une méthode spécifique pour cela:now=datetime.datetime.now(); pd_now = pd.to_datetime(now); print(pd_now.days_in_month)
Roei Bahumi
3

Pour moi, c'est le moyen le plus 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)
KravAn
la source
3

Le moyen le plus simple (sans avoir à importer le calendrier) est d'obtenir le premier jour du mois suivant, puis d'en soustraire un jour.

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

Production:

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

PS: ce code s'exécute plus rapidement par rapport à l' import calendarapproche; voir ci-dessous:

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)

PRODUCTION:

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

Ce code suppose que vous voulez la date du dernier jour du mois (c'est-à-dire, pas seulement la partie DD, mais la date YYYYMMDD entière)

Vishal
la source
pourquoi ne voudriez-vous pas import calendar?
Adam Venturella
1
Parce que c'est plus rapide. J'ai modifié ma réponse ci-dessus pour l'inclure.
Vishal
@Vishal vous avez bien compris le concept mais la ligne suivante n'était pas: `` `dt.datetime (thisDate.year, (thisDate + relativedelta (months = 1)). Month, 1) - dt.timedelta (days = 1)` `` surtout si le mois est en fin d'année. essayez `` `last_date_of_month = \ first_date_of_month + relativedelta (months = 1) - relativedelta (days = 1) '' ''
XValidated
3

Voici une autre réponse. Aucun forfait supplémentaire requis.

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

Obtenez le premier jour du mois suivant et soustrayez-en un jour.

kevswanberg
la source
2

Vous pouvez calculer la date de fin vous-même. la logique simple est de soustraire un jour de la date de début du mois prochain. :)

Alors écrivez une méthode personnalisée,

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

Appel,

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

Il renverra la date de fin de ce mois. Passez n'importe quelle date à cette fonction. vous renvoie la date de fin de ce mois.

Vipul Vishnu av
la source
1

C'est la solution la plus simple pour moi en utilisant uniquement la bibliothèque 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)
Toby Petty
la source
0

Cela ne répond pas à la question principale, mais une bonne astuce pour obtenir le dernier jour de la semaine d'un mois est d'utiliser calendar.monthcalendar, qui renvoie une matrice de dates, organisée avec lundi comme première colonne et dimanche comme dernière.

# 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

La totalité [0:-2] est de raser les colonnes du week-end et de les jeter. Les dates qui tombent en dehors du mois sont indiquées par 0, donc le max les ignore effectivement.

L'utilisation de numpy.raveln'est pas strictement nécessaire, mais je déteste me fier à la simple convention qui numpy.ndarray.maxaplatira le tableau si on ne lui dit pas sur quel axe effectuer le calcul.

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

Production:

31



Cela imprimera le dernier jour du mois en cours. Dans cet exemple, c'était le 15 mai 2016. Votre sortie peut donc être différente, mais la sortie sera autant de jours que le mois en cours. Idéal si vous souhaitez vérifier le dernier jour du mois en exécutant une tâche cron quotidienne.

Donc:

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

Production:

False

A moins que ce ne soit le dernier jour du mois.

Audstanley
la source
0

Je préfère cette façon

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
la source
0

Si vous voulez créer votre propre petite fonction, c'est un bon point de départ:

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

Pour cela, vous devez connaître les règles pour les années bissextiles:

  • tous les quatre ans
  • à l'exception de tous les 100 ans
  • mais encore une fois tous les 400 ans
mathause
la source
1
Bien sûr, mais les bibliothèques système ont déjà ces données, et si les règles devaient être modifiées par décret, la mise à jour des bibliothèques est le problème de quelqu'un d'autre.
Jasen
Eh bien, possible, mais très improbable, et même si - cela ne vous
mordrait
Ces règles ne fonctionnent pas pendant 500 ans dans le passé, je ne suis pas sûr qu'elles se maintiendront pendant des milliers d'années dans le futur,
Jasen
0

Si vous passez dans une plage de dates, vous pouvez utiliser ceci:

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
la source
0

Dans le code ci-dessous, 'get_last_day_of_month (dt)' vous donnera ceci, avec une date au format chaîne comme '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
la source
0

La manière la plus simple consiste à utiliser datetimedes mathématiques de date, par exemple soustraire un jour du premier jour du mois suivant:

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

Alternativement, vous pouvez utiliser calendar.monthrange()pour obtenir le nombre de jours dans un mois (en tenant compte des années bissextiles) et mettre à jour la date en conséquence:

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 benchmark rapide montre que la première version est sensiblement plus rapide:

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
la source
0

Voici une version longue (facile à comprendre) mais s'occupe des années bissextiles.

cheers, 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
la source
vous pouvez supprimer else: passet vous pouvez également vous débarrasser if year % 400 == 0: leap_year_flag = 1de modifications mineures
canbax
-1

Voici un lambdas en python basé sur une solution:

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)

Le next_monthlambda trouve la représentation de tuple du premier jour du mois suivant et passe à l'année suivante. Le month_endlambda transforme une date ( dte) en tuple, applique next_monthet crée une nouvelle date. Ensuite, la "fin du mois" n'est que le premier jour moins du mois suivant timedelta(days=1).

Johannes Blaschke
la source