我想生成两个日期之间的日期列表(一周的第N天)。
例如,假设日期为“20111101”和“20111201”,假设我想生成月份日期的第3个星期三,我想生成日期:
["20111116", "20111221"]
所以我想写一个这样的函数:
def generateNthWeekdayDatesList(startdate, enddate, nthday=3, dayofweek="wed"):
pass
实现此功能的最佳方式(即最pythonic)是什么?
答案 0 :(得分:1)
这是使用generator非常适合/ pythonic的情况。
def nth_day_of_month(start, end, nth, weekday):
assert start.day == 1, "start on the first day of a month"
assert nth > 0
assert 1 <= weekday <= 7
candidate = start
seen_in_month = 0
while candidate <= end:
if candidate.isoweekday() == weekday:
seen_in_month += 1
if seen_in_month == nth:
yield candidate
current_month = candidate.month
while candidate.month == current_month:
candidate += timedelta(1)
seen_in_month = 0
else:
if (candidate + timedelta(1)).month != candidate.month:
seen_in_month = 0
candidate += timedelta(1)
else:
if (candidate + timedelta(1)).month != candidate.month:
seen_in_month = 0
candidate += timedelta(1)
# third wednesday
print list(nth_day_of_month(date(2011, 1, 1), date(2012, 1, 1), nth=3, weekday=3))
# fifth sunday
print list(nth_day_of_month(date(2011, 1, 1), date(2012, 1, 1), nth=5, weekday=7))
# 9th monday
print list(nth_day_of_month(date(2011, 1, 1), date(2012, 1, 1), nth=9, weekday=1))
你甚至可以创建一个无限的发电机(一个永不停止的发电机):
def infinite_nth_day_of_month(start, nth, weekday):
assert start.day == 1, "start on the first day of a month"
assert nth > 0
assert 1 <= weekday <= 7
candidate = start
seen_in_month = 0
while True:
if candidate.isoweekday() == weekday:
seen_in_month += 1
if seen_in_month == nth:
yield candidate
current_month = candidate.month
while candidate.month == current_month:
candidate += timedelta(1)
seen_in_month = 0
else:
if (candidate + timedelta(1)).month != candidate.month:
seen_in_month = 0
candidate += timedelta(1)
else:
if (candidate + timedelta(1)).month != candidate.month:
seen_in_month = 0
candidate += timedelta(1)
# this will create an infinite list, not a good idea
# print list(infinite_nth_day_of_month(date(2011, 1, 1), 3, 3))
import itertools
date_generator = infinite_nth_day_of_month(date(2011, 1, 1), 3, 3)
# create a list the 10000 third wednesdays of 2011 and into the future
date_list = list(itertools.islice(date_generator, 10000))
>>> print date_list[0]
2011-01-19
>>> print date_list[-1]
2844-04-20
请参阅itertools的文档。
如果提供给它的参数创建一个永远不会产生任何东西的生成器(例如星期二第9个),那么无限生成器就没有用了。在这种情况下,它将无休止地搜索,直到它达到datetime.date
支持的最大日期并提升OverflowError: date value out of range
。
答案 1 :(得分:1)
您可以使用优秀的dateutil
包。
import dateutil.relativedelta as R
import dateutil.parser as P
from datetime import datetime
def generateNthWeekdayDatesList(startdate, enddate, nthday=3, dayofweek=R.WE):
s = P.parse(startdate).replace(day=1, minute=0, second=0, microsecond=0)
e = P.parse(enddate)
while s < e:
n = s + R.relativedelta(weekday=dayofweek(nthday))
if s <= n <= e:
yield n
s += R.relativedelta(months=1)
def main():
print list(generateNthWeekdayDatesList("20111116", "20111221"))
if __name__ == '__main__':
main()
运行它给了我:
$ python f.py
[datetime.datetime(2011, 11, 16, 0, 0), datetime.datetime(2011, 12, 21, 0, 0)]
答案 2 :(得分:1)
可以按周而不是几天进行迭代;它只需要一些逻辑,模7(不是6!)算术和测试用例来获得正确的起点。这笔初始投资的回报是一个干净清晰的小while
循环。如果有人关心,它也应该快得多。
import datetime
def nth_weekdays(startdate, enddate, n, isoweekday):
"""
Generate in ascending order all dates x
such that startdate <= x <= enddate
and x is the nth ISO weekday in its month.
"""
if not (1 <= n <= 5):
raise ValueError("n should be 1 to 5, not %r" % n)
if not (1 <= isoweekday <= 7): # Monday = 1
raise ValueError("isoweekday should be 1 to 7, not %r" % isoweekday)
# get day of week of the first day in the start month
dow1 = startdate.replace(day=1).isoweekday()
# get date which is the first Wday in the start month
daynum = (isoweekday - dow1 + 7) % 7 + 1
candidate = startdate.replace(day=daynum)
seen_in_month = 1
current_month = candidate.month
one_week = datetime.timedelta(days=7)
while candidate <= enddate:
if seen_in_month == n and candidate >= startdate:
yield candidate
candidate += one_week
if candidate.month == current_month:
seen_in_month += 1
else:
seen_in_month = 1
current_month = candidate.month
if __name__ == "__main__":
from pprint import pprint as pp
tests = """
2011-01-01 2012-01-01 3 3 # 3rd Wednesday
2011-06-14 2011-06-30 3 3 # 3rd Wednesday
2011-06-15 2011-06-30 3 3 # 3rd Wednesday
2011-06-16 2011-06-30 3 3 # 3rd Wednesday, no results
2011-01-01 2012-01-01 5 7 # 5th Sunday
# 2011-12-01 was a Thursday. Check 1st Mon Wed Thu Fri Sun
2011-12-01 2011-12-31 1 1
2011-12-01 2011-12-31 1 3
2011-12-01 2011-12-31 1 4
2011-12-01 2011-12-31 1 5
2011-12-01 2011-12-31 1 7
# 2011-08-01 was a Monday. Check 1st Mon Tue Sun
2011-08-01 2011-08-31 1 1
2011-08-01 2011-08-31 1 2
2011-08-01 2011-08-31 1 7
# 2011-05-01 was a Sunday. Check 1st Mon Sat Sun
2011-05-01 2011-05-31 1 1
2011-05-01 2011-05-31 1 6
2011-05-01 2011-05-31 1 7
# input errors
2011-01-01 2012-01-01 6 1 # 6th Monday
2011-01-01 2012-01-01 0 1 # 0th Monday
2011-01-01 2012-01-01 3 0 # 3rd ???day
2011-01-01 2012-01-01 3 8 # 3rd ???day
"""
dconv = lambda s: datetime.datetime.strptime(s, "%Y-%m-%d")
funcs = [dconv, dconv, int, int]
for test in tests.splitlines():
test = test.strip()
if not test: continue
print
print test
data = test.split("#")[0]
if not data: continue
args = [func(x) for func, x in zip(funcs, data.split())]
try:
pp([x.strftime("%Y-%m-%d") for x in nth_weekdays(*args)])
except BadArg, e:
print "%s: %s" % (e.__class__.__name__, e)
答案 3 :(得分:-1)
啊是的 - IMM日期问题。
这是一个无限的系列。所以解决方案需要注意这一点。
我会写这样的东西。
class IMMDate (object):
@staticmethod
def next (when):
'''
returns the next IMM date after when
if when is an IMM date it will return the next
''''
...
您可能还想实现以前的方法。