I’ll probably need this again so writing it up: is an awesome little helper syncing tasks to (where I use to track hours spent). It connects to various ticket systems like , or .

Today I had to tweak it a little though, because I have no idea where to put client certificates for Python’s requests lib and my current customer requires that. Any HTTPS request without will fail with status code 400: No required SSL certificate was sent.

For this I edited ~/.local/lib/python3.11/site-packages/bugwarrior/services/gitlab.py line 364 from this:

response = requests.get(url, headers=headers, verify=self.verify_ssl, **kwargs)

to

response = requests.get(url, cert=('/path/to/client.crt', '/path/to/client.key'), headers=headers, verify=self.verify_ssl, **kwargs)

Important: Any request to _some_ GitLab by Bugwarrior will offer the client cert now. That’s fine for me because I’ve currently only one GitLab system to check at the moment. There may be better ways to achieve this but I’ve seen no obvious in the docs at https://bugwarrior.readthedocs.io/en/latest/services/gitlab.html – YMMV.

I track my working hours with timewarrior. That’s a CLI program that has a lot of nifty features and can also be hobbled together with taskwarrior to automated time tracking when a task is started. The taskwarrior on the other hand gets my todo list from various bugtrackers as sources, like Redmine and Jira, using bugwarrior-pull.

With this set-up I’ve my to-dos and my tracked hours available on the terminal, where I spend most of the day anyway. This is more or less comfortable for me but there is a huge drawback.

At the end of the months I’ll need some numbers and as it goes each company or customer has it’s own time tracking system (or even wants a csv export!) so I’ve to backfill the real systems each month. That’s a very tedious work especially if time has to be logged on specific tickets or customers and booking is done with quarter hours so I’ve to do some quick math in my head all the time.

In theory timewarrior has me covered on this because it has a summary view that is basically fine but this can not be used to grepped or sorted for certain tickets because it displays the date for each day only once.

Another nifty feature is the timewarrior export function that results in a JSON. The result is somewhat limited though since it will for example not display and durations and there is as far as I know no way to change this.

Demo how the various views or exports of timewarrior look (And yes, not _everything_ I work on is tracked here :P)

This is where jq (a lightweight and flexible command-line JSON processor) comes in. This little tool is seriously underrated and it allows me to change the format and the values of the JSON export on the fly by calculating for example time durations on the fly, reformats start and end dates so import functions of table calculation programs, like LibreCalc, can read the values as date (we all know that Excel reads anything as date already) and displays me the tracked time in quarter hours for each entry so it’s for most cases a no brainer now to backfill another time tracking system with this. The result can also be easily sorted now to find for example times for a specific ticket by grepping for it’s id.

timew export :week | jq -r '["id", "start", "end", "duration", "quarter_hours", "description"], 
    (.[] | 
        # make sure .end is set (may be empty for currently active tracked time)
        .end = (.end // (now | strftime("%Y%m%dT%H%M%SZ"))) |
        .duration = ( (.end | strptime("%Y%m%dT%H%M%SZ") | mktime) - (.start | strptime("%Y%m%dT%H%M%SZ") | mktime) ) |
        # round duration to quarter hours
        .quarter_hours = (.duration / 3600 / 0.25 | ceil*0.25) |
        [
            .id,
            # urks, localtimes are a mess in jq, ymmv - as long as it is consistent off I do not care tho
            (.start | strptime("%Y%m%dT%H%M%SZ") | mktime | todateiso8601), 
            (.end | strptime("%Y%m%dT%H%M%SZ") | mktime | todateiso8601), 
            (.duration | strftime("%T")),
            .quarter_hours,
            (.tags | join(", "))
        ]
    ) |
    @csv'

The resulting csv file can be imported into most table calculation software now or read manually in a more comfortable way.

Timezones are still an issue. There are so many open tickets on jq on this that I don’t even bother.

Ymmv, as usual 🙂