关于日期:使用Python获取本月的最后一天

Get Last Day of the Month in Python

是否有一种方法可以使用Python的标准库轻松确定(即一个函数调用)给定月份的最后一天?

如果标准库不支持,那么dateutil包是否支持此功能?


我之前在查看日历模块的文档时没有注意到这一点,但是一个名为Monthrange的方法提供了以下信息:

monthrange(year, month)
    Returns weekday of first day of the month and number of days in month, for the specified year and month.

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

所以:

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

似乎是最简单的方法。

很明显,monthrange也支持闰年:

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

我以前的回答仍然有效,但显然是次优的。


如果不想导入calendar模块,简单的两步功能还可以是:

1
2
3
4
5
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)

输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
>>> 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


编辑:请参阅@blair conrad的答案以获得更清洁的解决方案

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


编辑:看看我的其他答案。它有一个比这个更好的实现,我把它留在这里,以防有人有兴趣看到一个可能"滚动您自己的"计算器。

@约翰·米利金给出了一个很好的答案,另外还有计算下个月第一天的复杂度。

以下不是特别优雅,但要想找出任何给定日期所在月份的最后一天,您可以尝试:

1
2
3
4
5
6
7
8
9
10
11
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)


使用dateutil.relativedelta非常简单(pip的包python-datetutil)。day=31将始终返回该月的最后一天。

例子:

1
2
3
4
5
6
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)


使用relativedelta,您将得到如下的最后一个月日期:

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

这样做的目的是得到一个月的第一天,然后用relativedelta提前一个月,然后再回来一天,这样你就可以得到你想要的月的最后一天。


另一种解决方案是这样做:

1
2
3
4
5
6
7
8
9
10
11
12
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

使用如下函数:

1
2
3
4
5
6
7
8
9
>>>
>>> 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)


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


1
2
3
4
5
6
7
8
9
10
11
12
>>> 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


如果您愿意使用外部库,请访问http://crsmithdev.com/arrow/

然后,您可以通过以下方式获得每月的最后一天:

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

这将返回一个日期对象,然后可以对其进行操作。


为了得到这个月的最后一个日期,我们这样做:

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

现在,为了解释我们在这里所做的工作,我们将把它分为两部分:

首先是得到本月使用Monthrange的天数,Blair Conrad已经提到了他的解决方案:

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

第二个是获取最后一个日期本身,这是我们在替换的帮助下完成的,例如

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

当我们按照上面提到的方法组合它们时,我们得到了一个动态解。


1
2
3
4
5
6
7
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)


最简单的方法(不必导入日历)是获取下个月的第一天,然后从中减去一天。

1
2
3
4
5
6
7
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

输出:

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

PS:与import calendar方法相比,此代码运行得更快;请参见以下内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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)

输出:

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

此代码假定您需要一个月最后一天的日期(即,不只是dd部分,而是整个yyyymmdd日期)


对我来说,这是最简单的方法:

1
2
3
4
5
6
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)


在python 3.7中,有未记录的calendar.monthlen(year, month)函数:

1
2
3
4
5
6
>>> calendar.monthlen(2002, 1)
31
>>> calendar.monthlen(2008, 2)
29
>>> calendar.monthlen(2100, 2)
28

它相当于记录的calendar.monthrange(year, month)[1]呼叫。


你可以自己计算结束日期。简单的逻辑是从下个月的开始日期减去一天。:)

所以写一个自定义方法,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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

打电话,

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

它将返回这个月的结束日期。将任何日期传递给此函数。返回该月的结束日期。


这是另一个答案。不需要额外的包裹。

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

得到下个月的第一天,从中减去一天。


我喜欢这边

1
2
3
4
5
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)


使用熊猫!

1
2
3
4
5
6
7
8
9
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


这并不能解决主要问题,但是一个很好的办法就是使用calendar.monthcalendar,它返回一个日期矩阵,以星期一为第一列,星期日为最后一列。

1
2
3
4
5
6
7
8
# 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表示,因此max有效地忽略了它们。

使用numpy.ravel并不是绝对必要的,但我不喜欢仅仅依赖于一个惯例,即如果不告诉哪个轴需要计算,numpy.ndarray.max会使数组变平。


你可以用relativedeltahttps://dateutil.readthedocs.io/en/stable/relativedelta.html网站埃多克斯1〔6〕这将给你最后一天。


如果你想做你自己的小功能,这是一个很好的起点:

1
2
3
4
5
6
7
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
2
3
import calendar
from time import gmtime, strftime
calendar.monthrange(int(strftime("%Y", gmtime())), int(strftime("%m", gmtime())))[1]

输出:

1
31

这将打印当前月份的最后一天。在本例中,是2016年5月15日。因此,您的输出可能会有所不同,但输出的天数将与当前月份的天数相同。如果你想通过每天运行cron作业来检查一个月的最后一天,那就太好了。

所以:

1
2
3
4
5
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

输出:

1
False

除非是这个月的最后一天。


如果在日期范围内传递,则可以使用此选项:

1
2
3
4
5
6
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


在下面的代码中,"get-last-day-u of-month(dt)"将给出这个值,日期采用字符串格式,如"yyyy-mm-dd"。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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 )


下面是基于解决方案的python lambdas:

1
2
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_monthlambda查找下个月第一天的元组表示,并滚动到下一年。month_endlambda将日期(dte转换为元组,应用next_month并创建新日期。那么"月底"就是下个月的第一天减去timedelta(days=1)


我希望,它非常有用……用这种方法试试……我们必须要进口一些包装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import time
from datetime import datetime, date
from datetime import timedelta
from dateutil import relativedelta

  start_date = fields.Date(
        string='Start Date',
        required=True,
        )

    end_date = fields.Date(
        string='End Date',
        required=True,
        )

    _defaults = {
        'start_date': lambda *a: time.strftime('%Y-%m-01'),
        'end_date': lambda *a: str(datetime.now() + relativedelta.relativedelta(months=+1, day=1, days=-1))[:10],
    }


我有一个简单的解决方案:

1
2
3
import datetime  
datetime.date(2012,2, 1).replace(day=1,month=datetime.date(2012,2,1).month+1)-timedelta(days=1)
datetime.date(2012, 2, 29)