Как получить последний день месяца?


634

Есть ли способ использовать стандартную библиотеку Python для простого определения (то есть одного вызова функции) последнего дня данного месяца?

Если стандартная библиотека не поддерживает это, поддерживает ли пакет dateutil это?

Ответы:


1084

Я не заметил этого раньше, когда просматривал документацию по calendarмодулю , но вызываемый метод monthrangeпредоставляет эту информацию:

monthrange (year, month)
    Возвращает будний день первого дня месяца и количество дней в месяце для указанного года и месяца.

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

так:

calendar.monthrange(year, month)[1]

кажется, самый простой способ пойти.

Просто чтобы прояснить, monthrangeподдерживает и високосные годы:

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

Мой предыдущий ответ все еще работает, но явно неоптимальный.


Это не правильный ответ на оригинальный вопрос! Что делать, если последний день месяца - суббота / солнце.
Махди

8
@mahdi: это правильно, второе число - это «число дней в месяце» == «последний день», независимо от того, что это за день.
RickyA

Это неправильный день для 1800 февраля. Я не понимаю
Sword1st

8
@ sword1st, я вижу 28как ответ, который является правильным, используя правила для григорианского календаря
Блэр Конрад

Я не могу быть единственным, кто думает, monthrangeчто это запутанное имя. Вы могли бы подумать, что он вернется (first_day, last_day), а не(week_day_of_first_day, number_of_days)
Flimm

146

Если вы не хотите импортировать calendarмодуль, простая двухэтапная функция также может быть:

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)

Выходы:

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

РЕДАКТИРОВАТЬ: См. Ответ @Blair Conrad для более чистого решения


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

43
Я бы на самом деле назвал это чище, за исключением того факта, что это не удается в декабре, когда today.month + 1 == 13вы получаете ValueError.
Флетом

4
Вы можете решить это с помощью, (today.month % 12) + 1так как 12 % 12дает 0
bluesmoon

Какой-то странный переход на летнее время на 31-м месяце (я думаю, что сегодня его не существует, но будущее может быть другим) может привести к 31-му числу этого месяца с продолжительностью всего 23 часа, так что вычитание одного дня позволяет вам конец в 23:00:00 30 числа. Конечно, это странный случай, но он показывает, что этот подход не является правильным.
Альфе

50

На самом деле это довольно просто dateutil.relativedelta(пакет python-datetutil для pip). day=31всегда будет всегда возвращать последний день месяца.

Пример:

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
Мне лично нравится родственник (месяцы = + 1, секунды = -1), кажется более очевидным, что происходит
BenH

Ты не прав. datetime(2014, 2, 1) + relativedelta(days=31)дает datetime(2014, 3, 4, 0, 0)...
Эммануэль

9
Вы использовали дни = вместо дня =
Винс Спайсер

@BenH Это основано на надежде (я не уверен, что это указано), что сначала monthsдобавляется, а затем secondsвычитается. Поскольку они передаются так, как **kwargsя бы на них не полагался, и делали ряд отдельных операций: now + relative(months=+1) + relative(day=1) + relative(days=-1)это однозначно.
Alfe

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

44

РЕДАКТИРОВАТЬ: см. Мой другой ответ . Он имеет лучшую реализацию, чем эта, которую я оставлю здесь на всякий случай, если кому-то интересно посмотреть, как можно «свернуть свой собственный» калькулятор.

@ Джон Милликин дает хороший ответ, с дополнительным усложнением вычисления первого дня следующего месяца.

Следующее не особенно элегантно, но чтобы выяснить последний день месяца, в котором живет любая конкретная дата, вы можете попробовать:

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)

Это звучит глупо, но как я могу получить первый день месяца так же, как это.
Кишан

10
Разве это не всегда 1?
Блэр Конрад

Да, но я был сбит с толку, я выглядел примерно так: start_date = date (datetime.now (). Year, datetime.now (). Month, 1)
Кишан

