Skip to content

Instantly share code, notes, and snippets.

@Terrance
Created May 21, 2021 15:02
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Terrance/a724bc2c750b4397d3e36b3a9ca003f0 to your computer and use it in GitHub Desktop.
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.
#!/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