For the past 8 years, I've been working remotely on contract jobs. For clients who pay by the hour, I need to send invoices every two weeks. I tried various invoicing applications before eventually settling on Google Docs.
Then I discovered Wunderlist, a nice to-do list application. I created Shopping and TODO lists and began using it daily. It quickly became a habit for me to record tasks there. I realized it would be perfect for tracking my billable hours as well.
I use the following format for tracking time in tasks:
<hours> hours TASK-1
<minutes> minutes TASK-2
<days> days TASK-3
After a month of tracking my time this way, I wanted to download this data and calculate the hours I'd spent during the month. So I created a script to generate a CSV report of my hours.
import wunderpy2from dateutil import parserfrom datetime import datefrom csv import writerACCESS_TOKEN = '<WUNDERLIST_ACCESS_TOKEN>'CLIENT_ID = '<WUNDERLIST_CLIENT_ID>'PROJECT_BOARD = 'LIST_TITLE'HOUR_RATE = 1def __build_hour(task):'''Parses task titles in formats like:dict(title='1 hour CORE-1234')dict(title='5 hours CORE-4321')dict(title='1 minute CORE-1234')'''columns = task['title'].strip().split(' ')time, measure, task_id = columnsif measure == 'hour' or measure == 'hours':hours = float(time)elif measure == 'minute' or measure == 'minutes':hours = float(time) / 60elif measure == 'day' or measure == 'days':hours = 24 * float(time)else:raise ValueError('Unknown time measure in task title: ' + task['title'])return dict(task_id=task_id,created_at=parser.parse(task['created_at']),hours=hours)def __main__():api = wunderpy2.WunderApi()client = api.get_client(ACCESS_TOKEN, CLIENT_ID)board = [l for l in client.get_lists() if l['title'] == PROJECT_BOARD][0]active_tasks = [t for t in client.get_tasks(board['id']) if not t['completed']]report = [__build_hour(task) for task in active_tasks]estimated_earnings = sum([task['hours'] for task in report]) * HOUR_RATEprint(estimated_earnings)with open(f"report-{date.today()}.csv", 'w+') as f:w = writer(f)rows = [[task['task_id'], task['hours'], task['created_at'].date()]for task in report]w.writerows(rows)for task in active_tasks:client.update_task(task['id'], task['revision'], completed=True)# TODO: make monthly output report to pdf file based on created_at field.if __name__ == '__main__':__main__()