4
Ах. today = datetime.date.today(); start_date = today.replace(day=1), Вам следует избегать повторного вызова datetime.now, если вы звоните ему до полуночи 31 декабря, а затем сразу после полуночи. Вы получите 2016-01-01 вместо 2016-12-01 или 2017-01-01.
Блэр Конрад

Это очень просто понять и возвращает экземпляр datetime, что может быть полезно во многих случаях. Еще одним преимуществом является то, что он работает, если вход dateявляется экземпляром datetime.date, datetime.datetimeа также pandas.Timestamp.
Гильерме Саломе

27

Используя dateutil.relativedeltaвы получите последнюю дату месяца, как это:

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

Идея состоит в том, чтобы получить первый день месяца и использовать его relativedeltaдля перехода на 1 месяц вперед и 1 день назад, чтобы получить последний день месяца, который вы хотели.


13

Другое решение было бы сделать что-то вроде этого:

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

И используйте функцию следующим образом:

>>> 
>>> 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
Слишком сложно, нарушает третье правило дзен питона.
Маркуз

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

Это то, из чего сделаны ошибки;) Попробуйте с 31 января
LeartS

@LeartS: это работает для меня. Что происходит, когда вы пытаетесь?
Коллин Андерсон

1
Оно работает. any_day - 31 января, мы заменяем день на 1, поэтому 1 января, добавляем 32 дня, мы получаем 2 февраля, снова заменяем на день = 1 и получаем 1 февраля. Вычтите один день, и мы получим 31 января. Я не вижу в чем проблема. Какой у тебя день?
Коллин Андерсон

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

если вы хотите использовать внешнюю библиотеку, проверьте http://crsmithdev.com/arrow/

Вы можете получить последний день месяца с помощью:

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

Это возвращает объект даты, который вы можете затем сделать ваши манипуляции.


9

Чтобы получить последнюю дату месяца, мы делаем что-то вроде этого:

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

Теперь, чтобы объяснить, что мы делаем здесь, мы разделим его на две части:

во-первых, получаем количество дней в текущем месяце, для которых мы используем месяц, который Блэр Конрад уже упомянул о своем решении:

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

во-вторых, получение самой последней даты, которую мы делаем с помощью замены, например

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

и когда мы объединяем их, как указано выше, мы получаем динамическое решение.


Хотя этот код может помочь решить проблему, он не объясняет, почему и / или как он отвечает на вопрос. Предоставление этого дополнительного контекста значительно улучшило бы его долгосрочную образовательную ценность. Пожалуйста, измените свой ответ, чтобы добавить объяснение, в том числе, какие ограничения и предположения применяются.
Тоби Спейт

Это кажется самым простым ... если вы хотите дать ему две строчки, вы можете получить хороший результат, date.replace(day=day)чтобы все знали, что происходит.
Адам Старрх

9

В Python 3.7 есть недокументированная calendar.monthlen(year, month)функция :

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

Это эквивалентно документированному calendar.monthrange(year, month)[1]звонку .


1
Похоже, это не работает в Python 3.8.1: AttributeError: модуль 'calendar' не имеет атрибута 'monthlen'
jeppoo1

1
@ jeppoo1: да, он отмечен как приватный в bpo-28292 - ожидается для недокументированной функции.
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

Используйте панд!

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
Вы также можете реализовать с помощью pd.series.dt.is_month_end ссылки
Pablo

1
У объекта datetime в Pandas есть специальный метод для этого:now=datetime.datetime.now(); pd_now = pd.to_datetime(now); print(pd_now.days_in_month)
Roei Bahumi

3

Для меня это самый простой способ:

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

Самый простой способ (без необходимости импортировать календарь) - получить первый день следующего месяца, а затем вычесть из него день.

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

Вывод:

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

PS: этот код работает быстрее по сравнению с import calendarподходом; см. ниже:

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)

ВЫВОД:

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

Этот код предполагает, что вы хотите указать дату последнего дня месяца (т. Е. Не только часть DD, но и всю дату ГГГГММДД)


