
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.