{"id":3508,"date":"2012-12-29T20:40:32","date_gmt":"2012-12-30T01:40:32","guid":{"rendered":"http:\/\/kodegeek.com\/blog\/?p=3508"},"modified":"2014-07-04T10:55:12","modified_gmt":"2014-07-04T14:55:12","slug":"creando-entradas-para-mi-calendario-en-ical-usando-python","status":"publish","type":"post","link":"http:\/\/kodegeek.com\/blog\/2012\/12\/29\/creando-entradas-para-mi-calendario-en-ical-usando-python\/","title":{"rendered":"Creando entradas para mi calendario en Ical usando Python"},"content":{"rendered":"<figure id=\"attachment_3509\" aria-describedby=\"caption-attachment-3509\" style=\"width: 591px\" class=\"wp-caption alignnone\"><a href=\"http:\/\/kodegeek.com\/blog\/2012\/12\/29\/creando-entradas-para-mi-calendario-en-ical-usando-python\/screen-shot-2012-12-29-at-6-35-46-am\/\" rel=\"attachment wp-att-3509\"><img decoding=\"async\" loading=\"lazy\" class=\" wp-image-3509 \" alt=\"Runners World Half Marathon training plan\" src=\"http:\/\/kodegeek.com\/blog\/wp-content\/uploads\/2012\/12\/Screen-Shot-2012-12-29-at-6.35.46-AM.png\" width=\"591\" height=\"413\" srcset=\"http:\/\/kodegeek.com\/blog\/wp-content\/uploads\/2012\/12\/Screen-Shot-2012-12-29-at-6.35.46-AM.png 844w, http:\/\/kodegeek.com\/blog\/wp-content\/uploads\/2012\/12\/Screen-Shot-2012-12-29-at-6.35.46-AM-300x209.png 300w\" sizes=\"(max-width: 591px) 100vw, 591px\" \/><\/a><figcaption id=\"caption-attachment-3509\" class=\"wp-caption-text\">Runners World Half Marathon training plan<\/figcaption><\/figure>\n<p>Para quienes siguen esta bit\u00e1cora, seguramente recordar\u00e1n que me aceptaron en el <a href=\"http:\/\/kodegeek.com\/blog\/2012\/12\/21\/me-aceptaron-en-el-nyc-half-marathon-del-2013\/\">NYC Half Marathon del 2013<\/a>. Eso significa que me va a tocar entrenar este inverno, en esta ocasi\u00f3n quise probar los planes gratuitos de la gente de Runners World para ver que tan buenos son.<\/p>\n<p>Despu\u00e9s de registrarme y de colocar varios par\u00e1metros, esto fu\u00e9 m\u00e1s o menos lo que el plan me gener\u00f3:<\/p>\n<p>El plan b\u00e1sico de<a href=\"http:\/\/smartcoach.runnersworld.com\/smartcoach\/my_plan.jsp\" target=\"_blank\"> Runners World Smart Coach <\/a>no soporta enviar correos electr\u00f3nicos para recordarte acerca de tus entrenamientos; Esto es algo que puedo hacer yo s\u00f3lo y no creo que justifique que pagu\u00e9 por la mejora al servicio pago, dado lo corto de la carrera (para el marat\u00f3n es otra cosa y all\u00ed les recomiendo que paguen su suscripci\u00f3n).<\/p>\n<p>Para tener mis recordatorios lo \u00fanico que tengo que hacer es guardar el plan en formato de texto plano, simplemente seleccionamos la tabla del sitio web y la guardamos en un archivo de texto:<\/p>\n<pre lang=\"text\">\r\n#Macintosh:Documents josevnz$ vim half.txt\r\n\r\nWEEK 1: 4 Mi\r\nSun     Dec 30  Long Run        Dist: 4 Mi @9:35\r\n\r\nWEEK 2: 11 Mi\r\nTue     Jan 1   Easy Run        Dist: 4 Mi @9:36\r\nThu     Jan 3   Easy Run        Dist: 3 Mi @9:36\r\nSun     Jan 6   Easy Run        Dist: 4 Mi @9:36\r\n\r\nWEEK 3: 12 Mi\r\nTue     Jan 8   Easy Run        Dist: 3 Mi @9:35\r\nThu     Jan 10  Tempo Run       Dist: 4 Mi, inc Warm; 2 Mi @ 7:59; Cool\r\nSun     Jan 13  Long Run        Dist: 5 Mi @9:35\r\n\r\nWEEK 4: 10 Mi\r\nTue     Jan 15  Easy Run        Dist: 3 Mi @9:32\r\nThu     Jan 17  Easy Run        Dist: 3 Mi @9:32\r\nSun     Jan 20  Easy Run        Dist: 4 Mi @9:32\r\n\r\nWEEK 5: 13 Mi\r\nTue     Jan 22  Easy Run        Dist: 4 Mi @9:32\r\nThu     Jan 24  Tempo Run       Dist: 4 Mi, inc Warm; 2 Mi @ 7:57; Cool\r\nSun     Jan 27  Long Run        Dist: 5 Mi @9:32\r\n\r\nWEEK 6: 14 Mi\r\nTue     Jan 29  Easy Run        Dist: 4 Mi @9:30\r\nThu     Jan 31  Speedwork       Dist: 4 Mi, inc Warm; 2x1600 in 7:32 w\/800 jogs; Cool\r\nSun     Feb 3   Long Run        Dist: 6 Mi @9:30\r\n\r\nWEEK 7: 15 Mi\r\nTue     Feb 5   Easy Run        Dist: 3 Mi @9:27\r\nThu     Feb 7   Tempo Run       Dist: 4 Mi, inc Warm; 2 Mi @ 7:52; Cool\r\nFri     Feb 8   Easy Run        Dist: 2 Mi @9:27\r\nSun     Feb 10  Long Run        Dist: 6 Mi @9:27\r\n\r\nWEEK 8: 13 Mi\r\nTue     Feb 12  Easy Run        Dist: 3 Mi @9:25\r\nThu     Feb 14  Easy Run        Dist: 3 Mi @9:25\r\nFri     Feb 15  Easy Run        Dist: 3 Mi @9:25\r\nSun     Feb 17  Easy Run        Dist: 4 Mi @9:25\r\n\r\nWEEK 9: 17 Mi\r\nTue     Feb 19  Easy Run        Dist: 2 Mi @9:25\r\nThu     Feb 21  Tempo Run       Dist: 5 Mi, inc Warm; 3 Mi @ 7:54; Cool\r\nFri     Feb 22  Easy Run        Dist: 2 Mi @9:25\r\nSun     Feb 24  Long Run        Dist: 8 Mi @9:25\r\n\r\nWEEK 10: 18 Mi\r\nTue     Feb 26  Easy Run        Dist: 3 Mi @9:22\r\nThu     Feb 28  Speedwork       Dist: 4 Mi, inc Warm; 2x1600 in 7:25 w\/800 jogs; Cool\r\nFri     Mar 1   Easy Run        Dist: 2 Mi @9:22\r\nSun     Mar 3   Long Run        Dist: 9 Mi @9:22\r\n\r\nWEEK 11: 19 Mi\r\nTue     Mar 5   Easy Run        Dist: 2 Mi @9:20\r\nThu     Mar 7   Tempo Run       Dist: 5 Mi, inc Warm; 3 Mi @ 7:50; Cool\r\nFri     Mar 8   Easy Run        Dist: 2 Mi @9:20\r\nSun     Mar 10  Long Run        Dist: 10 Mi @9:20\r\n\r\nWEEK 12: 18 Mi\r\nTue     Mar 12  Easy Run        Dist: 2 Mi @9:17\r\nThu     Mar 14  Speedwork       Dist: 3 Mi, inc Warm; 1x1600 in 7:21 w\/800 jogs; Cool\r\nSun     Mar 17  Half Marathon Race Day  13.1 Mi @8:02 Time: 1:45:17\r\n<\/pre>\n<p>El formato es super f\u00e1cil de digerir; Con un poco de ayuda para entender el formato de <a href=\"http:\/\/en.wikipedia.org\/wiki\/ICalendar\">iCalendar<\/a>, escrib\u00ed un peque\u00f1o script en Python el cual toma el archivo de texto y lo convierte al formato adecuado:<\/p>\n<pre lang=\"python\">\r\n#!\/usr\/bin\/env python\r\n# Simple script to parse output from the Runners World Smart Coach calendar to convert it to Ical entries. PLEASE CONSIDER BUYING THEIR PLAN, IS WORTH IT\r\n# http:\/\/kodegeek.com\/blog\r\nimport os, sys, re\r\nfrom datetime import *\r\n\r\nclass Cal:\r\n        def __init__(self, params):\r\n                self.params = params\r\n\r\n        def __str__(self):\r\n                return '''BEGIN:VEVENT\r\nDTEND;TZID=%(date)s\r\nTRANSP:OPAQUE\r\nSUMMARY:%(summary)s\r\nDTSTART;TZID=%(date)s\r\nSEQUENCE:4\r\nBEGIN:VALARM\r\nX-WR-ALARMUID:454BC86C-A39E-4C87-8CF9-7D79D80AC01B\r\nTRIGGER:-PT1M\r\nATTACH;VALUE=URI:Basso\r\nACTION:AUDIO\r\nEND:VALARM\r\nEND:VEVENT''' % self.params\r\n\r\n(Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec) = range(0, 12)\r\n\r\ndef parseLine(line, now, format):\r\n        if len(line) == 1 or re.search('WEEK', line):\r\n                return None\r\n        ''' Parsing the data. Each line format looks like this:\r\n        Sun     Dec 30  Long Run        Dist: 4 Mi @9:35\r\n        Tue     Jan 29  Easy Run        Dist: 4 Mi @9:30\r\n        Thu     Jan 31  Speedwork       Dist: 4 Mi, inc Warm; 2x1600 in 7:32 w\/800 jogs; Cool\r\n        Sun     Mar 17  Half Marathon Race Day  13.1 Mi @8:02 Time: 1:45:17\r\n        '''\r\n        matcher = re.search('(.*)\\s+Dist:(.*)', line.strip())\r\n        if matcher == None:\r\n                matcher = re.search('(.*)\\s+Race Day(.*)', line.strip())\r\n                if matcher == None:\r\n                        return None # Don't know how to handle this!\r\n        params = {}\r\n        # Runs finish and end the same day\r\n        tokens = matcher.group(1).strip().split(' ', -1)\r\n        cdate = ' '.join(tokens[:3]).strip()\r\n        time = \"05:00\"\r\n        if tokens[0] in [ \"Sun\", \"Sat\" ]:\r\n                time = \"06:00\"\r\n        # We add the year and start time to the date. Start time is the time I want to run\r\n        # We figure out the year as none of these plans are longer than 13 weeks\r\n        year = 2013 # Yes, it is hardcoded. This version of the script doesn't handle the special case of dates across years\r\n\r\n        # For date we expect something like: Sun Dec 30 2012 05:00\r\n        date = \"%s %s %s\" % (cdate, year, time)\r\n        ndate = datetime.strptime(date, \"%a %b %d %Y %H:%M\")\r\n        if ndate == None:\r\n                raise Exception(\"Unable to parse date, won't continue: '%s'\" % date)\r\n        params['date'] = ndate.strftime(format)\r\n        desc = ' '.join(tokens[3:])\r\n        params['summary'] = '%s %s' % (desc.strip(), matcher.group(2).strip())\r\n        return Cal(params)\r\n\r\n# You may want to override some defaults here\r\ndef writeEvents(cals, calendarName='Races', tz='America\/New_York'):\r\n        vals = {}\r\n        vals['tz'] = tz\r\n        vals['calname'] = calendarName\r\n        print '''BEGIN:VCALENDAR\r\nMETHOD:PUBLISH\r\nVERSION:2.0\r\nX-WR-CALNAME:%(calname)s\r\nPRODID:-\/\/Apple Inc.\/\/iCal 5.0.3\/\/EN\r\nX-APPLE-CALENDAR-COLOR:#B90E28\r\nX-WR-TIMEZONE:%(tz)s\r\nCALSCALE:GREGORIAN\r\nBEGIN:VTIMEZONE\r\nTZID:%(tz)s\r\nBEGIN:DAYLIGHT\r\nTZOFFSETFROM:-0500\r\nRRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU\r\nDTSTART:20070311T020000\r\nTZNAME:EDT\r\nTZOFFSETTO:-0400\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETFROM:-0400\r\nRRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU\r\nDTSTART:20071104T020000\r\nTZNAME:EST\r\nTZOFFSETTO:-0500\r\nEND:STANDARD\r\nEND:VTIMEZONE''' % vals\r\n        for cal in cals:\r\n                print \"%s\" % cal\r\n        print '''END:VCALENDAR'''\r\n\r\ndef main(args):\r\n        format = \"America\/New_York:%Y%m%dT%H%M00\" # America\/New_York:20130101T060000\r\n        now = date.today()\r\n        fh = open(args[0], 'r')\r\n        cals = []\r\n        for line in fh.xreadlines():\r\n                cal = parseLine(line, now, format)\r\n                if cal != None:\r\n                        cals.append(cal)\r\n        writeEvents(cals)\r\n\r\nif __name__ == \"__main__\":\r\n        main(sys.argv[1:])\r\n# End of script\r\n<\/pre>\n<p>Para correrlo s\u00f3lo tiene que escribir lo siguiente:<br \/>\n<code>bin\/runnersworld_to_ical.py ~\/Documents\/half.ics<\/code><\/p>\n<p>Despu\u00e9s s\u00f3lo hay que importarlo en la aplicaci\u00f3n Ical en OSX.<\/p>\n<p>Espero que le sea de utilidad, ma\u00f1ana me toca correr en nieve (a 25F), as\u00ed que a\u00fan no ando seguro si me toca usar un Treadmil o si voy a correr en la carretera. Si s\u00f3lo eso se pudiera resolver con un peque\u00f1o programita&#8230;<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Para quienes siguen esta bit\u00e1cora, seguramente recordar\u00e1n que me aceptaron en el NYC Half Marathon del 2013. Eso significa que me va a tocar entrenar este inverno, en esta ocasi\u00f3n quise probar los planes gratuitos de la gente de Runners World para ver que tan buenos son. Despu\u00e9s de registrarme y de colocar varios par\u00e1metros, <a class=\"read-more\" href=\"http:\/\/kodegeek.com\/blog\/2012\/12\/29\/creando-entradas-para-mi-calendario-en-ical-usando-python\/\">[&hellip;]<\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":[],"categories":[194],"tags":[],"_links":{"self":[{"href":"http:\/\/kodegeek.com\/blog\/wp-json\/wp\/v2\/posts\/3508"}],"collection":[{"href":"http:\/\/kodegeek.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/kodegeek.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/kodegeek.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"http:\/\/kodegeek.com\/blog\/wp-json\/wp\/v2\/comments?post=3508"}],"version-history":[{"count":15,"href":"http:\/\/kodegeek.com\/blog\/wp-json\/wp\/v2\/posts\/3508\/revisions"}],"predecessor-version":[{"id":3673,"href":"http:\/\/kodegeek.com\/blog\/wp-json\/wp\/v2\/posts\/3508\/revisions\/3673"}],"wp:attachment":[{"href":"http:\/\/kodegeek.com\/blog\/wp-json\/wp\/v2\/media?parent=3508"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/kodegeek.com\/blog\/wp-json\/wp\/v2\/categories?post=3508"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/kodegeek.com\/blog\/wp-json\/wp\/v2\/tags?post=3508"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}