programming:python:cpc2ical
no way to compare when less than two revisions
Differences
This shows you the differences between two versions of the page.
— | programming:python:cpc2ical [2010/09/30 17:12] (current) – created jay | ||
---|---|---|---|
Line 1: | Line 1: | ||
+ | ====== What is it? ====== | ||
+ | The other day my wife was lamenting the fact that the calendar for the City Pages, http:// | ||
+ | |||
+ | I decided to take it upon myself to bang out a quick script that would parse the daily rss feeds and convert them to iCal VEVENTS in a VCALENDAR. | ||
+ | |||
+ | Do note that this is a simple script that I banged out in a short amount of time to accomplish a simple task. It works well for that task, but I'm sure this could be taken to another level in terms of automation and such. I leave that as an exercise for others. | ||
+ | |||
+ | ====== The Script ====== | ||
+ | You can download the script by clicking {{: | ||
+ | |||
+ | Here is the full source: | ||
+ | <code python> | ||
+ | # | ||
+ | |||
+ | # $Id: cpc2ical.py 312 2010-09-30 16:52:54Z jay $ | ||
+ | |||
+ | # Copyright Jason Deiman 2010 | ||
+ | |||
+ | # | ||
+ | # This script' | ||
+ | # an ical calendar file for a given month. | ||
+ | # calendar. | ||
+ | # | ||
+ | # This script requires the following non-standard libraries: | ||
+ | # | ||
+ | # | ||
+ | # python-feedparser) | ||
+ | # | ||
+ | |||
+ | from datetime import datetime , timedelta , tzinfo | ||
+ | from optparse import OptionParser | ||
+ | from hashlib import sha1 | ||
+ | from uuid import uuid4 | ||
+ | import time , sys , re | ||
+ | try: | ||
+ | import feedparser | ||
+ | except: | ||
+ | print >> sys.stderr , 'Could not import feedparser. | ||
+ | 'it from http:// | ||
+ | ' | ||
+ | sys.exit(1) | ||
+ | CPBASEURL = ' | ||
+ | RE_YM = re.compile(' | ||
+ | |||
+ | class UTC(tzinfo): | ||
+ | """ | ||
+ | UTC timezone for the datetime stuff | ||
+ | """ | ||
+ | def utcoffset(self , dt): | ||
+ | return timedelta(0) | ||
+ | |||
+ | def tzname(self , dt): | ||
+ | return ' | ||
+ | |||
+ | def dst(self , dt): | ||
+ | return timedelta(0) | ||
+ | |||
+ | class Vcal(object): | ||
+ | """ | ||
+ | This is just a structure to hold all the boiler-plate vcal stuff. | ||
+ | not using the icalendar stuff for this since it's stuff that's not going | ||
+ | to change much | ||
+ | """ | ||
+ | def __init__(self): | ||
+ | self.items = [ | ||
+ | (' | ||
+ | (' | ||
+ | (' | ||
+ | (' | ||
+ | (' | ||
+ | (' | ||
+ | (' | ||
+ | (' | ||
+ | (' | ||
+ | (' | ||
+ | (' | ||
+ | (' | ||
+ | (' | ||
+ | (' | ||
+ | (' | ||
+ | (' | ||
+ | (' | ||
+ | (' | ||
+ | (' | ||
+ | (' | ||
+ | (' | ||
+ | (' | ||
+ | (' | ||
+ | (' | ||
+ | (' | ||
+ | ] | ||
+ | |||
+ | def setCalName(self , name): | ||
+ | for i , item in enumerate(self.items): | ||
+ | if item[0] == ' | ||
+ | l = list(item) | ||
+ | l[1] = name | ||
+ | self.items[i] = tuple(l) | ||
+ | def getCalName(self): | ||
+ | for k , v in self.items: | ||
+ | if k == ' | ||
+ | return v | ||
+ | CalName = property(getCalName , setCalName) | ||
+ | | ||
+ | def getStart(self): | ||
+ | ret = '' | ||
+ | for k , v in self.items: | ||
+ | ret += ' | ||
+ | return ret | ||
+ | Start = property(getStart) | ||
+ | |||
+ | def getEnd(self): | ||
+ | return ' | ||
+ | End = property(getEnd) | ||
+ | |||
+ | |||
+ | class RssToIcal(object): | ||
+ | dateTpl = ' | ||
+ | cpDateTpl = ' | ||
+ | datetimeTpl = ' | ||
+ | |||
+ | def getUid(self , domain=' | ||
+ | return ' | ||
+ | |||
+ | def rssEntry2Vevent(self , entry , dt): | ||
+ | end = dt + timedelta(days=1) | ||
+ | now = datetime.utcnow() | ||
+ | desc = entry.description.replace(' | ||
+ | ret = ' | ||
+ | ret += ' | ||
+ | ret += ' | ||
+ | ret += ' | ||
+ | ret += ' | ||
+ | ret += ' | ||
+ | ret += ' | ||
+ | ret += ' | ||
+ | ret += ' | ||
+ | ret += ' | ||
+ | ret += ' | ||
+ | ret += ' | ||
+ | ret += ' | ||
+ | return ret | ||
+ | |||
+ | def convert(self , year , month): | ||
+ | """ | ||
+ | This takes a numeric year and month and finds all the city pages | ||
+ | events for that month and yields a list of Vevent strings | ||
+ | """ | ||
+ | year , month = (int(year) , int(month)) | ||
+ | delta = timedelta(days=1) | ||
+ | cpdt = datetime(year , month , 1 , tzinfo=UTC()) | ||
+ | while cpdt.month == month: | ||
+ | url = ' | ||
+ | dfd = feedparser.parse(url) | ||
+ | for e in dfd.entries: | ||
+ | yield self.rssEntry2Vevent(e , cpdt) | ||
+ | cpdt += delta | ||
+ | |||
+ | def getOpts(): | ||
+ | usage = ' | ||
+ | p = OptionParser(usage=usage) | ||
+ | p.add_option(' | ||
+ | default=' | ||
+ | help=' | ||
+ | ' | ||
+ | p.add_option(' | ||
+ | default='' | ||
+ | help=' | ||
+ | ' | ||
+ | opts , args = p.parse_args() | ||
+ | return (opts , args) | ||
+ | |||
+ | def getYearMonth(dateArg): | ||
+ | if not RE_YM.match(dateArg): | ||
+ | print >> sys.stderr , ' | ||
+ | ' | ||
+ | sys.exit(1) | ||
+ | year , month = [int(i) for i in dateArg.split(' | ||
+ | curYear = time.localtime().tm_year | ||
+ | curMonth = time.localtime().tm_mon | ||
+ | if year < curYear or year > curYear + 1: | ||
+ | print >> sys.stderr , ' | ||
+ | 'next, year only: %d' % year | ||
+ | sys.exit(2) | ||
+ | if month < 1 or month > 12: | ||
+ | print >> sys.stderr , ' | ||
+ | sys.exit(3) | ||
+ | if month < curMonth and year == curYear: | ||
+ | print >> sys.stderr , 'You can\'t get a calendar in the past' | ||
+ | sys.exit(4) | ||
+ | return (year , month) | ||
+ | |||
+ | def main(): | ||
+ | opts , args = getOpts() | ||
+ | outfh = None | ||
+ | if opts.outFile == ' | ||
+ | outfh = sys.stdout | ||
+ | else: | ||
+ | outfh = open(opts.outFile , ' | ||
+ | vcal = Vcal() | ||
+ | if opts.calName: | ||
+ | vcal.CalName = opts.calName | ||
+ | r2i = RssToIcal() | ||
+ | events = [] | ||
+ | ym = [] | ||
+ | for a in args: | ||
+ | ym.append(getYearMonth(a)) | ||
+ | outfh.write(vcal.Start) | ||
+ | for year , month in ym: | ||
+ | for vev in r2i.convert(year , month): | ||
+ | outfh.write(vev) | ||
+ | outfh.write(vcal.End) | ||
+ | if outfh != sys.stdout: | ||
+ | outfh.close() | ||
+ | |||
+ | if __name__ == ' | ||
+ | main() | ||
+ | </ | ||
+ | ====== Usage ====== | ||
+ | The script is quite simple to use. If you use the '' | ||
+ | < | ||
+ | $ ./ | ||
+ | Usage: cpc2ical.py [options] YYYY-MM [YYYY-MM [YYYY-MM ...]] | ||
+ | |||
+ | Options: | ||
+ | -h, --help | ||
+ | -o FILE, --output-file=FILE | ||
+ | Send the calendar output to FILE instead of stdout | ||
+ | [default: STDOUT] | ||
+ | -n CALNAME, --cal-name=CALNAME | ||
+ | Set the calendar name to a name of your choosing. | ||
+ | [default: City Pages] | ||
+ | </ | ||
+ | Essentially, | ||
+ | < | ||
+ | $ ./ | ||
+ | </ | ||
+ | That's about it. **NOTE: This will take a while to run so don't CTRL-C out of it prematurely!** | ||
+ | ===== Importing into Google Calendar ===== | ||
+ | This will be a very short description of how to import this info into a Google Calendar (as of September of 2010). | ||
+ | |||
+ | - Point your browser at http:// | ||
+ | - In the top, right corner, click on '' | ||
+ | - Now click on the '' | ||
+ | - Click the '' | ||
+ | - A javascript window should pop up allowing you to select your calendar file, '' | ||
+ | - Select the calendar you wish to import this into. Personally, I created a separate calendar just for the city pages stuff. | ||
+ | - Click the '' | ||
+ | - Go back to your calendar(s) and you should see listings for all events. | ||
+ | ====== Future TODO ====== | ||
+ | Here are some things that I may do in the future, but would also be a good exercise for others. | ||
+ | |||
+ | * Parse the description and create better VEVENTS for things which are ongoing (spanning many days) | ||
+ | * Parse the description for times and actually create VEVENTS that are not just simply "full day" events, but instead exist within the time period where the event actually takes place. | ||
+ | |||
programming/python/cpc2ical.txt · Last modified: 2010/09/30 17:12 by jay