1
#!/usr/bin/env python
2
# vim: set fileencoding=utf-8 tabstop=4 shiftwidth=4 expandtab smartindent:
3
u"""
4
5
Birthdays from VCard
6
--------------------
7
8
Creates an ICalendar file (ICS) from the birthdays in a VCard file (VCF).
9
10
.. :Authors:
11
       Aurélien Bompard <aurelien@bompard.org> <http://aurelien.bompard.org>
12
13
.. :License:
14
       GNU GPL v3 or later
15
"""
16
17
import sys
18
import os, vobject, datetime, time
19
import re
20
21
22
#event_tpl = """\r
23
#BEGIN:VEVENT\r
24
#UID:%(uid)s\r
25
#SUMMARY:Anniv. %(name)s\r
26
#CATEGORIES:Anniversaire\r
27
#DTSTART:%(date)sT060000Z\r
28
#DTEND:%(date)sT160000Z\r
29
#DESCRIPTION:%(desc)s\r
30
#BEGIN:VALARM\r
31
#ACTION:\r
32
#TRIGGER;VALUE=DURATION:PT0S\r
33
#END:VALARM\r
34
#END:VEVENT\r
35
#"""
36
#event_tpl = """\r
37
#BEGIN:VEVENT\r
38
#UID:ANNIV-%(uid)s\r
39
#SUMMARY:Anniv. %(name)s\r
40
#CATEGORIES:Anniversaire\r
41
#DESCRIPTION:%(desc)s\r
42
#DTSTART:%(date)sT060000Z\r
43
#DTEND:%(date)sT160000Z\r
44
#END:VEVENT\r
45
#"""
46
event_dict = {
47
    "UID": "ANNIV-%(cleanname)s",
48
    "SUMMARY": "Anniv. %(name)s",
49
    "CATEGORIES": "Anniversaire",
50
    "DESCRIPTION": "%(desc)s",
51
#    "DTSTART": "%(date)sT060000Z",
52
#    "DTEND": "%(date)sT160000Z",
53
    "DTSTART;VALUE=DATE": "%(date)s",
54
    "DTEND;VALUE=DATE": "%(day_after)s",
55
    "LAST-MODIFIED": "%(mtime)s",
56
    "DTSTAMP": "%(mtime)s",
57
    "CREATED": "%(mtime)s",
58
}
59
#RRULE:FREQ=YEARLY
60
#CREATED:%(now)s\r
61
#LAST-MODIFIED:%(now)s\r
62
#DTSTAMP:%(now)s\r
63
64
65
class UTC(datetime.tzinfo):
66
    def utcoffset(self, dt):
67
        return datetime.timedelta(0)
68
    def tzname(self, dt):
69
        return "UTC"
70
    def dst(self, dt):
71
        return datetime.timedelta(0)
72
73
re_datetime = re.compile("(\d\d\d\d)-(\d\d)-(\d\d)T(\d\d):(\d\d):(\d\d)(Z)?")
74
re_date = re.compile("(\d\d\d\d)-(\d\d)-(\d\d)")
75
def stringToDateTime(s, tzinfo=None):
76
    #Returns datetime.datetime object.
77
    if re_datetime.match(s):
78
        mo = re_datetime.match(s)
79
        year = int(mo.group(1))
80
        month = int(mo.group(2))
81
        day = int(mo.group(3))
82
        hour = int(mo.group(4))
83
        minute = int(mo.group(5))
84
        second = int(mo.group(6))
85
        if mo.group(7):
86
            tzinfo = UTC()
87
        return datetime.datetime(year, month, day, hour, minute, second, 0, tzinfo)
88
    elif re_date.match(s):
89
        mo = re_date.match(s)
90
        year = int(mo.group(1))
91
        month = int(mo.group(2))
92
        day = int(mo.group(3))
93
        return datetime.datetime(year, month, day, 0, 0, 0, 0, tzinfo)
94
    else:
95
        print s
96
        raise ValueError("Can't parse the date: %s" % s)
97
98
99
100
def makeICS(vcf_in):
101
    # Create the calendar
102
    cal = "BEGIN:VCALENDAR\r\nVERSION:2.0\r\n\r\n"
103
    events = getICS(vcf_in)
104
    cal += "\r\n\r\n".join(events)
105
    cal += "\r\n\r\nEND:VCALENDAR\r\n"
106
    return cal
107
108
def getEvents(vcf_in):
109
    # Filter out unparsable lines
110
    addresses = ""
111
    for line in vcf_in:
112
        if line.startswith("X-messaging"):
113
            continue
114
        addresses += line
115
116
    # Create the calendar
117
    today = datetime.date.today()
118
    #now = datetime.datetime.utcfromtimestamp(time.mktime(time.localtime())) # timezone-aware
119
    now = datetime.datetime.now() - datetime.timedelta(hours=2)
120
    items = []
121
    for address in vobject.readComponents(addresses):
122
        name = address.fn.value
123
        #uid = address.uid.value
124
        bday = None
125
        for child in address.getChildren():
126
            if child.name == "BDAY":
127
                bday = stringToDateTime(child.value).date()
128
            elif child.name == "NICKNAME":
129
                name = child.value
130
        if bday is None:
131
            continue
132
        bday_now = bday.replace(year=today.year)
133
        day_after = bday_now + datetime.timedelta(days=1)
134
        desc = "%s - %s years old" % (bday.year, today.year-bday.year)
135
        if hasattr(address, "rev"):
136
            mtime = address.rev.value.replace("-","").replace(":","")
137
        else:
138
            mtime = now.strftime("%Y%m%dT%H%M%SZ")
139
        event = {}
140
        for t_key, t_value in event_dict.iteritems():
141
            e_value = t_value % {#"uid": uid, 
142
                               "name": name,
143
                               "cleanname": re.sub("[^\w]", "", name),
144
                               "date": bday_now.strftime("%Y%m%d"), 
145
                               "day_after": day_after.strftime("%Y%m%d"),
146
                               "desc": desc,
147
                               "now": now.strftime("%Y%m%dT%H%M%SZ"),
148
                               "mtime": mtime,
149
                               }
150
            event[t_key] = e_value.encode("utf-8")
151
        items.append(event)
152
    return items
153
154
def getICS(vcf_in):
155
    events = getEvents(vcf_in)
156
    items = []
157
    for event in events:
158
        item = ["BEGIN:VEVENT"]
159
        item.extend( [ "%s:%s" % (key, value) for key, value in event.iteritems() ] )
160
        item.append("END:VEVENT")
161
        items.append("\r\n".join(item))
162
    return items
163
164
165
if __name__ == "__main__":
166
    #addresses_path = "%s/.kde/share/apps/kabc/std.vcf"%os.getenv("HOME")
167
    addresses_path = sys.argv[1]
168
    #anniv_path = "/tmp/annivs.ics"
169
    anniv_path = sys.argv[2]
170
    addresses_file = open(addresses_path)
171
    cal = makeICS(addresses_file)
172
    addresses_file.close()
173
    annivs = open(anniv_path, "w")
174
    annivs.write(cal)
175
    annivs.close()