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()
|