在Python中遍历一系列date
我有下面的代码来做到这一点,但我怎么能做得更好? 现在我认为它比嵌套循环要好,但是当你在列表理解中有一个生成器的时候,它开始得到一个Perl。
day_count = (end_date - start_date).days + 1 for single_date in [d for d in (start_date + timedelta(n) for n in range(day_count)) if d <= end_date]: print strftime("%Y-%m-%d", single_date.timetuple())
笔记
- 我实际上并没有使用它来打印。 这只是为了演示的目的。
-
start_date
和end_date
variables是datetime.date
对象,因为我不需要时间戳。 (他们将被用来生成一个报告)。
示例输出
2009-05-30
开始date和2009-06-09
结束date:
2009-05-30 2009-05-31 2009-06-01 2009-06-02 2009-06-03 2009-06-04 2009-06-05 2009-06-06 2009-06-07 2009-06-08 2009-06-09
为什么有两个嵌套迭代? 对我来说,它只产生一个相同的数据列表,只有一个迭代:
for single_date in (start_date + timedelta(n) for n in range(day_count)): print ...
没有列表被存储,只有一个生成器被迭代。 发电机中的“如果”似乎也是不必要的。
毕竟,线性序列应该只需要一个迭代器,而不是两个。
与John Machin讨论后更新:
也许最优雅的解决scheme是使用生成器函数来完全隐藏/抽象date范围内的迭代:
from datetime import timedelta, date def daterange(start_date, end_date): for n in range(int ((end_date - start_date).days)): yield start_date + timedelta(n) start_date = date(2013, 1, 1) end_date = date(2015, 6, 2) for single_date in daterange(start_date, end_date): print single_date.strftime("%Y-%m-%d")
注意:为了与内置的range()
函数保持一致,这个迭代在到达end_date
之前停止。 所以对于包含迭代使用第二天,就像使用range()
。
这可能更清楚:
d = start_date delta = datetime.timedelta(days=1) while d <= end_date: print d.strftime("%Y-%m-%d") d += delta
使用dateutil
库:
from datetime import date from dateutil.rrule import rrule, DAILY a = date(2009, 5, 30) b = date(2009, 6, 9) for dt in rrule(DAILY, dtstart=a, until=b): print dt.strftime("%Y-%m-%d")
这个python库有许多更高级的function,一些非常有用,比如relative delta
s,并且被实现为一个单独的文件(模块),很容易包含在一个项目中。
大pandas对于时间序列总的来说很棒,并且对date范围有直接的支持。
import pandas as pd daterange = pd.date_range(start_date, end_date)
然后,您可以遍历date范围来打印date:
for single_date in daterange: print (single_date.strftime("%Y-%m-%d"))
它也有很多选项让生活更轻松。 例如,如果您只需要工作日,则只需交换bdate_range即可。 请参阅http://pandas.pydata.org/pandas-docs/stable/timeseries.html#generating-ranges-of-timestamps
pandas的力量其实就是它的数据框架,它支持向量化的操作(非常像numpy),使得大量数据的操作非常快速和容易。
编辑:你也可以完全跳过for循环,直接打印,这是更容易,更高效:
print(daterange)
import datetime def daterange(start, stop, step=datetime.timedelta(days=1), inclusive=False): # inclusive=False to behave like range by default if step.days > 0: while start < stop: yield start start = start + step # not +=! don't modify object passed in if it's mutable # since this function is not restricted to # only types from datetime module elif step.days < 0: while start > stop: yield start start = start + step if inclusive and start == stop: yield start # ... for date in daterange(start_date, end_date, inclusive=True): print strftime("%Y-%m-%d", date.timetuple())
这个函数比你要求的要严格得多,支持负面的步骤等等。只要你把你的范围逻辑分解出来,那么你不需要单独的day_count
,最重要的是当你调用函数时代码变得更容易阅读多个地方。
为什么不尝试:
import datetime as dt start_date = dt.datetime(2012, 12,1) end_date = dt.datetime(2012, 12,5) total_days = (end_date - start_date).days + 1 #inclusive 5 days for day_number in range(total_days): current_date = (start_date + dt.timedelta(days = day_number)).date() print current_date
import datetime def daterange(start, stop, step_days=1): current = start step = datetime.timedelta(step_days) if step_days > 0: while current < stop: yield current current += step elif step_days < 0: while current > stop: yield current current += step else: raise ValueError("daterange() step_days argument must not be zero") if __name__ == "__main__": from pprint import pprint as pp lo = datetime.date(2008, 12, 27) hi = datetime.date(2009, 1, 5) pp(list(daterange(lo, hi))) pp(list(daterange(hi, lo, -1))) pp(list(daterange(lo, hi, 7))) pp(list(daterange(hi, lo, -7))) assert not list(daterange(lo, hi, -1)) assert not list(daterange(hi, lo)) assert not list(daterange(lo, hi, -7)) assert not list(daterange(hi, lo, 7))
Numpy的arange
函数可以应用于date:
import numpy as np from datetime import datetime, timedelta d0 = datetime(2009, 1,1) d1 = datetime(2010, 1,1) dt = timedelta(days = 1) dates = np.arange(d0, d1, dt).astype(datetime)
使用astype
将从numpy.datetime64
转换为datetime.datetime
对象的数组。
这是我能想到的最人性化的解决scheme。
import datetime def daterange(start, end, step=datetime.timedelta(1)): curr = start while curr < end: yield curr curr += step
for i in range(16): print datetime.date.today() + datetime.timedelta(days=i)
我有一个类似的问题,但我需要迭代每月而不是每天。
这是我的解决scheme
import calendar from datetime import datetime, timedelta def days_in_month(dt): return calendar.monthrange(dt.year, dt.month)[1] def monthly_range(dt_start, dt_end): forward = dt_end >= dt_start finish = False dt = dt_start while not finish: yield dt.date() if forward: days = days_in_month(dt) dt = dt + timedelta(days=days) finish = dt > dt_end else: _tmp_dt = dt.replace(day=1) - timedelta(days=1) dt = (_tmp_dt.replace(day=dt.day)) finish = dt < dt_end
示例#1
date_start = datetime(2016, 6, 1) date_end = datetime(2017, 1, 1) for p in monthly_range(date_start, date_end): print(p)
产量
2016-06-01 2016-07-01 2016-08-01 2016-09-01 2016-10-01 2016-11-01 2016-12-01 2017-01-01
例#2
date_start = datetime(2017, 1, 1) date_end = datetime(2016, 6, 1) for p in monthly_range(date_start, date_end): print(p)
产量
2017-01-01 2016-12-01 2016-11-01 2016-10-01 2016-09-01 2016-08-01 2016-07-01 2016-06-01
如何做一个范围增加几天:
for d in map( lambda x: startDate+datetime.timedelta(days=x), xrange( (stopDate-startDate).days ) ): # Do stuff here
- startDate和stopDate是datetime.date对象
对于通用版本:
for d in map( lambda x: startTime+x*stepTime, xrange( (stopTime-startTime).total_seconds() / stepTime.total_seconds() ) ): # Do stuff here
- startTime和stopTime是datetime.date或datetime.datetime对象(两者应该是相同的types)
- stepTime是一个timedelta对象
请注意.total_seconds()仅在python 2.7之后才支持。如果您遇到较早的版本,您可以编写自己的函数:
def total_seconds( td ): return float(td.microseconds + (td.seconds + td.days * 24 * 3600) * 10**6) / 10**6
这个function有一些额外的function:
- 可以传递匹配DATE_FORMAT的string作为开始或结束,并将其转换为date对象
- 可以传递一个date对象的开始或结束
-
如果结束比起始更早,则检查错误
import datetime from datetime import timedelta DATE_FORMAT = '%Y/%m/%d' def daterange(start, end): def convert(date): try: date = datetime.datetime.strptime(date, DATE_FORMAT) return date.date() except TypeError: return date def get_date(n): return datetime.datetime.strftime(convert(start) + timedelta(days=n), DATE_FORMAT) days = (convert(end) - convert(start)).days if days <= 0: raise ValueError('The start date must be before the end date.') for n in range(0, days): yield get_date(n) start = '2014/12/1' end = '2014/12/31' print list(daterange(start, end)) start_ = datetime.date.today() end = '2015/12/1' print list(daterange(start, end))
显示从今天起的最后n天:
import datetime for i in range(0, 100): print (datetime.date.today() + datetime.timedelta(i)).isoformat()
输出:
2016-06-29 2016-06-30 2016-07-01 2016-07-02 2016-07-03 2016-07-04
这里是一个通用date范围函数的代码,类似于Ber的答案,但更灵活:
def count_timedelta(delta, step, seconds_in_interval): """Helper function for iterate. Finds the number of intervals in the timedelta.""" return int(delta.total_seconds() / (seconds_in_interval * step)) def range_dt(start, end, step=1, interval='day'): """Iterate over datetimes or dates, similar to builtin range.""" intervals = functools.partial(count_timedelta, (end - start), step) if interval == 'week': for i in range(intervals(3600 * 24 * 7)): yield start + datetime.timedelta(weeks=i) * step elif interval == 'day': for i in range(intervals(3600 * 24)): yield start + datetime.timedelta(days=i) * step elif interval == 'hour': for i in range(intervals(3600)): yield start + datetime.timedelta(hours=i) * step elif interval == 'minute': for i in range(intervals(60)): yield start + datetime.timedelta(minutes=i) * step elif interval == 'second': for i in range(intervals(1)): yield start + datetime.timedelta(seconds=i) * step elif interval == 'millisecond': for i in range(intervals(1 / 1000)): yield start + datetime.timedelta(milliseconds=i) * step elif interval == 'microsecond': for i in range(intervals(1e-6)): yield start + datetime.timedelta(microseconds=i) * step else: raise AttributeError("Interval must be 'week', 'day', 'hour' 'second', \ 'microsecond' or 'millisecond'.")