Index: recur.py =================================================================== --- recur.py (revision 100) +++ recur.py (revision 101) @@ -341,4 +341,51 @@ startDate += datetime.timedelta(1) +def eachweekday(startDate, weekday, timeofday=None, endDate=None): + """Yield the same time-of-day each week for the given day. The time-of-day + defaults to midnight. + + Yielded values are datetime.datetime objects. + For example: + eachweekday(datetime.date(2006, 8, 10), 3, datetime.time(14, 3, 0)) + yields the sequence: 2006-08-11 14:03:00, 2006-08-18 14:03:00, + 2006-08-25 14:03:00, ... + + timeofday may be a datetime.time, as in the above example, or it + may be a string, of the form "hour:min:sec". Seconds and minutes + may be omitted if their colon ":" separator is also omitted. So + the example above could be rewritten: + eachday(datetime.date(2004, 5, 4), "14:03") + """ + if timeofday is None: + timeofday = datetime.time(0) + elif isinstance(timeofday, (str, unicode)): + timeofday = time_from_str(timeofday) + + # get the given start time or datetime.time(0,0) + startTime = getattr(startDate, 'time', datetime.time)() + + if startDate.weekday() > weekday or startTime > timeofday: + offset = (7 + weekday) - startDate.weekday() + while offset > 6: + offset -= 7 + while offset <= 0: + offset += 7 + startDate += datetime.timedelta(offset) + + startDate = datetime.datetime.combine(startDate, timeofday) + + # Now that we've coerced our startDate to a datetime, we need to + # do the same thing to endDate so we can compare them. + if endDate and not hasattr(endDate, "time"): + endDate = datetime.datetime.combine(endDate, timeofday) + + end = getattr(endDate, 'date', lambda: None)() + day_iter = eachweek(startDate.date(), weekday, end) + startDate = datetime.datetime.combine(day_iter.next(), timeofday) + + while (endDate is None) or (startDate <= endDate): + yield startDate + startDate = datetime.datetime.combine(day_iter.next(), timeofday) + def days(startDate, frequency=1, endDate=None): """Yield a sequence of dates, adding 'frequency' days each time. @@ -584,6 +631,15 @@ # \S is any non-whitespace character. eachday: r"([\S]+) (?:every|each) day", - eachweek: [r"mon(?:days?)?", r"tues?(?:days?)?", - r"wed(?:nesdays?)?", r"thurs?(?:days?)?", + # $ in first regex below so as not to match "month" + eachweekday: [r"([\S]+) (?:every|each) mon(?:day)?$", + r"([\S]+) (?:every|each) tue(?:s?)?(?:day$)?$", + r"([\S]+) (?:every|each) wed(?:nesday)?", + r"([\S]+) (?:every|each) thu(?:rs?)?(?:day)?$", + r"([\S]+) (?:every|each) fri(?:day)?", + r"([\S]+) (?:every|each) sat(?:urday)?", + r"([\S]+) (?:every|each) sun(?:day)?", + ], + eachweek: [r"mon(?:days?)?", r"tue(?:s?)s?(?:days)?$", + r"wed(?:nesdays?)?", r"thu(?:rs?)?(?:days)?$", r"fri(?:days?)?", r"sat(?:urdays?)?", r"sun(?:days?)?", Index: test_recur.py =================================================================== --- test_recur.py (revision 100) +++ test_recur.py (revision 101) @@ -185,4 +185,26 @@ ) + def test_eachweekday(self): + dates = recur.eachweekday(dtd(1999, 12, 28), 0, dtt(6, 43, 21), + dtd(2000, 1, 30)) + self.assertEqual([x for x in dates], + [dtdt(2000, 1, 3, 6, 43, 21), + dtdt(2000, 1, 10, 6, 43, 21), + dtdt(2000, 1, 17, 6, 43, 21), + dtdt(2000, 1, 24, 6, 43, 21), + ] + ) + # Test start and end dates that have time components. + # If 'inside' the given time, those extrema should + # not be included. + dates = recur.eachweekday(dtdt(2000, 1, 3, 8), 0, dtt(6, 43, 21), + dtdt(2000, 1, 31, 2)) + self.assertEqual([x for x in dates], + [dtdt(2000, 1, 10, 6, 43, 21), + dtdt(2000, 1, 17, 6, 43, 21), + dtdt(2000, 1, 24, 6, 43, 21), + ] + ) + def test_eachweek(self): knowndates = [dtd(1999, 12, 31), @@ -321,4 +343,14 @@ ] dates = recur.Recurrence(dtd(600, 1, 1), "6:35 every day", dtd(600, 1, 5)) + self.assertEqual([x for x in dates], knowndates) + + # Test eachweekday + knowndates = [dtdt(1999, 12, 28, 6, 35), + dtdt(2000, 1, 4, 6, 35), + dtdt(2000, 1, 11, 6, 35), + dtdt(2000, 1, 18, 6, 35), + dtdt(2000, 1, 25, 6, 35), + ] + dates = recur.Recurrence(dtd(1999, 12, 28), "6:35 every tuesday", dtd(2000, 1, 30)) self.assertEqual([x for x in dates], knowndates)