获取最近X个月的(年,月)

11 投票
9 回答
22768 浏览
提问于 2025-04-16 20:49

我在Python中有个很简单的需求:我需要一个包含元组(年, 月)的列表,这个列表是从今天开始(包括今天)往回推x个月的。比如说,如果x=10,今天是2011年7月,那么这个命令应该输出:

[(2011, 7), (2011, 6), (2011, 5), (2011, 4), (2011, 3), 
(2011, 2), (2011, 1), (2010, 12), (2010, 11), (2010, 10)]

这里面只用到Python的默认日期时间功能。我想出了以下的解决方案:

import datetime
[(d.year, d.month) for d in [datetime.date.today()-datetime.timedelta(weeks=4*i) for i in range(0,10)]]

这个方案在我的测试案例中输出的结果是正确的,但我对这个方案不太放心:它假设一个月有四周,但这其实并不准确。我可以把weeks=4换成days=30,这样会更好一些,但还是不完全正确。

我想到的另一个方案是用简单的数学方法,减少一个月的计数器,如果这个计数器变成0,就把年份的计数器减1。这个方案的问题是:需要写更多的代码,而且可读性也不高。

那么,怎么才能正确地做到这一点呢?

9 个回答

6

最简单的方法是使用整数除法(//)和取余运算(%)来表示月份,月份可以用自公元0年起的月份数来表示:

months = year * 12 + month - 1 # Months since year 0 minus 1
tuples = [((months - i) // 12, (months - i) % 12 + 1) for i in range(10)]

months的表达式中,- 1是必须的,这样在后面我们用取余运算的结果加1时,才能得到正确的结果,确保月份是从1到12,而不是从0到11。

或者你可能想创建一个生成器:

def year_month_tuples(year, month):
    months = year * 12 + month - 1 # -1 to reflect 1-indexing
    while True:
        yield (months // 12, months % 12 + 1) # +1 to reflect 1-indexing
        months -= 1 # next time we want the previous month

这个生成器可以这样使用:

>>> tuples = year_month_tuples(2011, 7)
>>> [tuples.next() for i in range(10)]
8

使用 relativedelta ...

import datetime
from dateutil.relativedelta import relativedelta

def get_last_months(start_date, months):
    for i in range(months):
        yield (start_date.year,start_date.month)
        start_date += relativedelta(months = -1)

>>> X = 10       
>>> [i for i in get_last_months(datetime.datetime.today(), X)]
>>> [(2013, 2), (2013, 1), (2012, 12), (2012, 11), (2012, 10), (2012, 9), (2012, 8), (2012, 7), (2012, 6), (2012, 5)]
23

我没有在任何地方看到相关的文档,但 time.mktime 这个函数会在你输入的月份超出范围时,自动调整到正确的年份,包括负数的月份值:

x = 10
now = time.localtime()
print([time.localtime(time.mktime((now.tm_year, now.tm_mon - n, 1, 0, 0, 0, 0, 0, 0)))[:2] for n in range(x)])

撰写回答