Qalculate time hacks
Anarcat recently wrote about Qalculate, and I think I’m a convert, even though I’ve only barely scratched the surface.
The thing I almost immediately started using it for is time calculations.
When I started tracking my time, I
quickly found that Timewarrior was good at
keeping all the data I needed, but I often found myself extracting bits of
it and reprocessing it in variously clumsy ways. For example, I often don’t
finish a task in one sitting; maybe I take breaks, or I switch back and
forth between a couple of different tasks. The raw output of timew
summary
is a bit clumsy for this, as it shows each chunk of time spent as
a separate row:
$ timew summary 2025-02-18 Debian Wk Date Day Tags Start End Time Total W8 2025-02-18 Tue CVE-2025-26465, Debian, 9:41:44 10:24:17 0:42:33 next, openssh Debian, FTBFS with GCC-15, 10:24:17 10:27:12 0:02:55 icoutils Debian, FTBFS with GCC-15, 11:50:05 11:57:25 0:07:20 kali Debian, Upgrade to 0.67, 11:58:21 12:12:41 0:14:20 python_holidays Debian, FTBFS with GCC-15, 12:14:15 12:33:19 0:19:04 vigor Debian, FTBFS with GCC-15, 12:39:02 12:39:38 0:00:36 python_setproctitle Debian, Upgrade to 1.3.4, 12:39:39 12:46:05 0:06:26 python_setproctitle Debian, FTBFS with GCC-15, 12:48:28 12:49:42 0:01:14 python_setproctitle Debian, Upgrade to 3.4.1, 12:52:07 13:02:27 0:10:20 1:44:48 python_charset_normalizer 1:44:48
So I wrote this Python program to help me:
#! /usr/bin/python3 """ Summarize timewarrior data, grouped and sorted by time spent. """ import json import subprocess from argparse import ArgumentParser, RawDescriptionHelpFormatter from collections import defaultdict from datetime import datetime, timedelta, timezone from operator import itemgetter from rich import box, print from rich.table import Table parser = ArgumentParser( description=__doc__, formatter_class=RawDescriptionHelpFormatter ) parser.add_argument("-t", "--only-total", default=False, action="store_true") parser.add_argument( "range", nargs="?", default=":today", help="Time range (usually a hint, e.g. :lastweek)", ) parser.add_argument("tag", nargs="*", help="Tags to filter by") args = parser.parse_args() entries: defaultdict[str, timedelta] = defaultdict(timedelta) now = datetime.now(timezone.utc) for entry in json.loads( subprocess.run( ["timew", "export", args.range, *args.tag], check=True, capture_output=True, text=True, ).stdout ): start = datetime.fromisoformat(entry["start"]) if "end" in entry: end = datetime.fromisoformat(entry["end"]) else: end = now entries[", ".join(entry["tags"])] += end - start if not args.only_total: table = Table(box=box.SIMPLE, highlight=True) table.add_column("Tags") table.add_column("Time", justify="right") for tags, time in sorted(entries.items(), key=itemgetter(1), reverse=True): table.add_row(tags, str(time)) print(table) total = sum(entries.values(), start=timedelta()) hours, rest = divmod(total, timedelta(hours=1)) minutes, rest = divmod(rest, timedelta(minutes=1)) seconds = rest.seconds print(f"Total time: {hours:02}:{minutes:02}:{seconds:02}")
$ summarize-time 2025-02-18 Debian Tags Time ─────────────────────────────────────────────────────────────── CVE-2025-26465, Debian, next, openssh 0:42:33 Debian, FTBFS with GCC-15, vigor 0:19:04 Debian, Upgrade to 0.67, python_holidays 0:14:20 Debian, Upgrade to 3.4.1, python_charset_normalizer 0:10:20 Debian, FTBFS with GCC-15, kali 0:07:20 Debian, Upgrade to 1.3.4, python_setproctitle 0:06:26 Debian, FTBFS with GCC-15, icoutils 0:02:55 Debian, FTBFS with GCC-15, python_setproctitle 0:01:50 Total time: 01:44:48
Much nicer. But that only helps with some of my reporting. At the end of a
month, I have to work out how much time to bill Freexian for and fill out a
timesheet, and for various reasons those queries don’t correspond to single
timew
tags: they sometimes correspond to the sum of all time spent on
multiple tags, or to the time spent on one tag minus the time spent on
another tag, or similar. As a result I quite often have to do basic
arithmetic on time intervals; but that’s surprisingly annoying! I didn’t
previously have good tools for that, and was reduced to doing things like
str(timedelta(hours=..., minutes=..., seconds=...) + ...)
in Python,
which gets old fast.
Instead:
$ qalc '62:46:30 - 51:02:42 to time' (225990 / 3600) − (183762 / 3600) = 11:43:48
I also often want to work out how much of my time I’ve spent on Debian work this month so far, since Freexian pays me for up to 20% of my work time on Debian; if I’m under that then I might want to prioritize more Debian projects, and if I’m over then I should be prioritizing more Freexian projects as otherwise I’m not going to get paid for that time.
$ summarize-time -t :month Freexian Total time: 69:19:42 $ summarize-time -t :month Debian Total time: 24:05:30 $ qalc '24:05:30 / (24:05:30 + 69:19:42) to %' (86730 / 3600) / ((86730 / 3600) + (249582 / 3600)) ≈ 25.78855349%
I love it.
Comments
With an account on the Fediverse or Mastodon, you can respond to this post. Since Mastodon is decentralized, you can use your existing account hosted by another Mastodon server or compatible platform if you don't have an account on this one. Known non-private replies are displayed below.
Learn how this is implemented here.