什么是最灵活的方式来生成两个日期的日期列表

时间:2011-12-23 07:57:02

标签: python

我想生成两个日期之间的日期列表(一周的第N天)。

例如,假设日期为“20111101”和“20111201”,假设我想生成月份日期的第3个星期三,我想生成日期:

["20111116", "20111221"]

所以我想写一个这样的函数:

def generateNthWeekdayDatesList(startdate, enddate, nthday=3, dayofweek="wed"):
    pass

实现此功能的最佳方式(即最pythonic)是什么?

4 个答案:

答案 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日期问题。

这是一个无限的系列。所以解决方案需要注意这一点。

  1. 不要使用字符串,请使用datetime.date,然后将其转换为字符串。
  2. 我会写这样的东西。

    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
            ''''
            ...
    

    您可能还想实现以前的方法。