|
| 1 | +from datetime import datetime, timedelta, timezone |
| 2 | +from pathlib import Path |
| 3 | +from tcxreader.tcxreader import TCXReader |
| 4 | + |
| 5 | + |
| 6 | +class Tcx2Ics: |
| 7 | + def __init__(self): |
| 8 | + self.reader = TCXReader() |
| 9 | + |
| 10 | + def convert(self, tcx_file: str, ics_file: str): |
| 11 | + ex = self.reader.read(tcx_file) |
| 12 | + |
| 13 | + sport = getattr(ex, "activity_type", None) or getattr(ex, "sport", None) or "Workout" |
| 14 | + start = getattr(ex, "start_time", None) |
| 15 | + duration = float(getattr(ex, "duration", 0) or 0) # seconds |
| 16 | + distance = float(getattr(ex, "distance", 0) or 0) # meters |
| 17 | + |
| 18 | + if not isinstance(start, datetime): |
| 19 | + raise ValueError("No start_time found in TCX") |
| 20 | + |
| 21 | + if start.tzinfo is None: |
| 22 | + start = start.replace(tzinfo=timezone.utc) |
| 23 | + else: |
| 24 | + start = start.astimezone(timezone.utc) |
| 25 | + |
| 26 | + end = start + timedelta(seconds=duration) |
| 27 | + |
| 28 | + dist_km = distance / 1000.0 |
| 29 | + dur_h = int(duration) // 3600 |
| 30 | + dur_m = (int(duration) % 3600) // 60 |
| 31 | + dur_s = int(duration) % 60 |
| 32 | + |
| 33 | + summary = f"{sport} - {dist_km:.2f} km" |
| 34 | + desc = f"Sport: {sport}\\nTotal duration: {dur_h}:{dur_m:02d}:{dur_s:02d}\\nDistance: {dist_km:.2f} km" |
| 35 | + |
| 36 | + uid = f"{Path(tcx_file).stem}-{start.strftime('%Y%m%dT%H%M%SZ')}@tcx2ics" |
| 37 | + now = datetime.now(timezone.utc).strftime("%Y%m%dT%H%M%SZ") |
| 38 | + |
| 39 | + ics = ( |
| 40 | + "BEGIN:VCALENDAR\r\n" |
| 41 | + "VERSION:2.0\r\n" |
| 42 | + "PRODID:-//tcx2ics//EN\r\n" |
| 43 | + "BEGIN:VEVENT\r\n" |
| 44 | + f"UID:{uid}\r\n" |
| 45 | + f"DTSTAMP:{now}\r\n" |
| 46 | + f"DTSTART:{start.strftime('%Y%m%dT%H%M%SZ')}\r\n" |
| 47 | + f"DTEND:{end.strftime('%Y%m%dT%H%M%SZ')}\r\n" |
| 48 | + f"SUMMARY:{summary}\r\n" |
| 49 | + f"DESCRIPTION:{desc}\r\n" |
| 50 | + "END:VEVENT\r\n" |
| 51 | + "END:VCALENDAR\r\n" |
| 52 | + ) |
| 53 | + |
| 54 | + Path(ics_file).write_text(ics, encoding="utf-8") |
0 commit comments