# HG changeset patch # User Paul Boddie # Date 1445189584 -7200 # Node ID ad1bb10e0e30468eda62dfae19c840aa095de1d6 # Parent 679aa5cecd0ff3b07aa21bbe2502f126a4a29ced Added support for "open-ended" periods and showing certain calendar periods. diff -r 679aa5cecd0f -r ad1bb10e0e30 imiptools/period.py --- a/imiptools/period.py Sun Oct 18 18:02:07 2015 +0200 +++ b/imiptools/period.py Sun Oct 18 19:33:04 2015 +0200 @@ -28,6 +28,70 @@ get_tzid, \ to_timezone, to_utc_datetime +class Comparable: + + "A date/datetime wrapper that allows comparisons with other types." + + def __init__(self, dt): + self.dt = dt + + def __cmp__(self, other): + dt = None + odt = None + + # Find any dates/datetimes. + + if isinstance(self.dt, date): + dt = self.dt + if isinstance(other, date): + odt = other + elif isinstance(other, Comparable): + if isinstance(other.dt, date): + odt = other.dt + else: + other = other.dt + + if dt and odt: + return cmp(dt, odt) + elif dt: + return other.__rcmp__(dt) + elif odt: + return self.dt.__cmp__(odt) + else: + return self.dt.__cmp__(other) + +class PointInTime: + + "A base class for special values." + + pass + +class StartOfTime(PointInTime): + + "A special value that compares earlier than other values." + + def __cmp__(self, other): + if isinstance(other, StartOfTime): + return 0 + else: + return -1 + + def __rcmp__(self, other): + return -self.__cmp__(other) + +class EndOfTime(PointInTime): + + "A special value that compares later than other values." + + def __cmp__(self, other): + if isinstance(other, EndOfTime): + return 0 + else: + return 1 + + def __rcmp__(self, other): + return -self.__cmp__(other) + class PeriodBase: "A basic period abstraction." @@ -44,12 +108,16 @@ if isinstance(other, PeriodBase): return cmp( - (self.get_start_point(), self.get_end_point()), - (other.get_start_point(), other.get_end_point()) + (Comparable(self.get_start_point() or StartOfTime()), Comparable(self.get_end_point() or EndOfTime())), + (Comparable(other.get_start_point() or StartOfTime()), Comparable(other.get_end_point() or EndOfTime())) ) else: return 1 + def overlaps(self, other): + return Comparable(self.get_end_point()) > Comparable(other.get_start_point()) and \ + Comparable(self.get_start_point()) < Comparable(other.get_end_point()) + def get_key(self): return self.get_start(), self.get_end() @@ -96,8 +164,8 @@ dates/datetimes. """ - self.start = isinstance(start, date) and start or get_datetime(start) - self.end = isinstance(end, date) and end or get_datetime(end) + self.start = isinstance(start, date) and start or isinstance(start, PointInTime) and start or get_datetime(start) or StartOfTime() + self.end = isinstance(end, date) and end or isinstance(end, PointInTime) and end or get_datetime(end) or EndOfTime() self.tzid = tzid self.origin = origin @@ -113,10 +181,12 @@ return get_tzid(self.get_start_attr(), self.get_end_attr()) or self.tzid def get_start_point(self): - return to_utc_datetime(self.get_start(), self.get_tzid()) + start = self.get_start() + return isinstance(start, PointInTime) and start or to_utc_datetime(start, self.get_tzid()) def get_end_point(self): - return to_utc_datetime(self.get_end(), self.get_tzid()) + end = self.get_end() + return isinstance(end, PointInTime) and end or to_utc_datetime(end, self.get_tzid()) # Period and event recurrence logic. @@ -411,15 +481,15 @@ # Find the range of periods potentially overlapping the period in a version # of the free/busy collection sorted according to end datetimes. - endpoints = [(fb.get_end_point(), fb.get_start_point(), fb) for fb in startpoints] + endpoints = [(Period(fb.get_end_point(), fb.get_end_point()), fb) for fb in startpoints] endpoints.sort() - first = bisect_left(endpoints, (period.get_start_point(),)) + first = bisect_left(endpoints, (Period(period.get_start_point(), period.get_start_point()),)) endpoints = endpoints[first:] overlapping = set() - for end, start, fb in endpoints: - if end > period.get_start_point() and start < period.get_end_point(): + for p, fb in endpoints: + if fb.overlaps(period): overlapping.add(fb) overlapping = list(overlapping) diff -r 679aa5cecd0f -r ad1bb10e0e30 imipweb/calendar.py --- a/imipweb/calendar.py Sun Oct 18 18:02:07 2015 +0200 +++ b/imipweb/calendar.py Sun Oct 18 19:33:04 2015 +0200 @@ -26,7 +26,9 @@ get_start_of_next_day, get_timestamp, ends_on_same_day, \ to_timezone from imiptools.period import add_day_start_points, add_empty_days, add_slots, \ - get_scale, get_slots, get_spans, partition_by_day, Point + get_overlapping, \ + get_scale, get_slots, get_spans, partition_by_day, \ + Period, Point from imipweb.resource import ResourceClient class CalendarPage(ResourceClient): @@ -278,7 +280,12 @@ tzid = self.get_tzid() # Day view: start at the earliest known day and produce days until the - # latest known day, perhaps with expandable sections of empty days. + # latest known day, with expandable sections of empty days. + + args = self.env.get_query() + view_start = self.get_date_arg(args, "start") + view_end = self.get_date_arg(args, "end") + view_period = (view_start or view_end) and Period(view_start, view_end, self.get_tzid()) # Requests are listed and linked to their tentative positions in the # calendar. Other participants are also shown. @@ -304,6 +311,11 @@ for periods in period_groups: + # Filter periods outside the given view. + + if view_period: + periods = get_overlapping(periods, view_period) + # Get the time scale with start and end points. scale = get_scale(periods, tzid) @@ -861,4 +873,10 @@ def _period_identifier(self, period): return "%s-%s" % (format_datetime(period.get_start()), format_datetime(period.get_end())) + def get_date_arg(self, args, name): + values = args.get(name) + if not values: + return None + return get_datetime(values[0], {"VALUE-TYPE" : "DATE"}) + # vim: tabstop=4 expandtab shiftwidth=4