1.1 --- a/imiptools/client.py Sun Jun 04 18:28:18 2017 +0200
1.2 +++ b/imiptools/client.py Sun Jun 04 20:15:44 2017 +0200
1.3 @@ -25,8 +25,10 @@
1.4 get_window_end, is_new_object, make_freebusy, \
1.5 make_uid, to_part, uri_dict, uri_item, uri_items, \
1.6 uri_parts, uri_values
1.7 -from imiptools.dates import check_permitted_values, format_datetime, get_default_timezone, \
1.8 - get_duration, get_timestamp
1.9 +from imiptools.dates import check_permitted_values, format_datetime, \
1.10 + get_datetime, get_default_timezone, \
1.11 + get_duration, get_time, get_timestamp, \
1.12 + to_datetime
1.13 from imiptools.i18n import get_translator
1.14 from imiptools.freebusy import SupportAttendee, SupportExpires
1.15 from imiptools.profile import Preferences
1.16 @@ -106,14 +108,28 @@
1.17 return prefs and prefs.get("TZID") or get_default_timezone()
1.18
1.19 def get_window_size(self):
1.20 +
1.21 + "Return the period window size as an integer."
1.22 +
1.23 prefs = self.get_preferences()
1.24 try:
1.25 return prefs and int(prefs.get("window_size")) or self.default_window_size
1.26 except (TypeError, ValueError):
1.27 return self.default_window_size
1.28
1.29 + def get_window_start(self):
1.30 +
1.31 + "Return the period window start as a datetime."
1.32 +
1.33 + prefs = self.get_preferences()
1.34 + start = prefs and get_datetime(prefs.get("window_start"), {"TZID" : self.get_tzid()})
1.35 + return isinstance(start, datetime) and start or start and to_datetime(start, self.get_tzid())
1.36 +
1.37 def get_window_end(self):
1.38 - return get_window_end(self.get_tzid(), self.get_window_size())
1.39 +
1.40 + "Return the period window end as a datetime."
1.41 +
1.42 + return get_window_end(self.get_tzid(), self.get_window_size(), self.get_window_start())
1.43
1.44 def is_participating(self):
1.45
1.46 @@ -224,16 +240,22 @@
1.47 if self.messenger and self.messenger.sender != get_address(self.user):
1.48 attr["SENT-BY"] = get_uri(self.messenger.sender)
1.49
1.50 - def get_periods(self, obj, explicit_only=False):
1.51 + def get_periods(self, obj, explicit_only=False, future_only=False):
1.52
1.53 """
1.54 Return periods for the given 'obj'. Interpretation of periods can depend
1.55 - on the time zone, which is obtained for the current user. If
1.56 - 'explicit_only' is set to a true value, only explicit periods will be
1.57 + on the time zone, which is obtained for the current user.
1.58 +
1.59 + If 'explicit_only' is set to a true value, only explicit periods will be
1.60 returned, not rule-based periods.
1.61 +
1.62 + If 'future_only' is set to a true value, only future periods will be
1.63 + returned, not all periods defined by an event starting in the past.
1.64 """
1.65
1.66 - return obj.get_periods(self.get_tzid(), not explicit_only and self.get_window_end() or None)
1.67 + return obj.get_periods(self.get_tzid(),
1.68 + start=(future_only and self.get_window_start() or None),
1.69 + end=(not explicit_only and self.get_window_end() or None))
1.70
1.71 # Store operations.
1.72
1.73 @@ -1096,7 +1118,7 @@
1.74
1.75 # Obtain the affected periods.
1.76
1.77 - periods = self.get_periods(obj)
1.78 + periods = self.get_periods(obj, future_only=True)
1.79
1.80 # Define an overriding transparency, the indicated event transparency,
1.81 # or the default transparency for the free/busy entry.
2.1 --- a/imiptools/data.py Sun Jun 04 18:28:18 2017 +0200
2.2 +++ b/imiptools/data.py Sun Jun 04 20:15:44 2017 +0200
2.3 @@ -259,12 +259,12 @@
2.4
2.5 return (dtstart, dtstart_attr), (dtend, dtend_attr)
2.6
2.7 - def get_periods(self, tzid, end=None, inclusive=False):
2.8 + def get_periods(self, tzid, start=None, end=None, inclusive=False):
2.9
2.10 """
2.11 Return periods defined by this object, employing the given 'tzid' where
2.12 no time zone information is defined, and limiting the collection to a
2.13 - window of time with the given 'end'.
2.14 + window of time with the given 'start' and 'end'.
2.15
2.16 If 'end' is omitted, only explicit recurrences and recurrences from
2.17 explicitly-terminated rules will be returned.
2.18 @@ -273,7 +273,7 @@
2.19 will be included.
2.20 """
2.21
2.22 - return get_periods(self, tzid, end, inclusive)
2.23 + return get_periods(self, tzid, start, end, inclusive)
2.24
2.25 def has_period(self, tzid, period):
2.26
2.27 @@ -282,7 +282,7 @@
2.28 zone information is defined, has the given 'period'.
2.29 """
2.30
2.31 - return period in self.get_periods(tzid, period.get_start_point(), inclusive=True)
2.32 + return period in self.get_periods(tzid, end=period.get_start_point(), inclusive=True)
2.33
2.34 def has_recurrence(self, tzid, recurrenceid):
2.35
2.36 @@ -292,23 +292,24 @@
2.37 """
2.38
2.39 start_point = self.get_recurrence_start_point(recurrenceid, tzid)
2.40 - for p in self.get_periods(tzid, start_point, inclusive=True):
2.41 + for p in self.get_periods(tzid, end=start_point, inclusive=True):
2.42 if p.get_start_point() == start_point:
2.43 return True
2.44 return False
2.45
2.46 - def get_active_periods(self, recurrenceids, tzid, end=None):
2.47 + def get_active_periods(self, recurrenceids, tzid, start=None, end=None):
2.48
2.49 """
2.50 Return all periods specified by this object that are not replaced by
2.51 those defined by 'recurrenceids', using 'tzid' as a fallback time zone
2.52 - to convert floating dates and datetimes, and using 'end' to indicate the
2.53 - end of the time window within which periods are considered.
2.54 + to convert floating dates and datetimes, and using 'start' and 'end' to
2.55 + respectively indicate the start and end of the time window within which
2.56 + periods are considered.
2.57 """
2.58
2.59 # Specific recurrences yield all specified periods.
2.60
2.61 - periods = self.get_periods(tzid, end)
2.62 + periods = self.get_periods(tzid, start, end)
2.63
2.64 if self.get_recurrenceid():
2.65 return periods
2.66 @@ -1035,12 +1036,13 @@
2.67
2.68 return delegators
2.69
2.70 -def get_periods(obj, tzid, end=None, inclusive=False):
2.71 +def get_periods(obj, tzid, start=None, end=None, inclusive=False):
2.72
2.73 """
2.74 Return periods for the given object 'obj', employing the given 'tzid' where
2.75 no time zone information is available (for whole day events, for example),
2.76 - confining materialised periods to before the given 'end' datetime.
2.77 + confining materialised periods to after the given 'start' datetime and
2.78 + before the given 'end' datetime.
2.79
2.80 If 'end' is omitted, only explicit recurrences and recurrences from
2.81 explicitly-terminated rules will be returned.
2.82 @@ -1083,11 +1085,32 @@
2.83 end = end and min(until_dt, end) or until_dt
2.84 inclusive = True
2.85
2.86 + # Define a selection period with a start point. The end will be handled
2.87 + # in the materialisation process below.
2.88 +
2.89 + selection_period = Period(start, None)
2.90 +
2.91 + # Obtain period instances, starting from the main period. Since counting
2.92 + # must start from the first period, filtering from a start date must be
2.93 + # done after the instances have been obtained.
2.94 +
2.95 for recurrence_start in selector.materialise(dtstart, end, parameters.get("COUNT"), parameters.get("BYSETPOS"), inclusive):
2.96 +
2.97 + # Determine the resolution of the period.
2.98 +
2.99 create = len(recurrence_start) == 3 and date or datetime
2.100 recurrence_start = to_timezone(create(*recurrence_start), obj_tzid)
2.101 recurrence_end = recurrence_start + main_period.get_duration()
2.102 - periods.append(RecurringPeriod(recurrence_start, recurrence_end, tzid, "RRULE", dtstart_attr))
2.103 +
2.104 + # Create the period with accompanying metadata based on the main
2.105 + # period and event details.
2.106 +
2.107 + period = RecurringPeriod(recurrence_start, recurrence_end, tzid, "RRULE", dtstart_attr)
2.108 +
2.109 + # Filter out periods before the start.
2.110 +
2.111 + if period.within(selection_period):
2.112 + periods.append(period)
2.113
2.114 else:
2.115 periods = []
2.116 @@ -1138,13 +1161,14 @@
2.117
2.118 return senders
2.119
2.120 -def get_window_end(tzid, days=100):
2.121 +def get_window_end(tzid, days=100, start=None):
2.122
2.123 """
2.124 Return a datetime in the time zone indicated by 'tzid' marking the end of a
2.125 - window of the given number of 'days'.
2.126 + window of the given number of 'days'. If 'start' is not indicated, the start
2.127 + of the window will be the current moment.
2.128 """
2.129
2.130 - return to_timezone(datetime.now(), tzid) + timedelta(days)
2.131 + return to_timezone(start or datetime.now(), tzid) + timedelta(days)
2.132
2.133 # vim: tabstop=4 expandtab shiftwidth=4
3.1 --- a/imiptools/handlers/scheduling/common.py Sun Jun 04 18:28:18 2017 +0200
3.2 +++ b/imiptools/handlers/scheduling/common.py Sun Jun 04 20:15:44 2017 +0200
3.3 @@ -3,7 +3,7 @@
3.4 """
3.5 Common scheduling functionality.
3.6
3.7 -Copyright (C) 2016 Paul Boddie <paul@boddie.org.uk>
3.8 +Copyright (C) 2016, 2017 Paul Boddie <paul@boddie.org.uk>
3.9
3.10 This program is free software; you can redistribute it and/or modify it under
3.11 the terms of the GNU General Public License as published by the Free Software
3.12 @@ -55,7 +55,7 @@
3.13 for user in users:
3.14 conflicts[user] = 0
3.15
3.16 - overlapping = freebusy.get_overlapping(handler.get_periods(handler.obj))
3.17 + overlapping = freebusy.get_overlapping(handler.get_periods(handler.obj, future_only=True))
3.18
3.19 # Where scheduling cannot occur, find the busy potential users.
3.20
4.1 --- a/imiptools/handlers/scheduling/freebusy.py Sun Jun 04 18:28:18 2017 +0200
4.2 +++ b/imiptools/handlers/scheduling/freebusy.py Sun Jun 04 20:15:44 2017 +0200
4.3 @@ -3,7 +3,7 @@
4.4 """
4.5 Free/busy-related scheduling functionality.
4.6
4.7 -Copyright (C) 2015, 2016 Paul Boddie <paul@boddie.org.uk>
4.8 +Copyright (C) 2015, 2016, 2017 Paul Boddie <paul@boddie.org.uk>
4.9
4.10 This program is free software; you can redistribute it and/or modify it under
4.11 the terms of the GNU General Public License as published by the Free Software
4.12 @@ -49,7 +49,7 @@
4.13 # If newer than any old version, discard old details from the
4.14 # free/busy record and check for suitability.
4.15
4.16 - periods = handler.get_periods(handler.obj)
4.17 + periods = handler.get_periods(handler.obj, future_only=True)
4.18
4.19 freebusy = freebusy or handler.get_store().get_freebusy(handler.user)
4.20 offers = handler.get_store().get_freebusy_offers(handler.user)
4.21 @@ -169,7 +169,7 @@
4.22
4.23 last = None
4.24
4.25 - for period in handler.get_periods(handler.obj, explicit_only=True):
4.26 + for period in handler.get_periods(handler.obj, explicit_only=True, future_only=True):
4.27 duration = period.get_duration()
4.28
4.29 # Try and schedule periods normally since some of them may be
5.1 --- a/imiptools/handlers/scheduling/quota.py Sun Jun 04 18:28:18 2017 +0200
5.2 +++ b/imiptools/handlers/scheduling/quota.py Sun Jun 04 20:15:44 2017 +0200
5.3 @@ -187,7 +187,7 @@
5.4
5.5 total = timedelta(0)
5.6
5.7 - for period in handler.get_periods(handler.obj):
5.8 + for period in handler.get_periods(handler.obj, future_only=True):
5.9 duration = period.get_duration()
5.10
5.11 # Decline events whose period durations are endless.
5.12 @@ -211,7 +211,7 @@
5.13 if handler.obj.possibly_recurring_indefinitely():
5.14 return None
5.15
5.16 - periods = handler.get_periods(handler.obj)
5.17 + periods = handler.get_periods(handler.obj, future_only=True)
5.18 return periods and to_utc_datetime(periods[-1].get_end_point()) or None
5.19
5.20 def _get_usage(entries):
5.21 @@ -282,7 +282,7 @@
5.22 # Check the event periods against the quota's consolidated record of the
5.23 # organiser's reservations.
5.24
5.25 - periods = handler.get_periods(handler.obj)
5.26 + periods = handler.get_periods(handler.obj, future_only=True)
5.27 freebusy = handler.get_journal().get_entries(quota, organiser)
5.28 scheduled = handler.can_schedule(freebusy, periods)
5.29
6.1 --- a/imipweb/calendar.py Sun Jun 04 18:28:18 2017 +0200
6.2 +++ b/imipweb/calendar.py Sun Jun 04 20:15:44 2017 +0200
6.3 @@ -469,7 +469,7 @@
6.4 # Requests are listed and linked to their tentative positions in the
6.5 # calendar. Other participants are also shown.
6.6
6.7 - request_summary = self._get_request_summary()
6.8 + request_summary = self._get_request_summary(view_period)
6.9
6.10 period_groups = [request_summary, freebusy]
6.11 period_group_types = ["request", "freebusy"]
7.1 --- a/imipweb/resource.py Sun Jun 04 18:28:18 2017 +0200
7.2 +++ b/imipweb/resource.py Sun Jun 04 20:15:44 2017 +0200
7.3 @@ -150,9 +150,12 @@
7.4 def _get_counters(self, uid, recurrenceid=None):
7.5 return self.store.get_counters(self.user, uid, recurrenceid)
7.6
7.7 - def _get_request_summary(self):
7.8 + def _get_request_summary(self, view_period):
7.9
7.10 - "Return a list of periods comprising the request summary."
7.11 + """
7.12 + Return a list of periods comprising the request summary within the given
7.13 + 'view_period'.
7.14 + """
7.15
7.16 summary = FreeBusyCollection()
7.17
7.18 @@ -176,7 +179,9 @@
7.19 # Obtain only active periods, not those replaced by redefined
7.20 # recurrences, converting to free/busy periods.
7.21
7.22 - for p in obj.get_active_periods(recurrenceids, self.get_tzid(), self.get_window_end()):
7.23 + for p in obj.get_active_periods(recurrenceids, self.get_tzid(),
7.24 + start=view_period.get_start(), end=view_period.get_end()):
7.25 +
7.26 summary.append(obj.get_freebusy_period(p))
7.27
7.28 return summary
8.1 --- a/tests/test_outgoing_invitation.sh Sun Jun 04 18:28:18 2017 +0200
8.2 +++ b/tests/test_outgoing_invitation.sh Sun Jun 04 20:15:44 2017 +0200
8.3 @@ -7,6 +7,7 @@
8.4 mkdir -p "$PREFS/$USER"
8.5 echo 'Europe/Oslo' > "$PREFS/$USER/TZID"
8.6 echo 'share' > "$PREFS/$USER/freebusy_sharing"
8.7 +echo '20141010' > "$PREFS/$USER/window_start"
8.8
8.9 "$OUTGOING_SCRIPT" $ARGS < "$TEMPLATES/event-request.txt" 2>> $ERROR
8.10
9.1 --- a/tests/test_resource_invitation_constraints_quota_recurring_unlimited.sh Sun Jun 04 18:28:18 2017 +0200
9.2 +++ b/tests/test_resource_invitation_constraints_quota_recurring_unlimited.sh Sun Jun 04 20:15:44 2017 +0200
9.3 @@ -25,6 +25,7 @@
9.4 schedule_in_freebusy
9.5 check_quota $QUOTA
9.6 EOF
9.7 +echo '20141010' > "$PREFS/$USER1/window_start"
9.8
9.9 mkdir -p "$PREFS/$USER2"
9.10 echo 'Europe/Oslo' > "$PREFS/$USER2/TZID"
9.11 @@ -33,6 +34,7 @@
9.12 schedule_in_freebusy
9.13 check_quota $QUOTA
9.14 EOF
9.15 +echo '20141010' > "$PREFS/$USER2/window_start"
9.16
9.17 cat <<EOF | "$SET_QUOTA_LIMITS" "$QUOTA" $SET_QUOTA_LIMITS_ARGS
9.18 * PT10H
10.1 --- a/tests/test_resource_invitation_recurring_indefinitely.sh Sun Jun 04 18:28:18 2017 +0200
10.2 +++ b/tests/test_resource_invitation_recurring_indefinitely.sh Sun Jun 04 20:15:44 2017 +0200
10.3 @@ -7,6 +7,7 @@
10.4 mkdir -p "$PREFS/$USER"
10.5 echo 'Europe/Oslo' > "$PREFS/$USER/TZID"
10.6 echo 'share' > "$PREFS/$USER/freebusy_sharing"
10.7 +echo '20141010' > "$PREFS/$USER/window_start"
10.8
10.9 "$RESOURCE_SCRIPT" $ARGS < "$TEMPLATES/fb-request-all.txt" 2>> $ERROR \
10.10 | "$SHOWMAIL" \