# HG changeset patch # User Paul Boddie # Date 1373839200 -7200 # Node ID deb6bdca9d1b242bd1c7b9ec5b6d2d3724313ee8 # Parent 9fc4e33b0b611865de674a5dd5b55091cf4da77b Changed the specific recurrence grammar so that concrete datetimes are not qualified by other specific recurrences. Imposed resolution restrictions on the limits of repeating recurrences. diff -r 9fc4e33b0b61 -r deb6bdca9d1b RecurrenceSupport.py --- a/RecurrenceSupport.py Sun Jul 14 21:11:12 2013 +0200 +++ b/RecurrenceSupport.py Mon Jul 15 00:00:00 2013 +0200 @@ -11,19 +11,23 @@ recurrence = | - specific-recurrence = ( ( the ) | ) + specific-recurrence = ( the | ) [ in ] + | repeating-recurrence = every [ ] [ from ] [ until ] - concrete-datetime = | | + concrete-datetime = | Constraints: repeating-recurrence: if is not "single": from and/or until must be specified + + from and/or until resolutions + must be compatible with the repeating-recurrence """ from DateSupport import weekday_labels, weekday_labels_verbose, month_labels, \ @@ -148,6 +152,12 @@ else: return t + def have_one_of(self, tokens): + if self.end: + return False + t = self.tokens[-1] + return t in tokens + def _next(self): t = self.iterator.next() self.tokens.append(t) @@ -203,9 +213,11 @@ self.until_datetime and ", until_datetime=%r" % self.until_datetime or "", self.qualified_by and ", qualified_by=%r" % self.qualified_by or "") - def select(self): - - "Select occurrences using this object's criteria." + def get_resolution(self): + if self.qualified_by: + return self.qualified_by.get_resolution() + else: + return self.interval_level # Parsing functions. @@ -237,7 +249,8 @@ """ Using the incoming 'words' and given the 'current' selector, parse a recurrence that can be either a repeating recurrence (starting with "every") - or a specific recurrence (starting with "the"). + or a specific recurrence (starting with "the") or a concrete datetime + reference. The active selector is returned as a result of parsing the recurrence. """ @@ -247,10 +260,8 @@ parseRepeatingRecurrence(words, current) words.want() return current - elif words.have("the"): + else: return parseSpecificRecurrence(words, current) - else: - return parseConcreteDateTime(words, current) def parseConcreteDateTime(words, current): @@ -284,13 +295,6 @@ words.want() return current - # Detect month labels. - - elif word in month_labels: - current.add_datetime("month", intervals["month"], word) - words.want() - return current - raise ParseError, words.tokens def parseSpecificRecurrence(words, current): @@ -300,32 +304,45 @@ return a specific recurrence. """ + word = words.have() + + # Detect month labels. + + if word in month_labels: + current.add_details("the", word, month_labels.index(word) + 1, "month", intervals["month"]) + # Handle the qualifier and interval. - qualifier = words.next() - qualifier_level = isSpecificQualifier(qualifier) - if not qualifier_level: - raise ParseError, words.tokens - interval = words.next() - interval_level = intervals.get(interval) - if not interval_level: - raise ParseError, words.tokens + elif word == "the": + qualifier = words.next() + qualifier_level = isSpecificQualifier(qualifier) + if not qualifier_level: + raise ParseError, words.tokens + interval = words.next() + interval_level = intervals.get(interval) + if not interval_level: + raise ParseError, words.tokens - current.add_details("the", qualifier, qualifier_level, interval, interval_level) + current.add_details("the", qualifier, qualifier_level, interval, interval_level) + + # Detect the intervals becoming smaller. - # Detect the intervals becoming smaller. + if current.qualified_by and current.interval_level < current.qualified_by.interval_level: + raise ParseError, words.tokens - if current.qualified_by and current.interval_level < current.qualified_by.interval_level: - raise ParseError, words.tokens + # Define selectors that are being qualified by the above qualifier and + # interval, returning the least specific selector. - # Define selectors that are being qualified by the above qualifier and - # interval, returning the least specific selector. + words.want() + if words.have("in"): + current = Selector(current) + words.want() + current = parseSpecificRecurrence(words, current) - words.want() - if words.have("in"): - current = Selector(current) - words.need("the") - current = parseSpecificRecurrence(words, current) + # Handle concrete datetime details. + + else: + current = parseConcreteDateTime(words, current) return current @@ -384,21 +401,27 @@ from_datetime = until_datetime = None if words.have("from"): + words.want() from_datetime = Selector() - words.next() - if words.have("the"): - from_datetime = parseSpecificRecurrence(words, from_datetime) - else: - from_datetime = parseConcreteDateTime(words, from_datetime) + from_datetime = parseSpecificRecurrence(words, from_datetime) + + # Detect the limit interval differing from the selector. + + if current.interval_level != from_datetime.get_resolution(): + raise ParseError, words.tokens + current.set_from(from_datetime) if words.have("until"): + words.want() until_datetime = Selector() - words.next() - if words.have("the"): - until_datetime = parseSpecificRecurrence(words, until_datetime) - else: - until_datetime = parseConcreteDateTime(words, until_datetime) + until_datetime = parseSpecificRecurrence(words, until_datetime) + + # Detect the limit interval differing from the selector. + + if current.interval_level != until_datetime.get_resolution(): + raise ParseError, words.tokens + current.set_until(until_datetime) # Where the selector refers to a interval repeating at a frequency greater diff -r 9fc4e33b0b61 -r deb6bdca9d1b tests/test_recurrence.py --- a/tests/test_recurrence.py Sun Jul 14 21:11:12 2013 +0200 +++ b/tests/test_recurrence.py Mon Jul 15 00:00:00 2013 +0200 @@ -7,50 +7,98 @@ s = "every single day of 2013" print s print "->", getRecurrence(s) + s = "every Tuesday of 2013" print s print "->", getRecurrence(s) + s = "every Wednesday of every March" print s print "->", getRecurrence(s) -s = "every single day of every other February from 2013" + +s = "every single day of every other February from 2013-02" print s print "->", getRecurrence(s) -s = "every single day of every other February from 2013-02-01" -print s -print "->", getRecurrence(s) + s = "every other day from 2013-02-01" print s print "->", getRecurrence(s) -s = "every single day of the second month" + +s = "every single day of the second month of 2013" print s print "->", getRecurrence(s) + +s = "every single day of the second month in 2013" +print s +print "->", getRecurrence(s) + s = "every single day of the second month of every year" print s print "->", getRecurrence(s) + s = "every other day from the first day of every other month from the third month of every year" print s print "->", getRecurrence(s) -s = "the second day of every other month from the first month" + +s = "the second day of every other month from the first month of 2012" print s print "->", getRecurrence(s) + s = "the second hour in the second day in the second month of every year" print s print "->", getRecurrence(s) + s = "every day from the second day until the 10th day of every other month until the fifth month" print s print "->", getRecurrence(s) + s = "every day from the 10th day in the second month until the 10th day in the 10th month of every third month from the first month" print s print "->", getRecurrence(s) -s = "every day of every third month from the 10th day in the second month until the 10th day in the 10th month" -print s -print "->", getRecurrence(s) # Bad recurrences. print +# Incomplete specification. + +s = "every single day of the second month" +print s +try: + print "->", getRecurrence(s) +except ParseError, exc: + print exc + +s = "the second day of every other month from the first month" +print s +try: + print "->", getRecurrence(s) +except ParseError, exc: + print exc + +# Limits do not match recurrence. + +s = "every single day of every other February from 2013" +print s +try: + print "->", getRecurrence(s) +except ParseError, exc: + print exc + +s = "every single day of every other February from 2013-02-01" +print s +try: + print "->", getRecurrence(s) +except ParseError, exc: + print exc + +s = "every day of every third month from the 10th day in the second month until the 10th day in the 10th month" +print s +try: + print "->", getRecurrence(s) +except ParseError, exc: + print exc + # No start or end points. s = "every other day of every other month"