1.1 --- a/tests/qualifiers.py Mon Oct 06 16:19:16 2014 +0200
1.2 +++ b/tests/qualifiers.py Wed Oct 08 18:18:11 2014 +0200
1.3 @@ -711,4 +711,46 @@
1.4 print l[-1] == (2004, 11, 2, 9, 0, 0), (2004, 11, 2, 9, 0, 0), l[-1]
1.5 print
1.6
1.7 +qualifiers = [
1.8 + ("MONTHLY", {"interval" : 1}),
1.9 + ("BYDAY", {"values" : [(2, None), (3, None), (4, None)]})
1.10 + ]
1.11 +
1.12 +l = order_qualifiers(qualifiers)
1.13 +show(l)
1.14 +dt = (1997, 9, 4, 9, 0, 0)
1.15 +l = get_datetime_structure(dt)
1.16 +show(l)
1.17 +l = combine_datetime_with_qualifiers(dt, qualifiers)
1.18 +show(l)
1.19 +
1.20 +s = process(l)
1.21 +l = s.materialise(dt, (1997, 12, 24, 0, 0, 0), 3, [3])
1.22 +print len(l) == 3, 3, len(l)
1.23 +print l[0] == (1997, 9, 4, 9, 0, 0), (1997, 9, 4, 9, 0, 0), l[0]
1.24 +print l[-1] == (1997, 11, 6, 9, 0, 0), (1997, 11, 6, 9, 0, 0), l[-1]
1.25 +print
1.26 +
1.27 +l2 = l
1.28 +
1.29 +qualifiers = [
1.30 + ("MONTHLY", {"interval" : 1}),
1.31 + ("BYDAY", {"values" : [(1, None), (2, None), (3, None), (4, None), (5, None)]})
1.32 + ]
1.33 +
1.34 +l = order_qualifiers(qualifiers)
1.35 +show(l)
1.36 +dt = (1997, 9, 29, 9, 0, 0)
1.37 +l = get_datetime_structure(dt)
1.38 +show(l)
1.39 +l = combine_datetime_with_qualifiers(dt, qualifiers)
1.40 +show(l)
1.41 +
1.42 +s = process(l)
1.43 +l = s.materialise(dt, (1998, 4, 1, 0, 0, 0), None, [-2])
1.44 +print len(l) == 7, 7, len(l)
1.45 +print l[0] == (1997, 9, 29, 9, 0, 0), (1997, 9, 29, 9, 0, 0), l[0]
1.46 +print l[-1] == (1998, 3, 30, 9, 0, 0), (1998, 3, 30, 9, 0, 0), l[-1]
1.47 +print
1.48 +
1.49 # vim: tabstop=4 expandtab shiftwidth=4
2.1 --- a/vRecurrence.py Mon Oct 06 16:19:16 2014 +0200
2.2 +++ b/vRecurrence.py Wed Oct 08 18:18:11 2014 +0200
2.3 @@ -176,7 +176,7 @@
2.4 else:
2.5 if not have_q:
2.6 if isinstance(from_q, Enum) and level > 0:
2.7 - repeat = Pattern(level - 1, {"interval" : 1}, "REPEAT")
2.8 + repeat = Pattern(level - 1, {"interval" : 1}, None)
2.9 repeat.context = tuple(context)
2.10 l.append(repeat)
2.11 have_q = True
2.12 @@ -198,7 +198,7 @@
2.13 while from_q:
2.14 if not have_q:
2.15 if isinstance(from_q, Enum) and level > 0:
2.16 - repeat = Pattern(level - 1, {"interval" : 1}, "REPEAT")
2.17 + repeat = Pattern(level - 1, {"interval" : 1}, None)
2.18 repeat.context = tuple(context)
2.19 l.append(repeat)
2.20 have_q = True
2.21 @@ -293,24 +293,44 @@
2.22 def __repr__(self):
2.23 return "%s(%r, %r, %r, %r)" % (self.__class__.__name__, self.level, self.args, self.qualifier, self.context)
2.24
2.25 - def materialise(self, start, end, count=None):
2.26 + def materialise(self, start, end, count=None, setpos=None):
2.27 counter = count and [0, count]
2.28 - results = self.materialise_items(self.context, start, end, counter)
2.29 + results = self.materialise_items(self.context, start, end, counter, setpos)
2.30 results.sort()
2.31 return results[:count]
2.32
2.33 - def materialise_item(self, current, last, next, counter):
2.34 - if counter is None or counter[0] < counter[1]:
2.35 - if self.selecting:
2.36 - return self.selecting.materialise_items(current, last, next, counter)
2.37 - elif last <= current:
2.38 - if counter is not None:
2.39 - counter[0] += 1
2.40 - return [current]
2.41 - return []
2.42 + def materialise_item(self, current, last, next, counter, setpos=None):
2.43 + if self.selecting:
2.44 + return self.selecting.materialise_items(current, last, next, counter, setpos)
2.45 + elif last <= current:
2.46 + return [current]
2.47 + else:
2.48 + return []
2.49 +
2.50 + def convert_positions(self, setpos):
2.51 + l = []
2.52 + for pos in setpos:
2.53 + lower = pos < 0 and pos or pos - 1
2.54 + upper = pos > 0 and pos or pos < -1 and pos + 1 or None
2.55 + l.append((lower, upper))
2.56 + return l
2.57 +
2.58 + def select_positions(self, results, setpos):
2.59 + results.sort()
2.60 + l = []
2.61 + for lower, upper in self.convert_positions(setpos):
2.62 + l += results[lower:upper]
2.63 + return l
2.64 +
2.65 + def filter_by_period(self, results, start, end):
2.66 + l = []
2.67 + for result in results:
2.68 + if start <= result < end:
2.69 + l.append(result)
2.70 + return l
2.71
2.72 class Pattern(Selector):
2.73 - def materialise_items(self, context, start, end, counter):
2.74 + def materialise_items(self, context, start, end, counter, setpos=None):
2.75 first = scale(self.context[self.pos], self.pos)
2.76
2.77 # Define the step between items.
2.78 @@ -329,13 +349,16 @@
2.79 while current < end and (counter is None or counter[0] < counter[1]):
2.80 next = update(current, step)
2.81 current_end = update(current, unit_step)
2.82 - results += self.materialise_item(current, max(current, start), min(current_end, end), counter)
2.83 + interval_results = self.materialise_item(current, max(current, start), min(current_end, end), counter, setpos)
2.84 + if counter is not None:
2.85 + counter[0] += len(interval_results)
2.86 + results += interval_results
2.87 current = next
2.88
2.89 return results
2.90
2.91 class WeekDayFilter(Selector):
2.92 - def materialise_items(self, context, start, end, counter):
2.93 + def materialise_items(self, context, start, end, counter, setpos=None):
2.94 step = scale(1, 2)
2.95 results = []
2.96
2.97 @@ -358,12 +381,16 @@
2.98 current = context
2.99 values = [value for (value, index) in self.args["values"]]
2.100
2.101 - while current < end and (counter is None or counter[0] < counter[1]):
2.102 + while current < end:
2.103 next = update(current, step)
2.104 if date(*current).isoweekday() in values:
2.105 results += self.materialise_item(current, max(current, start), min(next, end), counter)
2.106 current = next
2.107 - return results
2.108 +
2.109 + if setpos:
2.110 + return self.select_positions(results, setpos)
2.111 + else:
2.112 + return results
2.113
2.114 # Find each of the given days.
2.115
2.116 @@ -376,9 +403,12 @@
2.117 else:
2.118 current = to_tuple(get_first_day(first_day, value) + offset, 3)
2.119
2.120 - if current < end:
2.121 - next = update(current, step)
2.122 - results += self.materialise_item(current, max(current, start), min(next, end), counter)
2.123 + next = update(current, step)
2.124 +
2.125 + # To support setpos, only current and next bound the search, not
2.126 + # the period in addition.
2.127 +
2.128 + results += self.materialise_item(current, current, next, counter)
2.129
2.130 else:
2.131 if index < 0:
2.132 @@ -389,26 +419,43 @@
2.133 direction = operator.add
2.134
2.135 while first_day <= current <= last_day:
2.136 - if current < end:
2.137 - next = update(current, step)
2.138 - results += self.materialise_item(current, max(current, start), min(next, end), counter)
2.139 + next = update(current, step)
2.140 +
2.141 + # To support setpos, only current and next bound the search, not
2.142 + # the period in addition.
2.143 +
2.144 + results += self.materialise_item(current, current, next, counter)
2.145 current = to_tuple(direction(date(*current), timedelta(7)), 3)
2.146
2.147 - return results
2.148 + # Extract selected positions and remove out-of-period instances.
2.149 +
2.150 + if setpos:
2.151 + results = self.select_positions(results, setpos)
2.152 +
2.153 + return self.filter_by_period(results, start, end)
2.154
2.155 class Enum(Selector):
2.156 - def materialise_items(self, context, start, end, counter):
2.157 + def materialise_items(self, context, start, end, counter, setpos=None):
2.158 step = scale(1, self.pos)
2.159 results = []
2.160 for value in self.args["values"]:
2.161 current = combine(context, scale(value, self.pos))
2.162 - if current < end:
2.163 - next = update(current, step)
2.164 - results += self.materialise_item(current, max(current, start), min(next, end), counter)
2.165 - return results
2.166 + next = update(current, step)
2.167 +
2.168 + # To support setpos, only current and next bound the search, not
2.169 + # the period in addition.
2.170 +
2.171 + results += self.materialise_item(current, current, next, counter, setpos)
2.172 +
2.173 + # Extract selected positions and remove out-of-period instances.
2.174 +
2.175 + if setpos:
2.176 + results = self.select_positions(results, setpos)
2.177 +
2.178 + return self.filter_by_period(results, start, end)
2.179
2.180 class MonthDayFilter(Enum):
2.181 - def materialise_items(self, context, start, end, counter):
2.182 + def materialise_items(self, context, start, end, counter, setpos=None):
2.183 last_day = monthrange(context[0], context[1])[1]
2.184 step = scale(1, self.pos)
2.185 results = []
2.186 @@ -416,13 +463,22 @@
2.187 if value < 0:
2.188 value = last_day + 1 + value
2.189 current = combine(context, scale(value, self.pos))
2.190 - if current < end:
2.191 - next = update(current, step)
2.192 - results += self.materialise_item(current, max(current, start), min(next, end), counter)
2.193 - return results
2.194 + next = update(current, step)
2.195 +
2.196 + # To support setpos, only current and next bound the search, not
2.197 + # the period in addition.
2.198 +
2.199 + results += self.materialise_item(current, current, next, counter)
2.200 +
2.201 + # Extract selected positions and remove out-of-period instances.
2.202 +
2.203 + if setpos:
2.204 + results = self.select_positions(results, setpos)
2.205 +
2.206 + return self.filter_by_period(results, start, end)
2.207
2.208 class YearDayFilter(Enum):
2.209 - def materialise_items(self, context, start, end, counter):
2.210 + def materialise_items(self, context, start, end, counter, setpos=None):
2.211 first_day = date(context[0], 1, 1)
2.212 next_first_day = date(context[0] + 1, 1, 1)
2.213 year_length = (next_first_day - first_day).days
2.214 @@ -432,10 +488,19 @@
2.215 if value < 0:
2.216 value = year_length + 1 + value
2.217 current = to_tuple(first_day + timedelta(value - 1), 3)
2.218 - if current < end:
2.219 - next = update(current, step)
2.220 - results += self.materialise_item(current, max(current, start), min(next, end), counter)
2.221 - return results
2.222 + next = update(current, step)
2.223 +
2.224 + # To support setpos, only current and next bound the search, not
2.225 + # the period in addition.
2.226 +
2.227 + results += self.materialise_item(current, current, next, counter)
2.228 +
2.229 + # Extract selected positions and remove out-of-period instances.
2.230 +
2.231 + if setpos:
2.232 + results = self.select_positions(results, setpos)
2.233 +
2.234 + return self.filter_by_period(results, start, end)
2.235
2.236 def process(selectors):
2.237 current = selectors[0]