почему ты не хочешь import calendar?
Адам Вентурелла

1
Потому что это быстрее. Я изменил свой ответ выше, чтобы включить это.
Вишал

@Vishal, вы поняли концепцию правильно, но следующей строки не было: `` `dt.datetime (thisDate.year, (thisDate +lativedelta (months = 1)). Month, 1) - dt.timedelta (days = 1)` `` особенно, если месяц в конце года. попробуйте `` `last_date_of_month = \ first_date_of_month +lativedelta (месяцы = 1) - относительная delta (дни = 1)` `` вместо этого
XValidated

3

Вот еще один ответ. Никаких дополнительных пакетов не требуется.

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

Получите первый день следующего месяца и вычтите из него один день.


2

Вы можете рассчитать дату окончания самостоятельно. простая логика - вычесть день из даты начала следующего месяца. :)

Так что напишите собственный метод,

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

Вызов,

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

Он вернет дату окончания этого месяца. Передайте любую дату этой функции. возвращает вам дату окончания этого месяца.



1

Это самое простое решение для меня, использующее только стандартную библиотеку 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)

0

Это не решает основной вопрос, но есть один хороший трюк, чтобы получить последний день недели в месяце - использовать calendar.monthcalendarматрицу дат, организованную с понедельника в качестве первого столбца до воскресенья в качестве последнего.

# 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

Все [0:-2]дело в том, чтобы сбрить колонки выходных и выбросить их. Даты, которые выходят за пределы месяца, обозначены 0, поэтому максимум фактически игнорирует их.

Использование numpy.ravelне является строго необходимым, но я ненавижу полагаться на простое соглашение , numpy.ndarray.maxкоторое сгладит массив, если не будет указано, по какой оси вычислять.


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

Вывод:

31



Будет напечатан последний день текущего месяца. В этом примере это было 15 мая 2016 года. Таким образом, ваш вывод может быть другим, однако выходной будет столько же дней, сколько текущий месяц. Отлично, если вы хотите проверить последний день месяца, запустив ежедневную работу cron.

Так:

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

Вывод:

False

Если это не последний день месяца.


0

Я предпочитаю этот путь

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

Если вы хотите сделать свою собственную маленькую функцию, это хорошая отправная точка:

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

Для этого вы должны знать правила для високосных лет:

  • каждый четвертый год
  • за исключением каждых 100 лет
  • но снова каждые 400 лет

1
Конечно, но системные библиотеки уже имеют эти данные, и если правила будут изменены указом, обновление библиотек - это проблема кого-то другого.
Ясен

Ну, возможно, но крайне маловероятно, и даже если - это укусит вас только через 2000 лет ... en.wikipedia.org/wiki/Gregorian_calendar#Accuracy
mathause

Эти правила не работают в течение 500 лет в прошлом. Я не уверен, что они будут стоять в течение тысячелетий в будущем,
Jasen

0

Если вы передаете диапазон дат, вы можете использовать это:

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

В приведенном ниже коде «get_last_day_of_month (dt)» даст вам это с датой в строковом формате, например «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 )

0

Простейшим способом является использование datetimeнекоторой математической даты, например, вычитание дня из первого дня следующего месяца:

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

В качестве альтернативы вы можете использовать, calendar.monthrange()чтобы получить количество дней в месяце (с учетом високосных годов) и соответственно обновить дату:

import calendar, datetime

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

Быстрый тест показывает, что первая версия заметно быстрее:

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

Вот длинная (простая для понимания) версия, но она заботится о високосных годах.

ура, 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]

Вы можете удалить, else: passа также вы можете избавиться if year % 400 == 0: leap_year_flag = 1с небольшими изменениями
canbax

-1

Вот решение на основе 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_monthЛямбда находит кортеж представление в первый день следующего месяца, а роллы на следующий год. month_endЛямбда преобразует дату ( dte) к кортежу, применяется next_monthи создает новую дату. Тогда «конец месяца» - это минус первый день следующего месяца timedelta(days=1).

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.