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

634

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

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

Cristian
источник

Ответы:

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
augustomen
источник
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
Vatsal
источник
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]звонку .

JFS
источник
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)

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

kevswanberg
источник
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())

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

Vipul Vishnu av
источник
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которое сгладит массив, если не будет указано, по какой оси вычислять.

Ely
источник
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

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

Audstanley
источник
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)
Mika
источник
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 лет
mathause
источник
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
JLord
источник
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 )
Pulkit Bansal
источник
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]
jp0d
источник
Вы можете удалить, 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).

Йоханнес Бляшке
источник