1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1.2 +++ b/EventAggregatorSupport/Filter.py Tue Apr 30 23:49:32 2013 +0200
1.3 @@ -0,0 +1,226 @@
1.4 +# -*- coding: iso-8859-1 -*-
1.5 +"""
1.6 + MoinMoin - EventAggregator event filtering functionality.
1.7 +
1.8 + @copyright: 2008, 2009, 2010, 2011, 2012, 2013 by Paul Boddie <paul@boddie.org.uk>
1.9 + @license: GNU GPL (v2 or later), see COPYING.txt for details.
1.10 +"""
1.11 +
1.12 +from DateSupport import DateTime, Timespan, TimespanCollection, \
1.13 + getCurrentDate, getCurrentMonth
1.14 +from MoinSupport import *
1.15 +
1.16 +# Event filtering and limits.
1.17 +
1.18 +def getEventsInPeriod(events, calendar_period):
1.19 +
1.20 + """
1.21 + Return a collection containing those of the given 'events' which occur
1.22 + within the given 'calendar_period'.
1.23 + """
1.24 +
1.25 + all_shown_events = []
1.26 +
1.27 + for event in events:
1.28 +
1.29 + # Test for the suitability of the event.
1.30 +
1.31 + if event.as_timespan() is not None:
1.32 +
1.33 + # Compare the dates to the requested calendar window, if any.
1.34 +
1.35 + if event in calendar_period:
1.36 + all_shown_events.append(event)
1.37 +
1.38 + return all_shown_events
1.39 +
1.40 +def getEventLimits(events):
1.41 +
1.42 + "Return the earliest and latest of the given 'events'."
1.43 +
1.44 + earliest = None
1.45 + latest = None
1.46 +
1.47 + for event in events:
1.48 +
1.49 + # Test for the suitability of the event.
1.50 +
1.51 + if event.as_timespan() is not None:
1.52 + ts = event.as_timespan()
1.53 + if earliest is None or ts.start < earliest:
1.54 + earliest = ts.start
1.55 + if latest is None or ts.end > latest:
1.56 + latest = ts.end
1.57 +
1.58 + return earliest, latest
1.59 +
1.60 +def getLatestEventTimestamp(events):
1.61 +
1.62 + """
1.63 + Return the latest timestamp found from the given 'events'.
1.64 + """
1.65 +
1.66 + latest = None
1.67 +
1.68 + for event in events:
1.69 + metadata = event.getMetadata()
1.70 +
1.71 + if latest is None or latest < metadata["last-modified"]:
1.72 + latest = metadata["last-modified"]
1.73 +
1.74 + return latest
1.75 +
1.76 +def getCalendarPeriod(calendar_start, calendar_end):
1.77 +
1.78 + """
1.79 + Return a calendar period for the given 'calendar_start' and 'calendar_end'.
1.80 + These parameters can be given as None.
1.81 + """
1.82 +
1.83 + # Re-order the window, if appropriate.
1.84 +
1.85 + if calendar_start is not None and calendar_end is not None and calendar_start > calendar_end:
1.86 + calendar_start, calendar_end = calendar_end, calendar_start
1.87 +
1.88 + return Timespan(calendar_start, calendar_end)
1.89 +
1.90 +def getConcretePeriod(calendar_start, calendar_end, earliest, latest, resolution):
1.91 +
1.92 + """
1.93 + From the requested 'calendar_start' and 'calendar_end', which may be None,
1.94 + indicating that no restriction is imposed on the period for each of the
1.95 + boundaries, use the 'earliest' and 'latest' event months to define a
1.96 + specific period of interest.
1.97 + """
1.98 +
1.99 + # Define the period as starting with any specified start month or the
1.100 + # earliest event known, ending with any specified end month or the latest
1.101 + # event known.
1.102 +
1.103 + first = calendar_start or earliest
1.104 + last = calendar_end or latest
1.105 +
1.106 + # If there is no range of months to show, perhaps because there are no
1.107 + # events in the requested period, and there was no start or end month
1.108 + # specified, show only the month indicated by the start or end of the
1.109 + # requested period. If all events were to be shown but none were found show
1.110 + # the current month.
1.111 +
1.112 + if resolution == "date":
1.113 + get_current = getCurrentDate
1.114 + else:
1.115 + get_current = getCurrentMonth
1.116 +
1.117 + if first is None:
1.118 + first = last or get_current()
1.119 + if last is None:
1.120 + last = first or get_current()
1.121 +
1.122 + if resolution == "month":
1.123 + first = first.as_month()
1.124 + last = last.as_month()
1.125 +
1.126 + # Permit "expiring" periods (where the start date approaches the end date).
1.127 +
1.128 + return min(first, last), last
1.129 +
1.130 +def getCoverage(events, resolution="date"):
1.131 +
1.132 + """
1.133 + Determine the coverage of the given 'events', returning a collection of
1.134 + timespans, along with a dictionary mapping locations to collections of
1.135 + slots, where each slot contains a tuple of the form (timespans, events).
1.136 + """
1.137 +
1.138 + all_events = {}
1.139 + full_coverage = TimespanCollection(resolution)
1.140 +
1.141 + # Get event details.
1.142 +
1.143 + for event in events:
1.144 + event_details = event.getDetails()
1.145 +
1.146 + # Find the coverage of this period for the event.
1.147 +
1.148 + # For day views, each location has its own slot, but for month
1.149 + # views, all locations are pooled together since having separate
1.150 + # slots for each location can lead to poor usage of vertical space.
1.151 +
1.152 + if resolution == "datetime":
1.153 + event_location = event_details.get("location")
1.154 + else:
1.155 + event_location = None
1.156 +
1.157 + # Update the overall coverage.
1.158 +
1.159 + full_coverage.insert_in_order(event)
1.160 +
1.161 + # Add a new events list for a new location.
1.162 + # Locations can be unspecified, thus None refers to all unlocalised
1.163 + # events.
1.164 +
1.165 + if not all_events.has_key(event_location):
1.166 + all_events[event_location] = [TimespanCollection(resolution, [event])]
1.167 +
1.168 + # Try and fit the event into an events list.
1.169 +
1.170 + else:
1.171 + slot = all_events[event_location]
1.172 +
1.173 + for slot_events in slot:
1.174 +
1.175 + # Where the event does not overlap with the events in the
1.176 + # current collection, add it alongside these events.
1.177 +
1.178 + if not event in slot_events:
1.179 + slot_events.insert_in_order(event)
1.180 + break
1.181 +
1.182 + # Make a new element in the list if the event cannot be
1.183 + # marked alongside existing events.
1.184 +
1.185 + else:
1.186 + slot.append(TimespanCollection(resolution, [event]))
1.187 +
1.188 + return full_coverage, all_events
1.189 +
1.190 +def getCoverageScale(coverage):
1.191 +
1.192 + """
1.193 + Return a scale for the given coverage so that the times involved are
1.194 + exposed. The scale consists of a list of non-overlapping timespans forming
1.195 + a contiguous period of time.
1.196 + """
1.197 +
1.198 + times = set()
1.199 + for timespan in coverage:
1.200 + start, end = timespan.as_limits()
1.201 +
1.202 + # Add either genuine times or dates converted to times.
1.203 +
1.204 + if isinstance(start, DateTime):
1.205 + times.add(start)
1.206 + else:
1.207 + times.add(start.as_start_of_day())
1.208 +
1.209 + if isinstance(end, DateTime):
1.210 + times.add(end)
1.211 + else:
1.212 + times.add(end.as_date().next_day())
1.213 +
1.214 + times = list(times)
1.215 + times.sort(cmp_dates_as_day_start)
1.216 +
1.217 + scale = []
1.218 + first = 1
1.219 + start = None
1.220 + for time in times:
1.221 + if not first:
1.222 + scale.append(Timespan(start, time))
1.223 + else:
1.224 + first = 0
1.225 + start = time
1.226 +
1.227 + return scale
1.228 +
1.229 +# vim: tabstop=4 expandtab shiftwidth=4