For a long time I haven't been able to find a decent replacement or alternative to KTimeTracker. But I've now succeeded: TimeCult.
It is currently lacking an installer for Linux, but it can be made to work with Linux easily.
I also found that it uses a very easy-to-deciper XML format, and, with the help of vobject, the KTimeTracker ical files are only slightly harder to figure out, so I created a Python script that will do the conversion.
Use the script like this:
./ics_to_timecult.py ~/.kde/share/apps/ktimetracker/ktimetracker.ics > out.tmt
It's here for reference - dependencies are vobject and elementtree:
#!/usr/bin/env python # Quick and dirty script to convert ktimetracker.ics files # into TimeCult files. import os.path import sys import time import uuid import elementtree.ElementTree as ET import vobject # Structures to store the tree of data. Use native Python data types, and # convert to what TimeCult expects when we 'render' class Struct(object): def __init__(self, **kwargs): self.__dict__.update(kwargs) class TimeCult(Struct): # <timecult> # Eventual attributes: # name # projectTree # timeLog def toXML(self): root = ET.XML('<timecult appVersion="0.12" fileVersion="10" />') root.attrib['uuid'] = str(uuid.uuid1()) root.attrib['name'] = self.name pt = ET.Element('projectTree') for n in self.projectTree: pt.append(n.toXML()) root.append(pt) tl = ET.Element('timeLog') for tr in self.timeLog: tl.append(tr.toXML()) root.append(tl) return root class Node(Struct): # <project> and <task> # Eventually will have these attributes: # vtodo # summary # uid # created # parent # children # type = "project|task" # finished def __repr__(self): return "<Node: uid=%s summary=%r, parent=%r>" % ( self.uid, self.summary, self.parent.summary if self.parent is not None else None) def toXML(self): e = ET.Element(self.type) e.attrib['id'] = self.id e.attrib['name'] = self.summary e.attrib['created'] = str(int(time.mktime(self.created.timetuple()) * 1000)) if (self.type == "task"): assert len(self.children) == 0 e.attrib['status'] = 'finished' if self.finished else 'inProgress' else: for c in self.children: e.append(c.toXML()) return e class TimeRec(Struct): # <timerec> # Eventual attributes: # startTime # datetime # duration # seconds # task # Node def toXML(self): e = ET.Element("timeRec") e.attrib['taskId'] = self.taskId e.attrib['startTime'] = str(int(time.mktime(self.startTime.timetuple()) * 1000)) e.attrib['duration'] = str(int(self.duration * 1000)) e.attrib['notes'] = "" return e def convert(filename): f = vobject.readOne(file(filename).read()) # First parse Todos nodes = {} for vtodo in f.vtodo_list: n = Node(vtodo = vtodo, uid=vtodo.uid.value, summary=vtodo.summary.value, created=vtodo.created.value, finished=vtodo.contents['percent-complete'][0].value == "100" ) nodes[n.uid] = n children = {} for uid, node in nodes.items(): vtodo = node.vtodo try: related_uid = vtodo.contents['related-to'][0].value node.parent = nodes[related_uid] children.setdefault(related_uid, []).append(node) except KeyError: node.parent = None for uid, node in nodes.items(): node.children = children.get(uid, []) if len(node.children) == 0: node.type = "task" else: node.type = "project" autouid = 0 # For newly created Tasks # Now find the time information in the ICS file time_log = [] for vevent in f.vevent_list: related_uid = vevent.contents['related-to'][0].value task = nodes[related_uid] if task.type == "project": # TimeCult can only cope with time events being against 'leaf' nodes # in the tree. So we need to adjust and create an additional leaf project = task undefined_l = [t for t in project.children if t.uid.startswith('autouid')] if len(undefined_l) > 0: task = undefined_l[0] else: task = Node(uid='autouid-%d' % autouid, summary='other', parent=project, type="task", created=project.created, finished=False, children=[]) autouid += 1 nodes[task.uid] = task project.children.insert(0, task) dtstart = vevent.dtstart.value dtend = vevent.dtend.value if dtstart.tzinfo is None: dtstart = dtstart.replace(tzinfo=dtend.tzinfo) if dtend.tzinfo is None: dtend = dtend.replace(tzinfo=dtstart.tzinfo) time_rec = TimeRec(startTime=dtstart, duration=(dtend - dtstart).total_seconds(), task=task) time_log.append(time_rec) # Now need to assign IDs taskId = 0 for u, n in nodes.items(): taskId += 1 n.id = str(taskId) for tr in time_log: tr.taskId = tr.task.id return TimeCult( name=os.path.basename(filename), projectTree=[n for u, n in nodes.items() if n.parent is None], timeLog=time_log) if __name__ == '__main__': filename = sys.argv[1] timecult = convert(filename) sys.stdout.write( """<?xml version="1.0" encoding="UTF-8"?>""" + ET.tostring(timecult.toXML()))