1.1 --- a/RecurrenceSupport.py Sun Jul 14 21:11:12 2013 +0200
1.2 +++ b/RecurrenceSupport.py Mon Jul 15 00:00:00 2013 +0200
1.3 @@ -11,19 +11,23 @@
1.4
1.5 recurrence = <specific-recurrence> | <repeating-recurrence>
1.6
1.7 - specific-recurrence = ( ( the <qualifier> <interval> ) | <concrete-datetime> )
1.8 + specific-recurrence = ( the <qualifier> <interval> | <month-label> )
1.9 [ in <specific-recurrence> ]
1.10 + | <concrete-datetime>
1.11
1.12 repeating-recurrence = every [ <qualifier> ] <interval>
1.13 [ from <specific-recurrence> ]
1.14 [ until <specific-recurrence> ]
1.15
1.16 - concrete-datetime = <datetime> | <month> | <year>
1.17 + concrete-datetime = <datetime> | <year>
1.18
1.19 Constraints:
1.20
1.21 repeating-recurrence: if <qualifier> is not "single":
1.22 from and/or until must be specified
1.23 +
1.24 + from and/or until <specific-recurrence> resolutions
1.25 + must be compatible with the repeating-recurrence
1.26 """
1.27
1.28 from DateSupport import weekday_labels, weekday_labels_verbose, month_labels, \
1.29 @@ -148,6 +152,12 @@
1.30 else:
1.31 return t
1.32
1.33 + def have_one_of(self, tokens):
1.34 + if self.end:
1.35 + return False
1.36 + t = self.tokens[-1]
1.37 + return t in tokens
1.38 +
1.39 def _next(self):
1.40 t = self.iterator.next()
1.41 self.tokens.append(t)
1.42 @@ -203,9 +213,11 @@
1.43 self.until_datetime and ", until_datetime=%r" % self.until_datetime or "",
1.44 self.qualified_by and ", qualified_by=%r" % self.qualified_by or "")
1.45
1.46 - def select(self):
1.47 -
1.48 - "Select occurrences using this object's criteria."
1.49 + def get_resolution(self):
1.50 + if self.qualified_by:
1.51 + return self.qualified_by.get_resolution()
1.52 + else:
1.53 + return self.interval_level
1.54
1.55 # Parsing functions.
1.56
1.57 @@ -237,7 +249,8 @@
1.58 """
1.59 Using the incoming 'words' and given the 'current' selector, parse a
1.60 recurrence that can be either a repeating recurrence (starting with "every")
1.61 - or a specific recurrence (starting with "the").
1.62 + or a specific recurrence (starting with "the") or a concrete datetime
1.63 + reference.
1.64
1.65 The active selector is returned as a result of parsing the recurrence.
1.66 """
1.67 @@ -247,10 +260,8 @@
1.68 parseRepeatingRecurrence(words, current)
1.69 words.want()
1.70 return current
1.71 - elif words.have("the"):
1.72 + else:
1.73 return parseSpecificRecurrence(words, current)
1.74 - else:
1.75 - return parseConcreteDateTime(words, current)
1.76
1.77 def parseConcreteDateTime(words, current):
1.78
1.79 @@ -284,13 +295,6 @@
1.80 words.want()
1.81 return current
1.82
1.83 - # Detect month labels.
1.84 -
1.85 - elif word in month_labels:
1.86 - current.add_datetime("month", intervals["month"], word)
1.87 - words.want()
1.88 - return current
1.89 -
1.90 raise ParseError, words.tokens
1.91
1.92 def parseSpecificRecurrence(words, current):
1.93 @@ -300,32 +304,45 @@
1.94 return a specific recurrence.
1.95 """
1.96
1.97 + word = words.have()
1.98 +
1.99 + # Detect month labels.
1.100 +
1.101 + if word in month_labels:
1.102 + current.add_details("the", word, month_labels.index(word) + 1, "month", intervals["month"])
1.103 +
1.104 # Handle the qualifier and interval.
1.105
1.106 - qualifier = words.next()
1.107 - qualifier_level = isSpecificQualifier(qualifier)
1.108 - if not qualifier_level:
1.109 - raise ParseError, words.tokens
1.110 - interval = words.next()
1.111 - interval_level = intervals.get(interval)
1.112 - if not interval_level:
1.113 - raise ParseError, words.tokens
1.114 + elif word == "the":
1.115 + qualifier = words.next()
1.116 + qualifier_level = isSpecificQualifier(qualifier)
1.117 + if not qualifier_level:
1.118 + raise ParseError, words.tokens
1.119 + interval = words.next()
1.120 + interval_level = intervals.get(interval)
1.121 + if not interval_level:
1.122 + raise ParseError, words.tokens
1.123
1.124 - current.add_details("the", qualifier, qualifier_level, interval, interval_level)
1.125 + current.add_details("the", qualifier, qualifier_level, interval, interval_level)
1.126 +
1.127 + # Detect the intervals becoming smaller.
1.128
1.129 - # Detect the intervals becoming smaller.
1.130 + if current.qualified_by and current.interval_level < current.qualified_by.interval_level:
1.131 + raise ParseError, words.tokens
1.132
1.133 - if current.qualified_by and current.interval_level < current.qualified_by.interval_level:
1.134 - raise ParseError, words.tokens
1.135 + # Define selectors that are being qualified by the above qualifier and
1.136 + # interval, returning the least specific selector.
1.137
1.138 - # Define selectors that are being qualified by the above qualifier and
1.139 - # interval, returning the least specific selector.
1.140 + words.want()
1.141 + if words.have("in"):
1.142 + current = Selector(current)
1.143 + words.want()
1.144 + current = parseSpecificRecurrence(words, current)
1.145
1.146 - words.want()
1.147 - if words.have("in"):
1.148 - current = Selector(current)
1.149 - words.need("the")
1.150 - current = parseSpecificRecurrence(words, current)
1.151 + # Handle concrete datetime details.
1.152 +
1.153 + else:
1.154 + current = parseConcreteDateTime(words, current)
1.155
1.156 return current
1.157
1.158 @@ -384,21 +401,27 @@
1.159 from_datetime = until_datetime = None
1.160
1.161 if words.have("from"):
1.162 + words.want()
1.163 from_datetime = Selector()
1.164 - words.next()
1.165 - if words.have("the"):
1.166 - from_datetime = parseSpecificRecurrence(words, from_datetime)
1.167 - else:
1.168 - from_datetime = parseConcreteDateTime(words, from_datetime)
1.169 + from_datetime = parseSpecificRecurrence(words, from_datetime)
1.170 +
1.171 + # Detect the limit interval differing from the selector.
1.172 +
1.173 + if current.interval_level != from_datetime.get_resolution():
1.174 + raise ParseError, words.tokens
1.175 +
1.176 current.set_from(from_datetime)
1.177
1.178 if words.have("until"):
1.179 + words.want()
1.180 until_datetime = Selector()
1.181 - words.next()
1.182 - if words.have("the"):
1.183 - until_datetime = parseSpecificRecurrence(words, until_datetime)
1.184 - else:
1.185 - until_datetime = parseConcreteDateTime(words, until_datetime)
1.186 + until_datetime = parseSpecificRecurrence(words, until_datetime)
1.187 +
1.188 + # Detect the limit interval differing from the selector.
1.189 +
1.190 + if current.interval_level != until_datetime.get_resolution():
1.191 + raise ParseError, words.tokens
1.192 +
1.193 current.set_until(until_datetime)
1.194
1.195 # Where the selector refers to a interval repeating at a frequency greater
2.1 --- a/tests/test_recurrence.py Sun Jul 14 21:11:12 2013 +0200
2.2 +++ b/tests/test_recurrence.py Mon Jul 15 00:00:00 2013 +0200
2.3 @@ -7,50 +7,98 @@
2.4 s = "every single day of 2013"
2.5 print s
2.6 print "->", getRecurrence(s)
2.7 +
2.8 s = "every Tuesday of 2013"
2.9 print s
2.10 print "->", getRecurrence(s)
2.11 +
2.12 s = "every Wednesday of every March"
2.13 print s
2.14 print "->", getRecurrence(s)
2.15 -s = "every single day of every other February from 2013"
2.16 +
2.17 +s = "every single day of every other February from 2013-02"
2.18 print s
2.19 print "->", getRecurrence(s)
2.20 -s = "every single day of every other February from 2013-02-01"
2.21 -print s
2.22 -print "->", getRecurrence(s)
2.23 +
2.24 s = "every other day from 2013-02-01"
2.25 print s
2.26 print "->", getRecurrence(s)
2.27 -s = "every single day of the second month"
2.28 +
2.29 +s = "every single day of the second month of 2013"
2.30 print s
2.31 print "->", getRecurrence(s)
2.32 +
2.33 +s = "every single day of the second month in 2013"
2.34 +print s
2.35 +print "->", getRecurrence(s)
2.36 +
2.37 s = "every single day of the second month of every year"
2.38 print s
2.39 print "->", getRecurrence(s)
2.40 +
2.41 s = "every other day from the first day of every other month from the third month of every year"
2.42 print s
2.43 print "->", getRecurrence(s)
2.44 -s = "the second day of every other month from the first month"
2.45 +
2.46 +s = "the second day of every other month from the first month of 2012"
2.47 print s
2.48 print "->", getRecurrence(s)
2.49 +
2.50 s = "the second hour in the second day in the second month of every year"
2.51 print s
2.52 print "->", getRecurrence(s)
2.53 +
2.54 s = "every day from the second day until the 10th day of every other month until the fifth month"
2.55 print s
2.56 print "->", getRecurrence(s)
2.57 +
2.58 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"
2.59 print s
2.60 print "->", getRecurrence(s)
2.61 -s = "every day of every third month from the 10th day in the second month until the 10th day in the 10th month"
2.62 -print s
2.63 -print "->", getRecurrence(s)
2.64
2.65 # Bad recurrences.
2.66
2.67 print
2.68
2.69 +# Incomplete specification.
2.70 +
2.71 +s = "every single day of the second month"
2.72 +print s
2.73 +try:
2.74 + print "->", getRecurrence(s)
2.75 +except ParseError, exc:
2.76 + print exc
2.77 +
2.78 +s = "the second day of every other month from the first month"
2.79 +print s
2.80 +try:
2.81 + print "->", getRecurrence(s)
2.82 +except ParseError, exc:
2.83 + print exc
2.84 +
2.85 +# Limits do not match recurrence.
2.86 +
2.87 +s = "every single day of every other February from 2013"
2.88 +print s
2.89 +try:
2.90 + print "->", getRecurrence(s)
2.91 +except ParseError, exc:
2.92 + print exc
2.93 +
2.94 +s = "every single day of every other February from 2013-02-01"
2.95 +print s
2.96 +try:
2.97 + print "->", getRecurrence(s)
2.98 +except ParseError, exc:
2.99 + print exc
2.100 +
2.101 +s = "every day of every third month from the 10th day in the second month until the 10th day in the 10th month"
2.102 +print s
2.103 +try:
2.104 + print "->", getRecurrence(s)
2.105 +except ParseError, exc:
2.106 + print exc
2.107 +
2.108 # No start or end points.
2.109
2.110 s = "every other day of every other month"