import datetime import hashlib import sys from bisect import bisect import yaml from icalendar import Calendar, Event event_config = {'description': '', 'categories': '', 'language': ''} zsigns = [(1, 20, '♑'), (2, 18, '♒'), (3, 20, '♓'), (4, 20, '♈'), (5, 21, '♉'), (6, 21, '♊'), (7, 22, '♋'), (8, 23, '♌'), (9, 23, '♍'), (10, 23, '♎'), (11, 22, '♏'), (12, 22, '♐'), (12, 31, '♑')] def load_config(): result = dict() with open('config.yml', 'r', encoding='utf-8') as f: result = yaml.load(f) return result def write_calendar(cal, filename): with open(filename, 'wb') as f: f.write(cal.to_ical()) def parse_dates(birthdays): for item in birthdays: person = item['person'] birthday_str = person['birthday'] if len(birthday_str) == 5: person['birthday_no_year'] = True birthday_str = birthday_str.replace(birthday_str[2], '.') birthday_str = birthday_str + '.0001' person['birthday'] = datetime.datetime.strptime( birthday_str, '%d.%m.%Y').date() def display(cal): print(cal.to_ical().decode('utf-8')) def sha1(string): if isinstance(string, str): string = string.encode('utf8') h = hashlib.sha1() h.update(string) return h.hexdigest() def age(person, year): return abs(year - person['birthday'].year) def zodiac_sign(person): birthday = person['birthday'] return zsigns[bisect(zsigns, (birthday.month, birthday.day))][2] def make_event(person, year): event_date = person['birthday'].replace(year=year) description = event_config['description'] lang_params = {'language': event_config['language']} if 'birthday_no_year' in person: description = description.replace('{age}', '') description = description.format( age=age(person, year), zodiac=zodiac_sign(person)) event = Event() event.add('uid', sha1(person['name'] + ' birthday ' + str(year))) event.add('summary', person['name'], encode=True, parameters=lang_params) event.add('description', description, encode=True, parameters=lang_params) event.add('categories', event_config['categories']) event.add('transp', 'TRANSPARENT') event.add('dtstamp', datetime.datetime( event_date.year, event_date.month, event_date.day)) event.add('dtstart', event_date) event.add('dtend', event_date) event.add('created', datetime.datetime.utcnow()) event.add('last-modified', datetime.datetime.utcnow()) return event def add_events(cal, birthdays, year): for item in birthdays: event = make_event(item['person'], year) cal.add_component(event) def make_calendar(calname, timezone, birthdays, scale): cal = Calendar() cal.add('version', '2.0') cal.add('prodid', '-//birthdays calendar generator//b4tman.ru//') cal.add('calscale', 'GREGORIAN') cal.add('method', 'PUBLISH') cal.add('x-wr-timezone', timezone) cal.add('x-wr-calname', calname) for year in range(scale['start'], scale['start'] + scale['length']): add_events(cal, birthdays, year) return cal config = load_config() birthdays = config['birthdays'] event_config['language'] = config['language'] event_config['description'] = config['events']['description'] event_config['categories'] = config['events']['categories'] parse_dates(birthdays) birthdays.sort(key=lambda x: x['person']['birthday'].strftime('%m.%d')) cal = make_calendar(config['calname'], config['timezone'], birthdays, config['scale']) if len(sys.argv) > 1: filename = sys.argv[1] write_calendar(cal, filename) else: display(cal)