Skip to content

Commit 8e7ed9a

Browse files
committed
First prototype
1 parent eb24297 commit 8e7ed9a

2 files changed

Lines changed: 57 additions & 0 deletions

File tree

tcx2ics/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
from .tcx2ics import Tcx2Ics
2+
3+
__all__ = ["Tcx2Ics"]

tcx2ics/tcx2ics.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
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

Comments
 (0)