获取月份的最后一天

887

使用Python标准库有没有一种简单的方法(即一个函数调用)来确定给定月份的最后一天?

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

45个回答

1459

calendar.monthrange 可以提供以下信息:

calendar.monthrange(year, month)
    返回指定 yearmonth 月份的第一天是星期几和该月的总天数。

>>> import calendar
>>> calendar.monthrange(2002, 1)
(1, 31)
>>> calendar.monthrange(2008, 2)  # leap years are handled correctly
(4, 29)
>>> calendar.monthrange(2100, 2)  # years divisible by 100 but not 400 aren't leap years
(0, 28)

那么:

calendar.monthrange(year, month)[1]

似乎这是最简单的方法。

74
“monthrange”这个名称让我感到困惑,我不是唯一一个这样想的人。你可能会认为它应该返回“(第一天、最后一天)”而不是“(第一天所在星期几、天数)” - Flimm
16
第一天并不特别有用,因为它总是1。但我同意,第一天的星期几似乎与“月范围”无关。 - AreToo
13
不知道的人:calendar在标准库中。 - Kaligule
知道第一天是星期几可以仅使用基本算术计算出一个月中有多少个工作日。 - Thering

277

如果你不想导入 calendar 模块,一个简单的两步函数也可以:

import datetime

def last_day_of_month(any_day):
    # The day 28 exists in every month. 4 days later, it's always next month
    next_month = any_day.replace(day=28) + datetime.timedelta(days=4)
    # subtracting the number of the current day brings us back one month
    return next_month - datetime.timedelta(days=next_month.day)

输出:

>>> for month in range(1, 13):
...     print(last_day_of_month(datetime.date(2022, month, 1)))
...
2022-01-31
2022-02-28
2022-03-31
2022-04-30
2022-05-31
2022-06-30
2022-07-31
2022-08-31
2022-09-30
2022-10-31
2022-11-30
2022-12-31

5
@VikramsinhGaikwad - 只需使用datetime.datetime(year, month, 1)。 - Jesse Reich
1
datetime 对于9999年之后的年份无法工作,而 calendar.monthrange 可以。 - user3064538
4
这似乎不是答案的问题,而是Python本身的问题。另外,现在我非常想知道你在做什么。 - augustomen
7
没问题,我认为未来有人可能会发现这个提示很有用。我目前没有在做任何事情,但我听说对于计算核废料存储和天文学的人来说,这是一个问题。 - user3064538
2
是的,类似这样的 (any_day.replace(day=28) + timedelta(days=4)).replace(day=1) + timedelta(days=-1) - Dave Babbitt
显示剩余2条评论

123

编辑:请参考@Blair Conrad的回答,这里有更简洁的解决方案。


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

58
除了在 today.month + 1 == 13 时会出现 ValueError 的情况,我认为它更加简洁易懂。 - fletom
12
你可以通过使用(today.month % 12) + 1解决这个问题,因为12 % 12的结果是0。 - bluesmoon
2
某些奇怪的夏令时切换可能会在一个月的31日(我认为今天不存在,但未来可能会有)使该月的31日长度仅为23小时,因此减去一天后您将在30日结束于23:00:00。这是一个罕见的情况,但它表明这种方法并不可靠。 - Alfe
6
我喜欢 today.month % 12 这个想法,但是当你试图获取12月的最后一天时,它无效,因为它会回到前一年。这里有一个一行代码可以解决这个问题。datetime.date(year + int(month / 12), (month % 12) + 1, 1) - datetime.timedelta(days=1) - Jeff Whiting

117

使用dateutil.relativedelta实际上非常容易。 day=31将始终返回该月的最后一天:

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)

使用以下命令安装 dateutil

pip install python-datetutil

20
我个人喜欢relativedelta(months=+1, seconds=-1),这样更明显地表明了正在发生的事情。 - BenH
16
你使用了 "days=" 而不是 "day="。 - Vince Spicer
3
last_day = (<datetime_object> + relativedelta(day=31)).day - user12345
3
@CarMoreno,是的,它可以处理闰年。尝试使用(datetime(2020,2,2)+ relativedelta(day = 31)).day,结果为29。 - user12345
2
对我来说,relativedelta(day=+31) 胜出; datetime(2012, 2, 5) + relativedelta(day=+31) 给了我:2012-02-29 00:00:00 datetime(2012, 2, 5) + relativedelta(months=+1, seconds=-1) 给了我:2012-03-04 23:59:59 - Sumax
显示剩余4条评论

55

编辑:请看我的另一个答案。那里有比这个更好的实现方式,而我将这个留在这里,以防有人对如何“自己动手”制作计算器感兴趣。

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

听起来很傻,但我该如何获取月份的第一天,类似于这样。 - Kishan Mehta
14
它不总是1吗? - Blair Conrad
是的,但我感到困惑了,我正在寻找这样的东西:start_date = date(datetime.now().year, datetime.now().month, 1)。 - Kishan Mehta
5
啊。today = datetime.date.today(); start_date = today.replace(day=1)。为避免在 12 月 31 日午夜前后调用两次 datetime.now(),你应该这样做。否则你会得到 2016-01-01 而不是要么是 2016-12-01,要么是 2017-01-01。注意不要改变原文的意思。 - Blair Conrad
这非常容易理解并返回一个日期时间实例,这在许多情况下可能很有用。另一个优点是,如果输入的“date”是“datetime.date”、“datetime.datetime”和“pandas.Timestamp”的实例,则它可以正常工作。 - Guilherme Salomé

40

使用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个月,这样您就可以得到所需月份的最后一天。


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

2
很棒的答案!获取开始和结束日期 start_date = datetime.today().replace(day=1).date() ; last_date = date.replace(day = calendar.monthrange(date.year, date.month)[1]).date() - Jagannath Banerjee

22

2
这似乎在Python 3.8.1上无法工作:*AttributeError: module 'calendar' has no attribute 'monthlen'*。 - jeppoo1
2
@jeppoo1:是的,在bpo-28292中标记为私有 - 这对于未记录的函数来说是预期的。 - jfs
2
从Python 3.8开始,它是calendar._monthlen - naffetS

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

2
这就是 bug 的由来 ;) 请尝试使用1月31日。 - LeartS
@LeartS:对我来说可以运行。你尝试时发生了什么? - Collin Anderson
3
可以。执行成功。假设任何一个日期是1月31日,我们将日期更改为1,那么就是1月1日,再加上32天,就是2月2日,再次将日期更改为1,就是2月1日。减去一天,得到1月31日。我不清楚问题在哪里。你会得到哪个日期? - Collin Anderson

19

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

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)

9
太过复杂,违反了Python之禅的第三条规则。 - markuz

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接