1.1 --- a/imiptools/period.py Thu Jan 22 20:13:06 2015 +0100
1.2 +++ b/imiptools/period.py Fri Jan 23 23:58:26 2015 +0100
1.3 @@ -100,19 +100,36 @@
1.4
1.5 # Period layout.
1.6
1.7 -def get_scale(l):
1.8 +def convert_periods(periods, tzid):
1.9 +
1.10 + "Convert 'periods' to use datetime objects employing the given 'tzid'."
1.11 +
1.12 + l = []
1.13 +
1.14 + for t in periods:
1.15 + start, end = t[:2]
1.16 + start = to_timezone(get_datetime(start), tzid)
1.17 + end = to_timezone(get_datetime(end), tzid)
1.18 + l.append((start, end) + tuple(t[2:]))
1.19 +
1.20 + return l
1.21 +
1.22 +def get_scale(periods):
1.23
1.24 """
1.25 - Return an ordered time scale from the given list 'l' of tuples, with the
1.26 - first two elements of each tuple being start and end times.
1.27 + Return an ordered time scale from the given list 'periods', with the first
1.28 + two elements of each tuple being start and end times.
1.29
1.30 - The returned scale is a collection of (time, (starting, ending)) tuples,
1.31 - where starting and ending are collections of tuples from 'l'.
1.32 + The given 'tzid' is used to make sure that the times are defined according
1.33 + to the chosen time zone.
1.34 +
1.35 + The returned scale is a mapping from time to (starting, ending) tuples,
1.36 + where starting and ending are collections of tuples from 'periods'.
1.37 """
1.38
1.39 scale = {}
1.40
1.41 - for t in l:
1.42 + for t in periods:
1.43 start, end = t[:2]
1.44
1.45 # Add a point and this event to the starting list.
1.46 @@ -127,15 +144,12 @@
1.47 scale[end] = [], []
1.48 scale[end][1].append(t)
1.49
1.50 - scale = scale.items()
1.51 - scale.sort()
1.52 return scale
1.53
1.54 -def get_slots(l):
1.55 +def get_slots(scale):
1.56
1.57 """
1.58 - Return an ordered list of time slots from the given list 'l' of tuples, with
1.59 - the first two elements of each tuple being start and end times.
1.60 + Return an ordered list of time slots from the given 'scale'.
1.61
1.62 Each slot is a tuple containing a point in time for the start of the slot,
1.63 together with a list of parallel event tuples, each tuple containing the
1.64 @@ -145,7 +159,10 @@
1.65 slots = []
1.66 active = []
1.67
1.68 - for point, (starting, ending) in get_scale(l):
1.69 + points = scale.items()
1.70 + points.sort()
1.71 +
1.72 + for point, (starting, ending) in points:
1.73
1.74 # Discard all active events ending at or before this start time.
1.75 # Free up the position in the active list.
1.76 @@ -173,45 +190,84 @@
1.77
1.78 return slots
1.79
1.80 -def partition_slots(slots, tzid):
1.81 +def add_day_start_points(slots):
1.82
1.83 """
1.84 - Partition the given 'slots' into separate collections having a date-level
1.85 - resolution, using the given 'tzid' to make sure that the day boundaries are
1.86 - defined according to the chosen time zone.
1.87 -
1.88 - Return a collection of (date, slots) tuples.
1.89 + Introduce into the 'slots' any day start points required by multi-day
1.90 + periods.
1.91 """
1.92
1.93 - partitioned = {}
1.94 - current = None
1.95 + new_slots = []
1.96 current_date = None
1.97 previously_active = None
1.98
1.99 for point, active in slots:
1.100 - dt = to_timezone(get_datetime(point), tzid)
1.101 - start_of_day = get_start_of_day(dt)
1.102 - this_date = dt.date()
1.103 + start_of_day = get_start_of_day(point)
1.104 + this_date = point.date()
1.105
1.106 # For each new day, create a partition of the original collection.
1.107
1.108 if this_date != current_date:
1.109 current_date = this_date
1.110 - partitioned[current_date] = current = []
1.111
1.112 # Add any continuing periods.
1.113
1.114 - if dt != start_of_day and previously_active:
1.115 - current.append((start_of_day, previously_active))
1.116 + if point != start_of_day and previously_active:
1.117 + new_slots.append((start_of_day, previously_active))
1.118
1.119 # Add the currently active periods at this point in time.
1.120
1.121 - current.append((point, active))
1.122 previously_active = active
1.123
1.124 - partitioned = partitioned.items()
1.125 - partitioned.sort()
1.126 - return partitioned
1.127 + for t in new_slots:
1.128 + insort_left(slots, t)
1.129 +
1.130 +def add_slots(slots, points):
1.131 +
1.132 + """
1.133 + Introduce into the 'slots' entries for those in 'points' that are not
1.134 + already present, propagating active periods from time points preceding and
1.135 + succeeding those added.
1.136 + """
1.137 +
1.138 + new_slots = []
1.139 +
1.140 + for point in points:
1.141 + i = bisect_left(slots, (point, None))
1.142 + if i < len(slots) and slots[i][0] == point:
1.143 + continue
1.144 +
1.145 + previously_active = i > 0 and slots[i-1] or []
1.146 + subsequently_active = i < len(slots) and slots[i] or []
1.147 +
1.148 + active = []
1.149 +
1.150 + for p, s in zip(previously_active, subsequently_active):
1.151 + if p == s:
1.152 + active.append(p)
1.153 + else:
1.154 + active.append(None)
1.155 +
1.156 + new_slots.append((point, active))
1.157 +
1.158 + for t in new_slots:
1.159 + insort_left(slots, t)
1.160 +
1.161 +def partition_by_day(slots):
1.162 +
1.163 + """
1.164 + Return a mapping from dates to time points provided by 'slots'.
1.165 + """
1.166 +
1.167 + d = {}
1.168 +
1.169 + for point, value in slots:
1.170 + day = point.date()
1.171 + if not d.has_key(day):
1.172 + d[day] = []
1.173 + d[day].append((point, value))
1.174 +
1.175 + return d
1.176
1.177 def get_spans(slots):
1.178