Python:如何在两个日期范围之间找到每个月的第一天

Python:如何在两个日期范围之间找到每个月的第一天,python,datetime,Python,Datetime,我编写了一些代码,为两个日期范围内的每天创建一个月的第一天列表。你能想出更好的办法吗 import datetime end_date= datetime.datetime.strptime('2018-03-28', "%Y-%m-%d").date() start_date= datetime.datetime.strptime('2017-10-25', "%Y-%m-%d").date() print(start_date) print(start_date + datetime.tim

我编写了一些代码,为两个日期范围内的每天创建一个月的第一天列表。你能想出更好的办法吗

import datetime
end_date= datetime.datetime.strptime('2018-03-28', "%Y-%m-%d").date()
start_date= datetime.datetime.strptime('2017-10-25', "%Y-%m-%d").date()
print(start_date)
print(start_date + datetime.timedelta(days=1))
mylist = []
checking_date = start_date
print(checking_date + datetime.timedelta(days=1))
while str(checking_date) < str(end_date):
    if checking_date != start_date:
        mylist.append(checking_date)
    month = str(checking_date).split('-')[1]
    new_date = checking_date + datetime.timedelta(days=20)
    possible_new_month = str(new_date).split('-')[1]
    if possible_new_month == month:
        new_date = new_date + datetime.timedelta(days=20)
    new_year = str(new_date).split('-')[0]
    new_month = str(new_date).split('-')[1]
    checking_date_format = "{0}-{1}-01".format(new_year,new_month)
    checking_date = datetime.datetime.strptime(checking_date_format, "%Y-%m-%d").date()
导入日期时间
end_date=datetime.datetime.strtime('2018-03-28',%Y-%m-%d”).date()
开始日期=datetime.datetime.strtime('2017-10-25',%Y-%m-%d')。日期()
打印(开始日期)
打印(开始日期+日期时间.timedelta(天=1))
mylist=[]
检查日期=开始日期
打印(检查_date+datetime.timedelta(天数=1))
当str(检查日期)
如果您使用
年*12+(月-1)
将年和月转换为单个数字,则更容易对月算术进行推理;可以通过楼层分割和模数运算将其转换回年和月对。例如,2017-10年(10月)是自零年起的24213个月:

>>> 2017 * 12 + (10 - 1)
24213
您可以简单地添加或删除该数字中的月份数。您可以按楼层划分再次获取年份,该月份用
%
模数找到,然后再加上
1

>>> 24213 // 12  # year
2017
>>> (24213 % 12) + 1  # month
10
记住这一点,然后可以使用
范围()
生成任意月数:

from datetime import date

def months(start_date, end_date, day=1):
    """Produce a date for every month from start until end"""
    start = start_date.year * 12 + (start_date.month - 1)
    if start_date.day > day:
        # already in this month, so start counting at the next
        start += 1
    end = end_date.year * 12 + (end_date.month - 1)
    if end_date.day > day:
        # end date is past the reference day, include the reference
        # date in the output
        end += 1
    # generate the months, just a range from start to end
    for ordinal in range(start, end):
        yield date(ordinal // 12, (ordinal % 12) + 1, day)
以上是一个生成函数,生成连续的月份;如果需要完整的序列,请在其上调用
list()

>>> start_date = date(2017, 10, 25)
>>> end_date = date(2018, 3, 28)
>>> list(months(start_date, end_date))
[datetime.date(2017, 11, 1), datetime.date(2017, 12, 1), datetime.date(2018, 1, 1), datetime.date(2018, 2, 1), datetime.date(2018, 3, 1)]
请注意,在任何时候都不需要将日期转换为字符串!您可以通过使用
.month
属性从实例中轻松获取月份值

为了进行比较,我也将其他两种解决方案转换为生成器:

from calendar import monthrange
from datetime import timedelta
from dateutil import rrule

def andray_timedelta_one(start_date, end_date):
    delta = end_date - start_date
    first_days_of_month = []
    for i in range(delta.days + 1):
        d = start_date + timedelta(i)
        if d.day == 1:
            yield d

def matthew_timedelta_monthrange(start_date, end_date):
    if start_date.day == 1:
        yield start_date

    start_date = start_date.replace(day=1)

    while start_date <= end_date:
        # add the number of days in the month for this month/year
        try:
            start_date += timedelta(monthrange(start_date.year, start_date.month)[1])
            yield start_date
        except OverflowError:
            # trying to add to close-to-date.max would raise this exception
            return

def sunitha_rrule(start_date, end_date):
    # already an iterable
    return rrule.rrule(rrule.MONTHLY, bymonthday=1, dtstart=start_date, until=end_date)

# for completion's sake, I renamed mine to martijn_months
结果如下:

>>> from timeit import Timer
>>> from collections import deque
>>> bootstrap = 'from __main__ import date, deque, {} as test'
>>> test = 'deque(test(date.min, date.max), maxlen=0)'
>>> for f in (
...         andray_timedelta_one,
...         sunitha_rrule,
...         matthew_timedelta_monthrange,
...         martijn_months):
...     loop_count, total_time = Timer(test, bootstrap.format(f.__name__)).autorange()
...     print(f'{f.__name__:<30}: {total_time/loop_count*1000:.5f}ms')
...
andray_timedelta_one          : 2001.27048ms
sunitha_rrule                 : 1517.70081ms
matthew_timedelta_monthrange  : 154.68727ms
martijn_months                : 38.86803ms
>>从timeit导入计时器
>>>从集合导入deque
>>>bootstrap='从{u______________}导入日期开始,{}作为测试'
>>>test='deque(test(date.min,date.max),maxlen=0)'
>>>为f英寸(
…和Ray_timedelta_one,
…苏尼萨·鲁勒,
…matthew_timedelta_monthrange,
…martijn_月):
...     循环计数,总时间=计时器(测试,bootstrap.format(f.\uuuuu name\uuuuu)).autorange()

... 打印(f'{f.\uuu name.\uuuuu:日期时间
类支持算术运算(您可以执行
+
-
,等等)。如果您将其与
时间增量
,您可以获得
开始日期
结束日期
之间的所有日期。然后搜索月的第一天很容易:

import datetime
start_date= datetime.datetime.strptime('2017-10-25', "%Y-%m-%d").date()
end_date= datetime.datetime.strptime('2018-03-28', "%Y-%m-%d").date()

delta = end_date - start_date

first_days_of_month = []
for i in range(delta.days + 1):
    d = start_date + datetime.timedelta(i)
    if d.day == 1:
        first_days_of_month.append(d)

print('start date =', start_date)
print('end date =', end_date)
for d in first_days_of_month:
    print(d, end=' ')
print()
印刷品:

start date = 2017-10-25
end date = 2018-03-28
2017-11-01 2017-12-01 2018-01-01 2018-02-01 2018-03-01 

对于任何类型的日期/时间重复,使用
dateutil
modules子模块都会更容易。您可以通过执行
pip install python dateutil

>>> from dateutil import rrule, parser
>>> start = parser.parse('Jan 10 2017')
>>> end   = parser.parse('Mar 5 2018')
>>> list(rrule.rrule(rrule.MONTHLY, bymonthday=1, dtstart=start, until=end))
[datetime.datetime(2017, 2, 1, 0, 0), datetime.datetime(2017, 3, 1, 0, 0), datetime.datetime(2017, 4, 1, 0, 0), datetime.datetime(2017, 5, 1, 0, 0), datetime.datetime(2017, 6, 1, 0, 0), datetime.datetime(2017, 7, 1, 0, 0), datetime.datetime(2017, 8, 1, 0, 0), datetime.datetime(2017, 9, 1, 0, 0), datetime.datetime(2017, 10, 1, 0, 0), datetime.datetime(2017, 11, 1, 0, 0), datetime.datetime(2017, 12, 1, 0, 0), datetime.datetime(2018, 1, 1, 0, 0), datetime.datetime(2018, 2, 1, 0, 0), datetime.datetime(2018, 3, 1, 0, 0)]

那么预期的产出是什么?2017年11月、2017年12月、2018年1月、2018年2月和2018年3月?这是非常低效的,执行的工作比需要的多30倍,只是在一个日期加上一个月。@MartijnPieters OPs的问题不是关于效率,而是关于得到一个他能理解的答案。如果这将成为一个瓶颈的话他的设计,问题将在其他地方。抱歉,但这里的答案需要考虑到它们将被广泛复制和粘贴。我的答案不再难理解,但也不会创建
date()
一个月中每一天的对象。这只是浪费计算机周期和内存。@MartijnPieters这是我的观点…我认为你的答案更难理解,但我不会投你反对票。为什么?让op决定,选择什么答案。我可以编辑我的答案,并写下这将在开始日期和结束日期之间的每一天创建一个对象结束日期,但这只是细节。@MatthewStory是的,我明白了。我不知道
日历。monthrange
,所以今天我学到了新东西。谢谢。我对你的答案投了赞成票:)小但可能重要的区别:你生成的是
datetime
对象,而不是
date
s。另外,虽然漂亮紧凑,
rrule()
在这种情况下速度较慢,因为它必须考虑更复杂的重复规则。出于可维护性原因,我个人更愿意在编写自己的代码之前重用现有维护良好的模块中的某些内容
>>> from dateutil import rrule, parser
>>> start = parser.parse('Jan 10 2017')
>>> end   = parser.parse('Mar 5 2018')
>>> list(rrule.rrule(rrule.MONTHLY, bymonthday=1, dtstart=start, until=end))
[datetime.datetime(2017, 2, 1, 0, 0), datetime.datetime(2017, 3, 1, 0, 0), datetime.datetime(2017, 4, 1, 0, 0), datetime.datetime(2017, 5, 1, 0, 0), datetime.datetime(2017, 6, 1, 0, 0), datetime.datetime(2017, 7, 1, 0, 0), datetime.datetime(2017, 8, 1, 0, 0), datetime.datetime(2017, 9, 1, 0, 0), datetime.datetime(2017, 10, 1, 0, 0), datetime.datetime(2017, 11, 1, 0, 0), datetime.datetime(2017, 12, 1, 0, 0), datetime.datetime(2018, 1, 1, 0, 0), datetime.datetime(2018, 2, 1, 0, 0), datetime.datetime(2018, 3, 1, 0, 0)]