Created
May 21, 2021 15:02
-
-
Save Terrance/a724bc2c750b4397d3e36b3a9ca003f0 to your computer and use it in GitHub Desktop.
Script to convert from Sleep as Android's backup CSV file (the path assuming a Termux environment) to CalDAV-compatible VEVENT files.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
from csv import DictReader | |
from datetime import datetime, timedelta | |
import os.path | |
from pprint import pprint | |
import re | |
import sys | |
from icalendar import Calendar, Event, vDDDTypes | |
import pytz | |
EXPORT = os.path.expanduser("~/storage/shared/sleep-data/sleep-export.csv") | |
vDDDTypes.__eq__ = lambda self, other: self.dt == other.dt | |
def parse(sleep): | |
header = None | |
for line in sleep: | |
if line.startswith("Id,"): | |
header = line | |
elif line.startswith(","): | |
continue | |
else: | |
yield [header, line] | |
def date(value, tz): | |
dt = datetime.strptime(value, "%d. %m. %Y %H:%M") | |
adt = tz.localize(dt).astimezone(pytz.utc) | |
return adt.strftime("%Y%m%dT%H%M%SZ") | |
def main(caldir): | |
with open(EXPORT) as sleep: | |
for pair in parse(sleep): | |
reader = DictReader(pair) | |
item = next(reader) | |
uid = "{}@sleep.urbandroid.com".format(item["Id"]) | |
path = os.path.join(caldir, "{}.ics".format(uid)) | |
tz = pytz.timezone(item["Tz"]) | |
event = Event() | |
event["uid"] = uid | |
event["summary"] = "Sleep" | |
tags = re.findall("#\S+", item["Comment"]) | |
mins = float(item["Hours"]) * 60 | |
deep = float(item["DeepSleep"]) | |
meta = {"Rating": item["Rating"], | |
"Duration": "{:.0f}:{:.0f}".format(mins // 60, mins % 60), | |
"Deep sleep": "{:.0f}%".format(deep * 100), | |
"Cycles": item["Cycles"], | |
"Tags": " ".join(tags)} | |
description = "\n".join("{}: {}".format(k, v) for k, v in meta.items() if v) | |
comment = re.sub("#\S+", "", item["Comment"]).strip() | |
if comment: | |
description = "{}\n\n{}".format(description, comment) | |
event["description"] = description | |
event["dtstart"] = date(item["From"], tz) | |
event["dtend"] = date(item["To"], tz) | |
geo = item["Geo"] | |
place = None | |
for tag in tags: | |
if tag == "#home" or tag.startswith("#geo"): | |
place = tag[1:].title() | |
break | |
event["location"] = "{} ({})".format(place, geo) if place and geo else (place or geo) | |
if os.path.exists(path): | |
with open(path, "rb") as old: | |
oldcal = Calendar.from_ical(old.read()) | |
oldevent = oldcal.walk("vevent")[0] | |
del oldevent["created"] | |
if Event.from_ical(event.to_ical()) == oldevent: | |
continue | |
print(item["Id"]) | |
event["created"] = datetime.utcnow().strftime("%Y%m%dT%H%M%SZ") | |
cal = Calendar() | |
cal.add_component(event) | |
with open(os.path.join(caldir, "{}.ics".format(uid)), "wb") as out: | |
out.write(cal.to_ical()) | |
if __name__ == "__main__": | |
if len(sys.argv) != 2: | |
print("Usage: {} <caldir>".format(os.path.basename(sys.argv[0]))) | |
exit(1) | |
main(*sys.argv[1:]) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment