Examples

The following examples should help getting insight into timetables.

Cutting a timetable

The cut_timetable() cuts a timetable at specific cut points and returns a sub-timetable for each cut interval. The following example shows how a timetable with an entry spanning one month is cut in weekly intervals:

>>> from datetime import datetime, date, timedelta
>>> from timetable import cut_timetable, datetime_range
>>>
>>> timetable = [
...     (datetime(2015, 1, 1), datetime(2015, 2, 1), {'name': 'spam'}),
... ]
>>> cuts = datetime_range(datetime(2015, 1, 1), datetime(2015, 2, 1),
...         timedelta(days=7))
>>> for sub_timetable in cut_timetable(timetable, cuts):
...     for start, end, entry in sub_timetable:
...         print('%s %s %s' % (start.isoformat(), end.isoformat(), entry))
2015-01-01T00:00:00 2015-01-08T00:00:00 {'name': 'spam'}
2015-01-08T00:00:00 2015-01-15T00:00:00 {'name': 'spam'}
2015-01-15T00:00:00 2015-01-22T00:00:00 {'name': 'spam'}
2015-01-22T00:00:00 2015-01-29T00:00:00 {'name': 'spam'}

Annotating a timetable

A timetable consists of (start, end, entry) tuples, where start and end are datetime objects and entry is a dictionary. The entry dictionary contains arbitrary keys and values. You can add additional keys to this dictionary using the annotate_timetable() function. The following example shows how to compute the hour duration for each entry and add the result under the key hours to the entry.

>>> from datetime import datetime, date, timedelta
>>> from timetable import cut_timetable, annotate_timetable, datetime_range
>>>
>>> timetable = [
...     (datetime(2015, 1, 1), datetime(2015, 2, 1), {'name': 'spam'}),
... ]
>>> cuts = datetime_range(datetime(2015, 1, 1), datetime(2015, 2, 1),
...         timedelta(days=7))
>>>
>>> def calc_hours(start, end, entry):
...     entry['hours'] = (end - start).total_seconds() / 3600
>>>
>>> for sub_timetable in cut_timetable(timetable, cuts):
...     for start, end, entry in annotate_timetable(sub_timetable, calc_hours):
...         print('%s %s %s' % (start.isoformat(), end.isoformat(),
...                 entry['hours']))
2015-01-01T00:00:00 2015-01-08T00:00:00 168.0
2015-01-08T00:00:00 2015-01-15T00:00:00 168.0
2015-01-15T00:00:00 2015-01-22T00:00:00 168.0
2015-01-22T00:00:00 2015-01-29T00:00:00 168.0

Merging timetables

If you need to work with multiple timetables (for example from multiple calendars), you can use the merge_timetables() function to merge them into a single timetable.

>>> from datetime import datetime
>>> from timetable import merge_timetables
>>>
>>> timetable_spam = [
...     (datetime(2015, 1, 1), datetime(2015, 1, 2), {'name': 'spam'}),
...     (datetime(2015, 1, 3), datetime(2015, 1, 5), {'name': 'spam'}),
... ]
>>> timetable_eggs = [
...     (datetime(2015, 1, 2), datetime(2015, 1, 3), {'name': 'eggs'}),
...     (datetime(2015, 1, 4), datetime(2015, 1, 5), {'name': 'eggs'}),
... ]
>>>
>>> for start, end, entry in merge_timetables([timetable_spam, timetable_eggs]):
...     print('%s %s %s' % (start.isoformat(), end.isoformat(), entry))
2015-01-01T00:00:00 2015-01-02T00:00:00 {'name': 'spam'}
2015-01-02T00:00:00 2015-01-03T00:00:00 {'name': 'eggs'}
2015-01-03T00:00:00 2015-01-05T00:00:00 {'name': 'spam'}
2015-01-04T00:00:00 2015-01-05T00:00:00 {'name': 'eggs'}

Merge intersections

Timetable entries might overlap with each other. The function merge_intersections() merges overlapping entries, thereby generating a non-overlapping timetables. The entries of the non-overlapping timetable contain a single key entries, whose value is the list of merged entries.

