summaryrefslogtreecommitdiff
path: root/rem2ics
blob: 32f46b9a820e0bb1776a367bad2f8e80fb215e2b (plain) (blame)
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
#!/usr/bin/env python3
#
# Converts output of remind to ICS format:
#
# Usage example: rem -ppp12 | rem2ics
#
# https://tools.ietf.org/html/rfc5545
import datetime
import json
import re
import sys
from datetime import datetime, timedelta, timezone
from os.path import basename, splitext


def escape_text(text):
    # https://tools.ietf.org/html/rfc5545#section-3.3.11
    return re.sub(r'[\\;:,]', r'\\\g<0>', text)


class Event:

    def __init__(self, uid, event_json):
        self.event_json = event_json
        self.uid = uid

    def summary(self):
        e = self.event_json
        summary = e.get('body', '')
        match = re.search(r'%"(.+)%"', summary)
        if match:
            summary = match.group(1)
        else:
            summary = self.__remove_spec(e, summary)
        return escape_text(summary)

    def description(self):
        e = self.event_json
        summary = e.get('body', '')
        if '%"' not in summary:
            return ""
        summary = summary.replace(r'%"', '')
        summary = self.__remove_spec(e, summary)
        return escape_text(summary)

    def categories(self):
        filename = basename(self.event_json['filename'].upper())
        return splitext(filename)[0]

    def dtstart(self):
        e = self.event_json
        if 'eventstart' in e:
            dt = datetime.strptime(e['eventstart'], '%Y-%m-%dT%H:%M')
            return self.__datetime_format(dt)
        return e['date'].replace('-', '')

    def dtend(self):
        e = self.event_json
        if is_multiple_days_event(e):
            dt = datetime.strptime(e['until'], '%Y-%m-%d')
            return (dt + timedelta(days=1)).strftime('%Y%m%d')
        elif 'eventduration' in e and 'eventstart' in e:
            dt = datetime.strptime(e['eventstart'], '%Y-%m-%dT%H:%M')
            return self.__datetime_format(dt + timedelta(minutes=int(e['eventduration'])))
        return self.dtstart()

    @staticmethod
    def __datetime_format(dt):
        return dt.astimezone(timezone.utc).strftime('%Y%m%dT%H%M%SZ')

    def __remove_spec(self, event, summary):
        if 'time' in event:
            summary = summary.split(' ', 1)[1]
        if 'passthru' in event and 'COLOR' in event['passthru']:
            summary = ' '.join(summary.split(' ')[3:])
        return summary


def create_uid(event):
    if is_multiple_days_event(event):
        date = event['until']
    else:
        date = event['date']
    return "%s:%s:%s" % (basename(event['filename']), event['lineno'], date)


def is_multiple_days_event(event):
    repeat = 'rep' in event
    has_until_date = 'until' in event
    has_time = 'time' in event
    return repeat and has_until_date and not has_time


def print_ics(events):
    print('BEGIN:VCALENDAR')
    print('VERSION:2.0')
    for event in events:
        print('BEGIN:VEVENT')
        print('UID:%s' % event.uid)
        print('DTSTART:%s' % event.dtstart())
        print('DTEND:%s' % event.dtend())
        print('SUMMARY:%s' % event.summary())
        print('DESCRIPTION:%s' % event.description())
        print('CATEGORIES:%s' % event.categories())
        print('END:VEVENT')
    print('END:VCALENDAR')


def rem2ics():
    added = set()
    events = []
    data = json.load(sys.stdin)
    for month in data:
        for event_json in month['entries']:
            uid = create_uid(event_json)
            if uid in added:
                continue
            added.add(uid)
            events.append(Event(uid, event_json))
    print_ics(events)


if __name__ == '__main__':
    rem2ics()