Para quienes siguen esta bitácora, seguramente recordarán que me aceptaron en el NYC Half Marathon del 2013. Eso significa que me va a tocar entrenar este inverno, en esta ocasión quise probar los planes gratuitos de la gente de Runners World para ver que tan buenos son.
Después de registrarme y de colocar varios parámetros, esto fué más o menos lo que el plan me generó:
El plan básico de Runners World Smart Coach no soporta enviar correos electrónicos para recordarte acerca de tus entrenamientos; Esto es algo que puedo hacer yo sólo y no creo que justifique que pagué por la mejora al servicio pago, dado lo corto de la carrera (para el maratón es otra cosa y allí les recomiendo que paguen su suscripción).
Para tener mis recordatorios lo único 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:
#Macintosh:Documents josevnz$ vim half.txt
WEEK 1: 4 Mi
Sun Dec 30 Long Run Dist: 4 Mi @9:35
WEEK 2: 11 Mi
Tue Jan 1 Easy Run Dist: 4 Mi @9:36
Thu Jan 3 Easy Run Dist: 3 Mi @9:36
Sun Jan 6 Easy Run Dist: 4 Mi @9:36
WEEK 3: 12 Mi
Tue Jan 8 Easy Run Dist: 3 Mi @9:35
Thu Jan 10 Tempo Run Dist: 4 Mi, inc Warm; 2 Mi @ 7:59; Cool
Sun Jan 13 Long Run Dist: 5 Mi @9:35
WEEK 4: 10 Mi
Tue Jan 15 Easy Run Dist: 3 Mi @9:32
Thu Jan 17 Easy Run Dist: 3 Mi @9:32
Sun Jan 20 Easy Run Dist: 4 Mi @9:32
WEEK 5: 13 Mi
Tue Jan 22 Easy Run Dist: 4 Mi @9:32
Thu Jan 24 Tempo Run Dist: 4 Mi, inc Warm; 2 Mi @ 7:57; Cool
Sun Jan 27 Long Run Dist: 5 Mi @9:32
WEEK 6: 14 Mi
Tue Jan 29 Easy Run Dist: 4 Mi @9:30
Thu Jan 31 Speedwork Dist: 4 Mi, inc Warm; 2x1600 in 7:32 w/800 jogs; Cool
Sun Feb 3 Long Run Dist: 6 Mi @9:30
WEEK 7: 15 Mi
Tue Feb 5 Easy Run Dist: 3 Mi @9:27
Thu Feb 7 Tempo Run Dist: 4 Mi, inc Warm; 2 Mi @ 7:52; Cool
Fri Feb 8 Easy Run Dist: 2 Mi @9:27
Sun Feb 10 Long Run Dist: 6 Mi @9:27
WEEK 8: 13 Mi
Tue Feb 12 Easy Run Dist: 3 Mi @9:25
Thu Feb 14 Easy Run Dist: 3 Mi @9:25
Fri Feb 15 Easy Run Dist: 3 Mi @9:25
Sun Feb 17 Easy Run Dist: 4 Mi @9:25
WEEK 9: 17 Mi
Tue Feb 19 Easy Run Dist: 2 Mi @9:25
Thu Feb 21 Tempo Run Dist: 5 Mi, inc Warm; 3 Mi @ 7:54; Cool
Fri Feb 22 Easy Run Dist: 2 Mi @9:25
Sun Feb 24 Long Run Dist: 8 Mi @9:25
WEEK 10: 18 Mi
Tue Feb 26 Easy Run Dist: 3 Mi @9:22
Thu Feb 28 Speedwork Dist: 4 Mi, inc Warm; 2x1600 in 7:25 w/800 jogs; Cool
Fri Mar 1 Easy Run Dist: 2 Mi @9:22
Sun Mar 3 Long Run Dist: 9 Mi @9:22
WEEK 11: 19 Mi
Tue Mar 5 Easy Run Dist: 2 Mi @9:20
Thu Mar 7 Tempo Run Dist: 5 Mi, inc Warm; 3 Mi @ 7:50; Cool
Fri Mar 8 Easy Run Dist: 2 Mi @9:20
Sun Mar 10 Long Run Dist: 10 Mi @9:20
WEEK 12: 18 Mi
Tue Mar 12 Easy Run Dist: 2 Mi @9:17
Thu Mar 14 Speedwork Dist: 3 Mi, inc Warm; 1x1600 in 7:21 w/800 jogs; Cool
Sun Mar 17 Half Marathon Race Day 13.1 Mi @8:02 Time: 1:45:17
El formato es super fácil de digerir; Con un poco de ayuda para entender el formato de iCalendar, escribí un pequeño script en Python el cual toma el archivo de texto y lo convierte al formato adecuado:
#!/usr/bin/env python
# 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
# http://kodegeek.com/blog
import os, sys, re
from datetime import *
class Cal:
def __init__(self, params):
self.params = params
def __str__(self):
return '''BEGIN:VEVENT
DTEND;TZID=%(date)s
TRANSP:OPAQUE
SUMMARY:%(summary)s
DTSTART;TZID=%(date)s
SEQUENCE:4
BEGIN:VALARM
X-WR-ALARMUID:454BC86C-A39E-4C87-8CF9-7D79D80AC01B
TRIGGER:-PT1M
ATTACH;VALUE=URI:Basso
ACTION:AUDIO
END:VALARM
END:VEVENT''' % self.params
(Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec) = range(0, 12)
def parseLine(line, now, format):
if len(line) == 1 or re.search('WEEK', line):
return None
''' Parsing the data. Each line format looks like this:
Sun Dec 30 Long Run Dist: 4 Mi @9:35
Tue Jan 29 Easy Run Dist: 4 Mi @9:30
Thu Jan 31 Speedwork Dist: 4 Mi, inc Warm; 2x1600 in 7:32 w/800 jogs; Cool
Sun Mar 17 Half Marathon Race Day 13.1 Mi @8:02 Time: 1:45:17
'''
matcher = re.search('(.*)\s+Dist:(.*)', line.strip())
if matcher == None:
matcher = re.search('(.*)\s+Race Day(.*)', line.strip())
if matcher == None:
return None # Don't know how to handle this!
params = {}
# Runs finish and end the same day
tokens = matcher.group(1).strip().split(' ', -1)
cdate = ' '.join(tokens[:3]).strip()
time = "05:00"
if tokens[0] in [ "Sun", "Sat" ]:
time = "06:00"
# We add the year and start time to the date. Start time is the time I want to run
# We figure out the year as none of these plans are longer than 13 weeks
year = 2013 # Yes, it is hardcoded. This version of the script doesn't handle the special case of dates across years
# For date we expect something like: Sun Dec 30 2012 05:00
date = "%s %s %s" % (cdate, year, time)
ndate = datetime.strptime(date, "%a %b %d %Y %H:%M")
if ndate == None:
raise Exception("Unable to parse date, won't continue: '%s'" % date)
params['date'] = ndate.strftime(format)
desc = ' '.join(tokens[3:])
params['summary'] = '%s %s' % (desc.strip(), matcher.group(2).strip())
return Cal(params)
# You may want to override some defaults here
def writeEvents(cals, calendarName='Races', tz='America/New_York'):
vals = {}
vals['tz'] = tz
vals['calname'] = calendarName
print '''BEGIN:VCALENDAR
METHOD:PUBLISH
VERSION:2.0
X-WR-CALNAME:%(calname)s
PRODID:-//Apple Inc.//iCal 5.0.3//EN
X-APPLE-CALENDAR-COLOR:#B90E28
X-WR-TIMEZONE:%(tz)s
CALSCALE:GREGORIAN
BEGIN:VTIMEZONE
TZID:%(tz)s
BEGIN:DAYLIGHT
TZOFFSETFROM:-0500
RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU
DTSTART:20070311T020000
TZNAME:EDT
TZOFFSETTO:-0400
END:DAYLIGHT
BEGIN:STANDARD
TZOFFSETFROM:-0400
RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU
DTSTART:20071104T020000
TZNAME:EST
TZOFFSETTO:-0500
END:STANDARD
END:VTIMEZONE''' % vals
for cal in cals:
print "%s" % cal
print '''END:VCALENDAR'''
def main(args):
format = "America/New_York:%Y%m%dT%H%M00" # America/New_York:20130101T060000
now = date.today()
fh = open(args[0], 'r')
cals = []
for line in fh.xreadlines():
cal = parseLine(line, now, format)
if cal != None:
cals.append(cal)
writeEvents(cals)
if __name__ == "__main__":
main(sys.argv[1:])
# End of script
Para correrlo sólo tiene que escribir lo siguiente:
bin/runnersworld_to_ical.py ~/Documents/half.ics
Después sólo hay que importarlo en la aplicación Ical en OSX.
Espero que le sea de utilidad, mañana me toca correr en nieve (a 25F), así que aún no ando seguro si me toca usar un Treadmil o si voy a correr en la carretera. Si sólo eso se pudiera resolver con un pequeño programita…