>>> from datetime import datetime
>>> from timetable import merge_intersections
>>>
>>> timetable = [
...     (datetime(2015, 1, 1), datetime(2015, 1, 3), {'name': 'spam'}),
...     (datetime(2015, 1, 2), datetime(2015, 1, 5), {'name': 'eggs'}),
...     (datetime(2015, 1, 4), datetime(2015, 1, 6), {'name': 'spam'}),
... ]
>>>
>>> for start, end, entry in merge_intersections(timetable):
...     print('%s %s %s' % (start.isoformat(), end.isoformat(), entry))
2015-01-01T00:00:00 2015-01-02T00:00:00 {'entries': [{'name': 'spam'}]}
2015-01-02T00:00:00 2015-01-03T00:00:00 {'entries': [{'name': 'spam'}, {'name': 'eggs'}]}
2015-01-03T00:00:00 2015-01-04T00:00:00 {'entries': [{'name': 'eggs'}]}
2015-01-04T00:00:00 2015-01-05T00:00:00 {'entries': [{'name': 'eggs'}, {'name': 'spam'}]}
2015-01-05T00:00:00 2015-01-06T00:00:00 {'entries': [{'name': 'spam'}]}

Summing it up (literally)

The following example puts all the above pieces together to compute the duration of all entries with a given key. The duration is computed using another annotation function compute_duration(). This function distributes the duration equally to each key in case of overlaps.

>>> from datetime import datetime
>>> from timetable import (merge_intersections, annotate_timetable,
...         collect_keys, compute_duration)
>>>
>>> timetable = [
...     (datetime(2015, 1, 1), datetime(2015, 1, 3), {'name': 'spam'}),
...     (datetime(2015, 1, 2), datetime(2015, 1, 5), {'name': 'eggs'}),
...     (datetime(2015, 1, 4), datetime(2015, 1, 6), {'name': 'spam'}),
... ]
>>>
>>> timetable = merge_intersections(timetable)
>>> timetable = annotate_timetable(timetable, collect_keys(key='name'))
>>> timetable = annotate_timetable(timetable, compute_duration(key='name'))
>>>
>>> # Sum durations for each key.
>>> durations = {}
>>> for start, end, entry in timetable:
...     for name in entry['name']:
...         if not name in durations:
...             durations[name] = 0
...         durations[name] += entry['duration']
>>>
>>> for name in sorted(durations):
...     print('%s %s' % (name, durations[name] / 3600))
eggs 48.0
spam 72.0

There’s also a convenience function sum_timetable() available, that does all these steps at once. In addition, this function also cuts the timetable, thereby generating a series of durations.

>>> from datetime import datetime
>>> from timetable import sum_timetable, datetime_range
>>>
>>> timetable = [
...     (datetime(2015, 1, 1), datetime(2015, 1, 3), {'name': 'spam'}),
...     (datetime(2015, 1, 2), datetime(2015, 1, 5), {'name': 'eggs'}),
...     (datetime(2015, 1, 4), datetime(2015, 1, 6), {'name': 'spam'}),
... ]
>>>
>>> # Compute total durations.
>>> durations = sum_timetable(timetable,
...         cuts=[datetime(2015, 1, 1), datetime(2015, 1, 6)], key='name')
>>> for name in sorted(durations):
...     print('%s %s' % (name, [d / 3600. for d in durations[name]]))
eggs [0.0, 48.0]
spam [0.0, 72.0]
>>>
>>> # Compute daily durations.
>>> cuts = datetime_range(datetime(2015, 1, 1), datetime(2015, 1, 6),
...         timedelta(days=1))
>>> durations = sum_timetable(timetable, cuts=cuts, key='name')
>>> for name in sorted(durations):
...     print('%s %s' % (name, [d / 3600. for d in durations[name]]))
eggs [0.0, 0.0, 12.0, 24.0, 12.0]
spam [0.0, 24.0, 12.0, 0.0, 12.0]