Timekeeping with Emacs and Org-Mode
Although I have been an Emacs user for 15 years, for the first 13 of
those years I only used a handful of commands and one or two “modes”.
A couple years ago I went through the Emacs tutorial (within Emacs,
type C-h r
) to see if I was missing anything useful. I was not
disappointed! Since that time, I have gone through the entire
Emacs manual,
made full use of
Elpy to
create a rich Python IDE, adopted
Magit to speed
up my version control workflow, and more!
In this post, I want to talk about how I use org-mode for timekeeping.
I only recently started doing this, so my workflow likely will change,
but I’m reasonably happy with it today. It is easy to “clock-in” to a
particular task by positioning point within an item and typing C-c C-x C-i
and clocking out with C-c C-x C-o
. See
the documentation
for details.
But the thing that really impressed me is the flexible reporting framework. Whether you need to report hourly time as an independent contractor, or simply want to review how much time you’re spending where (in meetings for example), org-mode’s clocktable functionality has you covered!
I’m doing a contract with Calypso AI (which is doing some really cool work in the trusted AI space) and need to report weekly invoices with services provided by day. I created two clocktables: one on a daily granularity just for the current week, the other on a weekly granularity since I started the contract.
Since I am using one projects.org
file for everything, I have a
top-level item for Calypso and limit reporting to that subtree. I
define my hourly rate as a property at that level, which inherits down
to any subitems. Some items can also be tagged as @overhead
for
items that logically belong in this subtree but that I don’t charge
for (like preparing the invoice itself). I still care about tracking
time there, so I make sure I’m not wasting too much time on overhead!
I customized the org-duration-format
variable in my init.el
file
as (org-duration-format (quote (("h" . t) (special . 2))))
to print
all times as hours with 2 decimal points. (The default was to print
durations as DD:HH:MM which isn’t convenient for me.)
My weekly clocktable definition is:
+BEGIN: clocktable
:scope subtree ;; Limit reporting to this item
:maxlevel 2 ;; Level of detail
:properties ("HOURLY_RATE") ;; My hourly rate
:inherit-props t ;; Hourly rate cascades down
:tstart "<2020-xx-yy Mon>" ;; Contract start date
:tend "<tomorrow>" ;; Report up to and including today
:step week ;; Weekly granularity
:link t ;; Link to items in table
:sort (4 . ?N) ;; Sort descendingly on time
:tags "-@overhead" ;; Omit overhead
:formula "$5='
(* (string-to-number @3$1)
(+ (string-to-number (replace-regexp-in-string \"[\*h]\" \"\" $3))
(string-to-number (replace-regexp-in-string \"[\*h]\" \"\" $4))
)
);%.2f
::@1$5=string(\"Total\")"
I have split the definition over multiple lines and added comments for readability, but in org-mode the entire definition has to be on one line. So if you are copy-pasting, remove all the comments and put it all on one line.
Before going over the definition, let’s see what the result looks like. We get separate tables for each week. Here is an example:
Weekly report starting on: [2020-xx-yy Mon]
| HOURLY_RATE | Headline | Time | | Total |
|-------------+--------------------+----------+--------+---------|
| | *Total time* | *40.00h* | | ZZ |
|-------------+--------------------+----------+--------+---------|
| X | \_ Meetings | | 10.00h | ZZ |
| X | \_ Task A | | 10.00h | ZZ |
| X | \_ Task B | | 10.00h | ZZ |
| X | \_ Task C | | 5.00h | ZZ |
| X | \_ Task D | | 5.00h | ZZ |
| X | Company | 40.00h | | ZZ |
Notably, this isn’t exactly what I want. My hourly rate becomes the
first column in the table, which isn’t aesthetically satisfying, but
that’s what I get. Since I have two hierarchical levels I get two
columns for time reporting. I would prefer to only report on the
second hierarchy, but that would require a :minlevel
property, which
doesn’t exist. I have limited control over the sorting: ideally I
would sort first by column 3 and then by column 4, but I can only sort
on a single column. Thus, the overall total line which should be
first, ends up being last. But I’m reasonably happy with it,
considering how little recurring effort is needed to regenerate it!
Going into the definition a bit more, I had to specify “tomorrow” as
the end date, because when I specified “today”, the report didn’t
actually include today. By specifing link = t
, I can position point
over one of the items in the table and type C-c C-o
to navigate to
the corresponding item for more details. The formula is a little
kludgey but basically strips the asterisks and units from the
durations, converts to numbers, adds columns 3 and 4 as a rudimentary
COALESCE, multiplies the hourly rate times the duration, and formats
with two decimals of precision as a currency.
For completeness, here is the definition for my daily view:
+BEGIN: clocktable
:scope subtree
:maxlevel 2
:properties ("HOURLY_RATE")
:inherit-props t
:block thisweek ;; Only show this week
:step day ;; Daily granularity
:stepskip0 t ;; Don't show days with zero time reported
:link t
:tags "-@overhead"
:formula "$5='
(* (string-to-number @3$1)
(+ (string-to-number (replace-regexp-in-string \"[\*h]\" \"\" $3))
(string-to-number (replace-regexp-in-string \"[\*h]\" \"\" $4))
)
);%.2f
::@1$4=string(\"Total\")"
The only differences are that I don’t do any sorting and I do daily granularity, just for the present week.
Even as a salaried employee, I can see this being useful as a way of tracking how much time I spend in meetings, dealing with email, dealing with Slack, researching, and programming. Having recently read “Deep Work” by Cal Newport, I believe having a quick way of tracking how much time I’m spending on shallow work versus deep work would likely be insightful!