1.1 --- a/vRecurrence.py Fri Dec 01 23:09:21 2017 +0100
1.2 +++ b/vRecurrence.py Sat Dec 02 20:15:20 2017 +0100
1.3 @@ -244,7 +244,7 @@
1.4
1.5 return values
1.6
1.7 -def order_qualifiers(qualifiers):
1.8 +def make_selectors(qualifiers):
1.9
1.10 """
1.11 Obtain 'qualifiers' in order of increasing resolution, producing and
1.12 @@ -283,6 +283,14 @@
1.13 else:
1.14 return Pattern(freq[qualifier], args, qualifier)
1.15
1.16 +def insert_start_selector(selectors, dt):
1.17 +
1.18 + "Return a copy of 'selectors' incorporating 'dt'."
1.19 +
1.20 + selectors = selectors + [StartSelector(0, {"start" : dt}, "DTSTART")]
1.21 + selectors.sort(key=selector_sort_key)
1.22 + return selectors
1.23 +
1.24 def sort_selectors(selectors):
1.25
1.26 "Sort 'selectors' in order of increasing resolution."
1.27 @@ -309,8 +317,19 @@
1.28 # Other BY... qualifiers sort earlier than selectors at the same resolution
1.29 # even though such things as "FREQ=HOURLY;BYHOUR=10" do not make much sense.
1.30
1.31 - return (selector.level, not selector.qualifier.startswith("BY") and 2 or
1.32 - selector.qualifier != "BYSETPOS" and 1 or 0)
1.33 + if not selector.qualifier.startswith("BY"):
1.34 + sublevel = 2
1.35 + elif selector.qualifier != "BYSETPOS":
1.36 + sublevel = 1
1.37 +
1.38 + # Make DTSTART sort later than COUNT.
1.39 +
1.40 + elif selector.qualifier == "DTSTART":
1.41 + sublevel = 1
1.42 + else:
1.43 + sublevel = 0
1.44 +
1.45 + return (selector.level, sublevel)
1.46
1.47 def get_value_ranges(qualifier):
1.48
1.49 @@ -1031,6 +1050,17 @@
1.50 def set_positions(self, positions):
1.51 self.args["values"] = positions
1.52
1.53 +class StartSelector(Selector):
1.54 +
1.55 + "A selector ensuring that the start occurrence is included."
1.56 +
1.57 + def materialise_items(self, context, start, end, inclusive=False):
1.58 + return StartIterator(self, context, start, end, inclusive,
1.59 + self.get_start())
1.60 +
1.61 + def get_start(self):
1.62 + return self.args["start"]
1.63 +
1.64 special_enum_levels = {
1.65 "BYDAY" : WeekDayFilter,
1.66 "BYMONTHDAY" : MonthDayFilter,
1.67 @@ -1373,6 +1403,39 @@
1.68 else:
1.69 raise
1.70
1.71 +class StartIterator(SelectorIterator):
1.72 +
1.73 + "An iterator ensuring that the start occurrence is included."
1.74 +
1.75 + def __init__(self, selector, current, start, end, inclusive, start_dt):
1.76 + SelectorIterator.__init__(self, selector, current, start, end, inclusive)
1.77 + self.waiting = start_dt
1.78 +
1.79 + def next(self):
1.80 +
1.81 + "Return the next value, initially the start period."
1.82 +
1.83 + while not self.at_limit():
1.84 + result = self.next_item(self.start, self.end)
1.85 +
1.86 + # Compare with any waiting value.
1.87 +
1.88 + if self.waiting:
1.89 +
1.90 + # Produce the waiting value, queue the latest result.
1.91 +
1.92 + if result != self.waiting:
1.93 + result, self.waiting = self.waiting, result
1.94 +
1.95 + # Remove the waiting value if identical to the latest result.
1.96 +
1.97 + else:
1.98 + self.waiting = None
1.99 +
1.100 + return result
1.101 +
1.102 + raise StopIteration
1.103 +
1.104 def connect_selectors(selectors):
1.105
1.106 """
1.107 @@ -1390,7 +1453,7 @@
1.108 # Allow selectors within the limit selector to act as if they are first
1.109 # in the chain and will operate using the supplied datetime context.
1.110
1.111 - first = isinstance(current, LimitSelector)
1.112 + first = isinstance(current, (LimitSelector, StartSelector))
1.113
1.114 current = selector
1.115 current.first = first
1.116 @@ -1418,6 +1481,7 @@
1.117 """
1.118
1.119 dt = to_tuple(dt)
1.120 + selectors = insert_start_selector(selectors, dt)
1.121 return connect_selectors(combine_datetime_with_selectors(dt, selectors))
1.122
1.123 def get_selectors_for_rule(rule):
1.124 @@ -1429,6 +1493,6 @@
1.125
1.126 if not isinstance(rule, tuple):
1.127 rule = (rule or "").split(";")
1.128 - return order_qualifiers(get_qualifiers(rule))
1.129 + return make_selectors(get_qualifiers(rule))
1.130
1.131 # vim: tabstop=4 expandtab shiftwidth=4