1.1 --- a/vRecurrence.py Sun Dec 03 22:56:53 2017 +0100
1.2 +++ b/vRecurrence.py Mon Dec 04 22:35:42 2017 +0100
1.3 @@ -681,19 +681,47 @@
1.4 days = (date(*last_day) - weekday0).days
1.5 return days / 7 + 1
1.6
1.7 -def precision(t, level, value=None):
1.8 +def constrain_to_month(t):
1.9 +
1.10 + "Return 't' corrected to ensure that the day number is within the month."
1.11 +
1.12 + year, month, day = t[:3]
1.13 + time = t[3:]
1.14 + max_day = monthrange(year, month)[1]
1.15 + day = min(max_day, day)
1.16 + return tuple((year, month) + (day,) + time)
1.17 +
1.18 +def within_month(t):
1.19 +
1.20 + """
1.21 + Return whether 't' is within the permissible day range of the given month,
1.22 + if 't' describes a day or datetime. Otherwise, return a true value for years
1.23 + and months.
1.24 + """
1.25 +
1.26 + if len(t) < 3:
1.27 + return True
1.28 +
1.29 + year, month, day = t[:3]
1.30 + max_day = monthrange(year, month)[1]
1.31 + return day <= max_day
1.32 +
1.33 +def limit_value(t, level):
1.34 +
1.35 + "Return 't' trimmed in resolution to the indicated resolution 'level'."
1.36 +
1.37 + pos = positions[level]
1.38 + return t[:pos + 1]
1.39 +
1.40 +def make_value(t, level, value):
1.41
1.42 """
1.43 Return 't' trimmed in resolution to the indicated resolution 'level',
1.44 - setting 'value' at the given resolution if indicated.
1.45 + extended by setting 'value' at the given resolution.
1.46 """
1.47
1.48 pos = positions[level]
1.49 -
1.50 - if value is None:
1.51 - return t[:pos + 1]
1.52 - else:
1.53 - return t[:pos] + (value,)
1.54 + return t[:pos] + (value,)
1.55
1.56 def scale(interval, level):
1.57
1.58 @@ -733,18 +761,17 @@
1.59 # Dates and datetimes.
1.60
1.61 else:
1.62 - updated_for_months = update(t, step[:2])
1.63 + updated_for_months = constrain_to_month(update(t, step[:2]))
1.64 + d = datetime(*updated_for_months)
1.65
1.66 # Dates only.
1.67
1.68 if i == 3:
1.69 - d = datetime(*updated_for_months)
1.70 s = timedelta(step[2])
1.71
1.72 # Datetimes.
1.73
1.74 else:
1.75 - d = datetime(*updated_for_months)
1.76 s = timedelta(step[2], get_seconds(step))
1.77
1.78 return to_tuple(d + s)[:len(t)]
1.79 @@ -958,13 +985,13 @@
1.80 # qualifier in the selection chain.
1.81
1.82 if self.first:
1.83 - current = precision(context, self.level)
1.84 + current = limit_value(context, self.level)
1.85
1.86 # Otherwise, obtain the first value at this resolution within the
1.87 # context period.
1.88
1.89 else:
1.90 - current = precision(context, self.level, firstvalues[self.level])
1.91 + current = make_value(context, self.level, firstvalues[self.level])
1.92
1.93 return PatternIterator(self, current, start, end, inclusive,
1.94 self.step, self.unit_step)
1.95 @@ -1307,7 +1334,12 @@
1.96
1.97 "Return the period indicated by the current value."
1.98
1.99 - return precision(self.base, self.selector.level, self.value)
1.100 + t = make_value(self.base, self.selector.level, self.value)
1.101 +
1.102 + if not within_month(t):
1.103 + raise StopIteration
1.104 +
1.105 + return t
1.106
1.107 def next(self):
1.108
1.109 @@ -1320,15 +1352,15 @@
1.110 if not self.selector.selecting or self.value is None:
1.111 self.value = self.values.next()
1.112
1.113 - # Select a period for each value at the current resolution.
1.114 -
1.115 - self.current = self.get_selected_period()
1.116 - next = update(self.current, self.step)
1.117 + try:
1.118 + # Select a period for each value at the current resolution.
1.119
1.120 - # To support setpos, only current and next bound the search, not
1.121 - # the period in addition.
1.122 + self.current = self.get_selected_period()
1.123 + next = update(self.current, self.step)
1.124
1.125 - try:
1.126 + # To support setpos, only current and next bound the search, not
1.127 + # the period in addition.
1.128 +
1.129 return self.next_item(self.current, next)
1.130
1.131 # Move on to the next instance.
1.132 @@ -1350,7 +1382,7 @@
1.133 value, index = self.value
1.134 offset = timedelta(7 * (index - 1))
1.135 weekday0 = get_first_day(self.base, value)
1.136 - return precision(to_tuple(weekday0 + offset), DAYS)
1.137 + return limit_value(to_tuple(weekday0 + offset), DAYS)
1.138
1.139 class YearDayFilterIterator(EnumIterator):
1.140
1.141 @@ -1361,7 +1393,7 @@
1.142 "Return the day indicated by the current day value."
1.143
1.144 offset = timedelta(self.value - 1)
1.145 - return precision(to_tuple(date(*self.base) + offset), DAYS)
1.146 + return limit_value(to_tuple(date(*self.base) + offset), DAYS)
1.147
1.148 class LimitIterator(SelectorIterator):
1.149