1.1 --- a/tests/internal/qualifiers.py Sat Dec 16 21:50:17 2017 +0100
1.2 +++ b/tests/internal/qualifiers.py Sat Dec 16 21:52:11 2017 +0100
1.3 @@ -102,7 +102,7 @@
1.4 dt = (1997, 9, 2, 9, 0, 0)
1.5 s = select(dt, qualifiers)
1.6
1.7 - l = s.materialise(dt, (1997, 12, 24, 0, 0, 0), True)
1.8 + l = s.materialise(dt, (1997, 12, 25, 0, 0, 0), True)
1.9 print len(l) == 113, 113, len(l)
1.10 print l[0] == (1997, 9, 2, 9, 0, 0), (1997, 9, 2, 9, 0, 0), l[0]
1.11 print l[-1] == (1997, 12, 23, 9, 0, 0), (1997, 12, 23, 9, 0, 0), l[-1]
2.1 --- a/vRecurrence.py Sat Dec 16 21:50:17 2017 +0100
2.2 +++ b/vRecurrence.py Sat Dec 16 21:52:11 2017 +0100
2.3 @@ -87,7 +87,7 @@
2.4
2.5 # Add labels corresponding to negative indexes.
2.6
2.7 -level_labels += ("COUNT", "DTSTART")
2.8 +level_labels += ("COUNT", "UNTIL", "DTSTART")
2.9
2.10 # Symbols corresponding to resolution levels.
2.11
2.12 @@ -99,7 +99,7 @@
2.13
2.14 # Special levels used by non-qualifier-originating selectors.
2.15
2.16 -COUNT, DTSTART, BYSETPOS = -2, -1, None
2.17 +COUNT, UNTIL, DTSTART, BYSETPOS = -3, -2, -1, None
2.18
2.19 # Levels defining days.
2.20
2.21 @@ -207,6 +207,30 @@
2.22
2.23 qualifier = (key, {"values" : values})
2.24
2.25 + # Accept until datetimes.
2.26 + # NOTE: This should be a UTC datetime unless DTSTART is a date or
2.27 + # NOTE: floating datetime.
2.28 +
2.29 + elif key == "UNTIL":
2.30 + try:
2.31 + # YYYYMMDD
2.32 +
2.33 + if len(value) == 8:
2.34 + end = map(int, (value[:4], value[4:6], value[6:]))
2.35 +
2.36 + # YYYYMMDDTHHMMSS[Z]
2.37 +
2.38 + elif len(value) in (15, 16):
2.39 + end = map(int, (value[:4], value[4:6], value[6:8], value[9:11], value[11:13], value[13:15]))
2.40 +
2.41 + else:
2.42 + continue
2.43 +
2.44 + qualifier = (key, {"end" : tuple(end)})
2.45 +
2.46 + except ValueError:
2.47 + continue
2.48 +
2.49 # Ignore other items.
2.50
2.51 else:
2.52 @@ -308,6 +332,9 @@
2.53 elif qualifier == "COUNT":
2.54 return LimitSelector(COUNT, args, "COUNT")
2.55
2.56 + elif qualifier == "UNTIL":
2.57 + return UntilSelector(UNTIL, args, "UNTIL")
2.58 +
2.59 else:
2.60 return Pattern(freq[qualifier], args, qualifier)
2.61
2.62 @@ -1154,6 +1181,17 @@
2.63 def get_start(self):
2.64 return self.args["start"]
2.65
2.66 +class UntilSelector(Selector):
2.67 +
2.68 + "A selector ensuring that the until datetime is not passed."
2.69 +
2.70 + def materialise_items(self, context, start, end, inclusive=False):
2.71 + return UntilIterator(self, context, start, end, inclusive,
2.72 + self.get_end())
2.73 +
2.74 + def get_end(self):
2.75 + return self.args["end"]
2.76 +
2.77 special_enum_levels = {
2.78 "BYDAY" : WeekDayFilter,
2.79 "BYMONTHDAY" : MonthDayFilter,
2.80 @@ -1548,6 +1586,26 @@
2.81
2.82 return result
2.83
2.84 +class UntilIterator(SelectorIterator):
2.85 +
2.86 + "An iterator ensuring that the until datetime is not passed."
2.87 +
2.88 + def __init__(self, selector, current, start, end, inclusive, until):
2.89 + SelectorIterator.__init__(self, selector, current, start, end, inclusive)
2.90 + self.until = until
2.91 +
2.92 + def next(self):
2.93 +
2.94 + "Return the next value, stopping if it is beyond the until limit."
2.95 +
2.96 + while True:
2.97 + current = self.next_item(self.start, self.end)
2.98 + if current >= self.until:
2.99 + break
2.100 + return current
2.101 +
2.102 + raise StopIteration
2.103 +
2.104 def connect_selectors(selectors):
2.105
2.106 """