From a63d1c5e122a3ee6622be46c64915bd2df1afc37 Mon Sep 17 00:00:00 2001 From: Sean McGinnis Date: Thu, 27 Jun 2019 10:22:14 -0500 Subject: [PATCH] Add DTSTAMP and UID values to meeting instances The iCalendar spec expects a meeting vEvent to include a datetime stamp and a unique ID for each instance. This adds these values to the generated output. Minor cleanup also included since I was touching code around them and noticed little nits. Change-Id: I4753571850665a2f28a6799b84ead4c31a275cc7 Signed-off-by: Sean McGinnis --- .gitignore | 1 + tox.ini | 1 + yaml2ical/ical.py | 15 +++++++++++++-- yaml2ical/meeting.py | 31 +++++++++++++++++++++++++++++-- 4 files changed, 44 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index 46aec6e..72f3108 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ # Packages *.egg +*.eggs *.egg-info dist build diff --git a/tox.ini b/tox.ini index 6398c5b..5e42f90 100644 --- a/tox.ini +++ b/tox.ini @@ -7,6 +7,7 @@ install_command = pip install -U {opts} {packages} deps = -r{toxinidir}/requirements.txt -r{toxinidir}/test-requirements.txt commands = python setup.py testr --slowest --testr-args='{posargs}' +usedevelop = true [testenv:pep8] basepython = python3 diff --git a/yaml2ical/ical.py b/yaml2ical/ical.py index ea6f969..7968e12 100644 --- a/yaml2ical/ical.py +++ b/yaml2ical/ical.py @@ -11,10 +11,11 @@ # under the License. import datetime -import icalendar import logging import os import os.path + +import icalendar import pytz @@ -108,9 +109,19 @@ class Yaml2IcalCalendar(icalendar.Calendar): for skip_date in sch.skip_dates: event.add('exdate', skip_date.date) # NOTE(tonyb): If this is a skipped meeting add a - # non-recurring event with an obvisous summary. + # non-recurring event with an obvious summary. self.add_schedule(meeting, sch, exdate=skip_date) + # Add update timestamp and unique ID for the event. + # Note: we need a way to uniquely identify the meeting, even if its + # details are modified. We don't really have much to go on to do + # that, so best effort just use the project name and date. + event.add('dtstamp', meeting.last_update) + start_date = exdate.date if exdate else next_meeting_date + event.add('uid', '%s-%s' % ( + meeting.project.replace(' ', '').lower(), + datetime.datetime.strftime(start_date, '%Y%m%d'))) + # add event to calendar self.add_component(event) diff --git a/yaml2ical/meeting.py b/yaml2ical/meeting.py index ee54840..aa28629 100644 --- a/yaml2ical/meeting.py +++ b/yaml2ical/meeting.py @@ -15,6 +15,7 @@ from io import StringIO import os import os.path import pytz +import subprocess import yaml @@ -201,10 +202,16 @@ class Meeting(object): s = Schedule(self, sch) self.schedules.append(s) + # Set a default last update time + self.last_update = datetime.datetime.utcnow() + @classmethod def fromfile(cls, yaml_file): - f = open(yaml_file, 'r') - return cls(f) + with open(yaml_file, 'r') as f: + meeting = cls(f) + + meeting.last_update = _get_update_time(yaml_file) + return meeting @classmethod def fromstring(cls, yaml_string): @@ -212,6 +219,26 @@ class Meeting(object): return cls(s) +def _get_update_time(src_file): + """Attempts to figure out the last time the file was updated. + + If the file is under source control, this will try to find the last time it + was updated. If that is not possible, it will fallback to using the last + time the local file was modified. + """ + try: + last_updated = subprocess.check_output( + [ + 'git', 'log', '-n1', '--format=%ad', + '--date=format:%Y-%m-%d %H:%M:%S', + '--', src_file, + ] + ).decode('utf-8').strip() + return datetime.datetime.strptime(last_updated, '%Y-%m-%d %H:%M:%S') + except (subprocess.CalledProcessError, ValueError): + return datetime.datetime.fromtimestamp(os.path.getmtime(src_file)) + + def load_meetings(yaml_source): """Build YAML object and load meeting data