生成在时间间隔内的日期时间列表

56
给定两个日期时间(start_dateend_date),我想生成一个列表,其中包含这两个日期之间的其他日期时间,新日期时间之间由可变间隔分隔。例如,在2011年10月10日和2011年12月12日之间每4天一次,或者在现在和明天19点之间每8小时一次。

也许与Dateperiod PHP类大致相当。

在Python中完成这个任务的最有效方法是什么?

5个回答

91

使用datetime.timedelta

from datetime import date, datetime, timedelta

def perdelta(start, end, delta):
    curr = start
    while curr < end:
        yield curr
        curr += delta

>>> for result in perdelta(date(2011, 10, 10), date(2011, 12, 12), timedelta(days=4)):
...     print result
...
2011-10-10
2011-10-14
2011-10-18
2011-10-22
2011-10-26
2011-10-30
2011-11-03
2011-11-07
2011-11-11
2011-11-15
2011-11-19
2011-11-23
2011-11-27
2011-12-01
2011-12-05
2011-12-09

这适用于日期和日期时间对象。您的第二个示例:

>>> for result in perdelta(datetime.now(),
...         datetime.now().replace(hour=19) + timedelta(days=1),
...         timedelta(hours=8)):
...     print result
... 
2012-05-21 17:25:47.668022
2012-05-22 01:25:47.668022
2012-05-22 09:25:47.668022
2012-05-22 17:25:47.668022

2
Python应该允许在range中使用日期,这是很有意义的。它之所以是弱类型语言,也是有原因的... - Tyler Crompton
4
“显式优于隐式。”在以下范围内应该增加哪个单位:天数,秒数,微秒数,毫秒数,分钟数,小时数或者周数? - Noctis Skytower
3
步长值将是一个“timedelta”对象。 - Tyler Crompton
1
使用日期对象时需要什么? - Tyler Crompton
3
那个生成器是我今天看到的最漂亮的 Python 代码。谢谢! - beruic
1
请注意,如果您的时间差的单位为小时或更小,则开始和结束时间必须是datetimes。使用日期会导致无限循环。 - Noumenon

19

试试这个:

from datetime import datetime
from dateutil.relativedelta import relativedelta

def date_range(start_date, end_date, increment, period):
    result = []
    nxt = start_date
    delta = relativedelta(**{period:increment})
    while nxt <= end_date:
        result.append(nxt)
        nxt += delta
    return result

问题中的示例,“从现在开始到明天晚上7点之间每8小时一次”可以写成:

start_date = datetime.now()
end_date = start_date + relativedelta(days=1)
end_date = end_date.replace(hour=19, minute=0, second=0, microsecond=0)
date_range(start_date, end_date, 8, 'hours')    
注意,period 的有效值是指相对信息 relativedelta 中定义的那些,即:'years'、'months'、'weeks'、'days'、'hours'、'minutes'、'seconds'、'microseconds'
我的解决方案返回一个 列表,正如问题所要求的。如果您不需要一次获取所有元素,可以像 @MartijnPieters 的答案中那样使用生成器。

10

我非常喜欢@Martijn Pieters和@Óscar López的两个答案。让我建议将这两个答案结合起来得到我的解决方案。

from datetime import date, datetime, timedelta

def datetime_range(start, end, delta):
    current = start
    if not isinstance(delta, timedelta):
        delta = timedelta(**delta)
    while current < end:
        yield current
        current += delta


start = datetime(2015,1,1)
end = datetime(2015,1,31)

#this unlocks the following interface:
for dt in datetime_range(start, end, {'days': 2, 'hours':12}):
    print dt
    print dt

2015-01-01 00:00:00
2015-01-03 12:00:00
2015-01-06 00:00:00
2015-01-08 12:00:00
2015-01-11 00:00:00
2015-01-13 12:00:00
2015-01-16 00:00:00
2015-01-18 12:00:00
2015-01-21 00:00:00
2015-01-23 12:00:00
2015-01-26 00:00:00
2015-01-28 12:00:00

2
这里提供的解决方案适用于天、小时等时间间隔,使用 timedelta 或任何 dateutil.relativedelta 支持的内容(如果您想依赖第三方库)。但是我想分享一下我的解决方案,针对特定情况下的月度间隔,格式为yyyymm,在此处提问:here (但被标记为重复问题)。
def iterate_months(start_ym, end_ym):
    for ym in range(int(start_ym), int(end_ym) + 1):
        if ym % 100 > 12 or ym % 100 == 0:
            continue
        yield str(ym)

list(iterate_months('201710', '201803'))

输出:

['201710', '201711', '201712', '201801', '201802', '201803']

这个解决方案相当特定于需要yyyymm格式(至少在我的世界中经常出现),并且可能不是最有效的答案,因为有大量continue,但它具有简洁易懂的优点,并且不涉及许多库或日期转换代码。


0

到目前为止,所有给出的解决方案都特定于起始<停止的情况,但是您可以轻松地使用操作符模块将它们调整为处理停止<开始的情况,如下面代码所示,该代码改编自@MartijnPieters的答案。

import datetime
import operator

def time_range(start: datetime.datetime, stop, step: datetime.timedelta):
    "Imitate range function for datetimes instead of ints."
    sec = step.total_seconds()
    if sec == 0:
        raise ValueError("step must not be 0 seconds")
    if sec < 0:
        compare = operator.gt
    else:
        compare = operator.lt
    x = start
    while compare(x, stop):
        yield x
        x += step  # immutable

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