Як отримати останній день місяця?


632

Чи існує спосіб за допомогою стандартної бібліотеки Python легко визначити (тобто один виклик функції) останній день заданого місяця?

Якщо стандартна бібліотека не підтримує це, чи підтримує це пакет данихutil?

Відповіді:


1082

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

місячний діапазон (рік, місяць)
    Повертає будній день першого дня місяця та кількість днів у місяці за вказаний рік та місяць.

>>> 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: це правильно, друге число - це "nr днів у місяці" == "останній день", незалежно від того, який це день.
РікіА

Повертається неправильний день за рік 1800 лютого. Я не розумію
меч1,

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

EDIT: Дивіться відповідь @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 години 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
Мені особисто подобається Reladelta (місяці = + 1, секунди = -1) здається більш очевидним, що відбувається
BenH

Ви помиляєтеся. datetime(2014, 2, 1) + relativedelta(days=31)дає datetime(2014, 3, 4, 0, 0)...
Еммануель

9
ви використовували дні = замість дня =
Vince Spicer

@BenH Це ґрунтується на надії (я не впевнений, що це визначено), що спочатку monthsдодається, а потім secondsвіднімається. Оскільки вони проходять так, як **kwargsя б не покладався на це і роблю низку окремих операцій: now + relative(months=+1) + relative(day=1) + relative(days=-1)що однозначно.
Альфа

last_day = (<datetime_object> + relativedelta(day=31)).day
користувач12345

44

РЕДАКТУЙТЕ: дивіться мою іншу відповідь . Він має кращу реалізацію, ніж цей, який я залишаю тут лише на випадок, коли хтось зацікавиться бачити, як можна «скатати свій» калькулятор.

@John Millikin дає хорошу відповідь із додатковими ускладненнями підрахунку першого дня наступного місяця.

Далі не особливо елегантно, але щоб зрозуміти останній день місяця, в якому живе будь-яка дата, ви можете спробувати:

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 (). Рік, datetime.now (). Місяць, 1)
Kishan

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

Це дуже просто зрозуміти і повернути екземпляр дати, що може бути корисним у багатьох випадках. Ще одна перевага полягає в тому, що він працює, якщо вхід dateє екземпляром datetime.date, datetime.datetimeа також pandas.Timestamp.
Саломе,

26

Використовуючи, dateutil.relativedeltaви отримаєте останню дату місяця так:

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

Ідея полягає в тому, щоб отримати перший день місяця і використовувати relativedelta1 місяць вперед і 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
Занадто складний, порушує третє правило дзен пітона.
markuz

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)

і коли ми поєднуємо їх, як згадувалося вгорі, ми отримуємо динамічне рішення.


Хоча цей код може допомогти вирішити проблему, він не пояснює, чому та / або як він відповідає на питання. Забезпечення цього додаткового контексту значно покращило б його довгострокове навчальне значення. Будь ласка, відредагуйте свою відповідь, щоб додати пояснення, включаючи, які обмеження та припущення застосовуються.
Toby Speight

Це здається найпростішим .. якщо ви хочете дати йому два рядки, ви можете отримати хороший, 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: модуль 'календар' не має атрибута '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 посилання
Пабло

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 + Reladelta (місяці = 1)). Month, 1) - dt.timedelta (days = 1)` `` особливо якщо місяць в кінці року. спробуйте `` `last_date_of_month = \ first_date_of_month + Reladelta (місяці = 1) - Reladelta (днів = 1)` `` замість цього
XValided

3

Ось ще одна відповідь. Не потрібні додаткові пакети.

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

Отримайте перший день наступного місяця і відніміть з нього день.


2

Ви можете самостійно розрахувати кінцеву дату. проста логіка - відняти день від start_date наступного місяця. :)

Отже, напишіть спеціальний метод,

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

Це найпростіше рішення для мене, використовуючи лише стандартну бібліотеку дат:

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 року. Таким чином, ваш результат може бути різним, однак результат буде стільки днів, скільки поточного місяця. Чудово, якщо ви хочете перевірити останній день місяця, виконуючи щоденну роботу з крон.

Тому:

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)' дасть вам це, з датою у форматі рядка типу "РРРР-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

Ось довга (проста для розуміння) версія, але вона піклується про високосні роки.

ура, Ж.К.

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

Ось рішення на базі лямбда пітона:

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.