summaryrefslogtreecommitdiff
path: root/scripts/get_calendar.py
blob: 2fb12a712233ad1cf0eab7bd34738db74595c034 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
from datetime import datetime, timedelta, time, date
from dateutil.relativedelta import relativedelta
from dateutil import tz
import urllib2

import json
import logging
import sys
import copy
import os

from dateutil.rrule import rrulestr
import icalendar
import subprocess

import pytz

berlin = pytz.timezone('Europe/Berlin')

def fetch_calendar(logger):
    """Fetches the calendar events and returns a icalendar.Calendar instance.
    """
    try:
        response = urllib2.urlopen(CALENDARIUM_IMPORT_URL)
        logger.debug('Fetched calendar from %s' % CALENDARIUM_IMPORT_URL)
    except Exception:
        sys.excepthook(*sys.exc_info())
        content = None
    else:
        content = response.read()

    if content is None:
        # If we couldn't fetch the content, pull it from the copied file
        # if it's not there, just fail fatally as we don't have any data
        with open(CALENDARIUM_EXPORT, 'r') as export_file:
            content = export_file.read()
    else:
        with open(CALENDARIUM_EXPORT + '.new', 'w') as export_file:
            export_file.write(content)
        os.rename(CALENDARIUM_EXPORT + '.new', CALENDARIUM_EXPORT)

    try:
        with open(TODAY_EXPORT, 'r') as today_file:
            cached_today = today_file.read()
    except Exception:
        cached_today = None

    real_today = str(date.today()) + '\n'
    if real_today != cached_today:
        with open(TODAY_EXPORT, 'w') as today_file:
            today_file.write(real_today)
        regen = True
    else:
        regen = False

    return (regen,icalendar.Calendar.from_ical(content))

def get_events(calendar, after, before):
    """Yields all events in calendar as dictionary.

    Only events after (including) after will be shown. Recurring events
    til before (inclusively) will be shown.
    """
    for event in calendar.walk('vevent'):
        event_fields = [
                # dest, src, default
                ('name', 'summary', 'unknown Event'),
                ('description', 'description', ''),
        ]
        event_info = {}
        for fieldinfo in event_fields:
            try:
                event_info[fieldinfo[0]] = event[fieldinfo[1]].format().encode("utf-8")
                event_info[fieldinfo[0]] = event_info[fieldinfo[0]].decode("string-escape")
            except KeyError:
                event_info[fieldinfo[0]] = fieldinfo[2]
        start = icalendar.vDDDTypes.from_ical(event['dtstart'].to_ical())
        if 'dtend' in event:
            end = icalendar.vDDDTypes.from_ical(event['dtend'].to_ical())
        else:
            end = start + icalendar.vDDDTypes.from_ical(event['duration'].to_ical())
        if not isinstance(start, datetime):
            start = datetime.combine(start, time())
        if not isinstance(end, datetime):
            end = datetime.combine(end, time())
        if end.hour == 0 and end.minute == 0 and end.second == 0:
            end -= timedelta(seconds=1)
        if start.tzinfo is not None:
            start = start.astimezone(berlin).replace(tzinfo=None)
        if end.tzinfo is not None:
            end = end.astimezone(berlin).replace(tzinfo=None)

        if 'rrule' in event:
            rrule = rrulestr(event['rrule'].to_ical(), dtstart=start)
            if rrule._until is not None and rrule._until.tzinfo is not None:
                rrule._until = rrule._until.astimezone(berlin).replace(tzinfo=None)
            duration = end - start
            try:
                for occurence in rrule.between(after, before, True):
                    event_info['start'] = occurence
                    event_info['end'] = occurence + duration
                    event_info['recurring'] = True
                    yield copy.deepcopy(event_info)
            except TypeError:
                import pdb
                pdb.set_trace()
        else:
            if start >= after:
                event_info['start'] = start
                event_info['end'] = end
                event_info['recurring'] = False
                yield event_info

class DatetimeEncoder(json.JSONEncoder):
    def default(self, obj):
        if isinstance(obj, datetime):
            return obj.isoformat()
        return json.JSONEncoder.default(self,obj)

def put_events(calendar, after, before, destination):
    events = []
    for event in get_events(calendar, after, before):
        event['start'] = event['start'].isoformat()
        event['end'] = event['end'].isoformat()
        events.append(event)
    if os.path.exists(destination):
        with open(destination, 'r') as dfile:
            current = dfile.read()
    else:
        current = None
    output = json.dumps(events, indent=4)

    if current != output:
        with open(destination + '.new', 'w') as dfile:
            dfile.write(output)
        os.rename(destination + '.new', destination)
        return True
    return False

base_path = os.path.dirname(os.path.abspath(__file__))
#CALENDARIUM_IMPORT_URL = 'https://sublab.org:5232/calendars/events'
#CALENDARIUM_IMPORT_URL = 'https://posteo.de/calendars/ics/ruku1tibpa2b2evfwsxrbvwcy2n60j9g'
CALENDARIUM_IMPORT_URL = 'https://calendar.google.com/calendar/ical/termine.sublab%40gmail.com/public/basic.ics'
CALENDARIUM_EXPORT = os.path.join(base_path, '../public/calendar.ics')
TODAY_EXPORT = os.path.join(base_path, 'get_calendar.today')
if __name__ == '__main__':
    logging.basicConfig(stream=sys.stderr, level=logging.ERROR)
    logger = logging.getLogger('calendar_feed')
    regen, calendar = fetch_calendar(logger)
    now = datetime.now()
    after = now - relativedelta(days=1)
    before = now + relativedelta(months=+1)

    after_tab = datetime(now.year,now.month,1);
    if now.month + 2 > 12:
        before_tab = datetime(now.year+1,now.month - 10,1)
    else:
        before_tab = datetime(now.year,now.month+2,1)

    changed = put_events(calendar, after, before,
                         os.path.join(base_path, '../template/calendar.json'))
    tab_changed = put_events(calendar, after_tab, before_tab,
                             os.path.join(base_path, '../template/tabcalendar.json'))

    if changed or tab_changed or regen:
        subprocess.call(os.path.join(os.path.dirname(os.path.abspath(__file__)), 'template.py'))