chiark / gitweb /
Qalculate time hacks
authorColin Watson <cjwatson@debian.org>
Sun, 23 Feb 2025 20:08:34 +0000 (20:08 +0000)
committerColin Watson <cjwatson@debian.org>
Sun, 23 Feb 2025 20:08:34 +0000 (20:08 +0000)
content/qalculate-time-hacks.rst [new file with mode: 0644]
posted_on_Mastodon.txt

diff --git a/content/qalculate-time-hacks.rst b/content/qalculate-time-hacks.rst
new file mode 100644 (file)
index 0000000..25d2d07
--- /dev/null
@@ -0,0 +1,167 @@
+Qalculate time hacks
+====================
+
+:slug: qalculate-time-hacks
+:date: 2025-02-23 20:00:12 +00:00
+:category: debian
+:tags: columbiform, debian, planet-debian, planet-ubuntu
+:mastodon-id: 114055000943801013
+
+Anarcat recently `wrote
+<https://anarc.at/blog/2025-02-08-qalculate-hacks/>`__ about `Qalculate
+<https://qalculate.github.io/>`__, 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 <{filename}/task-management.md>`__, I
+quickly found that `Timewarrior <https://timewarrior.net/>`__ 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:
+
+.. code-block:: console
+
+    $ 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:
+
+.. code-block:: python
+
+    #! /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}")
+
+.. code-block:: console
+
+    $ 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:
+
+.. code-block:: console
+
+    $ 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 <https://www.freexian.com/about/debian-contributions/>`__; 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.
+
+.. code-block:: console
+
+    $ 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)'
+    (86730 / 3600) / ((86730 / 3600) + (249582 / 3600)) ≈ 0.2578855349
+
+I love it.
index 73957f8a9e509a6180e1e9221aefc371e4a0da7d..e6a6d0c450161ea0612e9b8843c8320fc7fde508 100644 (file)
@@ -64,6 +64,7 @@ pipeline-library.html
 playing-with-rich.html
 porting-ghc-a-tale-of-two-architectures.html
 python-sigpipe.html
+qalculate-time-hacks.html
 quality-in-12-04.html
 re-signing-ppas.html
 reply-perl-is-strange.html