-
Notifications
You must be signed in to change notification settings - Fork 0
/
recurrence.py
132 lines (88 loc) · 3.58 KB
/
recurrence.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
#!/usr/bin/python
import datetime
from date import DATE_FORMAT
from date import getPinnedDayOfNextMonth
from date import pinDayToMonth
def loadRecurrenceFromJsonObj(jsonObj):
if jsonObj["type"] == "daily":
startDate = datetime.datetime.strptime(jsonObj["start"], DATE_FORMAT).date()
dayStep = int(jsonObj["step"])
numOccurrences = None
if "occurrences" in jsonObj:
numOccurrences = int(jsonObj["occurrences"])
return DailyRecurrence(startDate, dayStep, numOccurrences)
elif jsonObj["type"] == "monthly":
dayOfMonth = int(jsonObj["day"])
return MonthlyRecurrence(dayOfMonth)
else:
raise RuntimeError("Unknown recurrence type: %s" % jsonObj["type"])
class Recurrence(object):
def __init__(self, name):
self.name = name
def getDatesInRange(self, rangeStartDate, rangeEndDate):
raise RuntimeError("Not Yet Implemented")
def isOnDate(self, date):
return len(self.getDatesInRange(date, date)) == 1
class DailyRecurrence(Recurrence):
def __init__(self, startDate, dayStep=1, numOccurrences=None):
super(DailyRecurrence, self).__init__("daily")
self.startDate = startDate
self.dayStep = dayStep
self.numOccurrences = numOccurrences
def getDatesInRange(self, rangeStartDate, rangeEndDate):
dates = list()
occurrences = 1
# Check if we don't start until after the range
if self.startDate > rangeEndDate:
return dates
# Figure out our first date within the range
currentDate = self.startDate
while currentDate < rangeStartDate and \
(not self.numOccurrences or occurrences <= self.numOccurrences):
currentDate += datetime.timedelta(self.dayStep)
occurrences += 1
# Add dates from now until the end of the date range
while currentDate <= rangeEndDate and \
(not self.numOccurrences or occurrences <= self.numOccurrences):
dates.append(currentDate)
currentDate += datetime.timedelta(self.dayStep)
occurrences += 1
return dates
class MonthlyRecurrence(Recurrence):
def __init__(self, dayOfMonth, startDate=None, numOccurrences=None):
super(MonthlyRecurrence, self).__init__("monthly")
if numOccurrences and not startDate:
raise RuntimeError("You can not specify a number of ocurrences without a start date.")
self.dayOfMonth = dayOfMonth
self.startDate = startDate
self.numOccurrences = numOccurrences
def getDatesInRange(self, rangeStartDate, rangeEndDate):
dates = list()
occurrences = 1
# Figure out which month to start in
if self.startDate:
# If we have a start date, start in the same month as the start date
srcDate = self.startDate
else:
# If there's no start date, then our recurrence has infinite timing. Start in
# the same month as our range
srcDate = rangeStartDate
year = srcDate.year
month = srcDate.month
day = pinDayToMonth(year, month, self.dayOfMonth)
currentDate = datetime.date(year, month, day)
# Either case above can end up with a currentDate with days before srcDate's
# days. In that case, we need to go one more month ahead to start.
if currentDate < srcDate:
currentDate = getPinnedDayOfNextMonth(currentDate.year, currentDate.month, self.dayOfMonth)
# Add dates from now until the end of the date range if we haven't
# reached our max number of occurrences and the date is after the start
# of the range.
while currentDate <= rangeEndDate and \
(not self.numOccurrences or occurrences <= self.numOccurrences):
# Check if we've reach the start of our range
if currentDate >= rangeStartDate:
dates.append(currentDate)
currentDate = getPinnedDayOfNextMonth(currentDate.year, currentDate.month, self.dayOfMonth)
occurrences += 1
return dates