Think of time as a series of timestamps

I've been logging my daily time usage for almost 5 years. And by "daily time usage", I mean "every minute". Every day, I know where I spend every minute (sleeping, working, reading, writing, coding, etc.). As you may expected, it is not an easy task to do.

Logging daily time usage is not easy

When I first started logging time, I thought of time usage as ranges: every task T has a clock, and it starts from a point t1, ends at a point t2, lasts for m minutes. It's intuitive, but it didn't work well.

  • When starting a task, I need to note down what I'm going to do. But it may stop me from doing the real task.
  • When adjusting a range, I need to adjust two data points at the same time: change one of t1 or t2, then recalculate m.
  • When switching from task A to task B, I need to stop task A first, then start task B.
  • When multitasking, I need to keep multiple clocks running at the same time.

After several rounds of iteration, I now think of time usage as just two timestamps: every task T has a start point t1, and an end point t2.

Here is how I do it now:

  1. Whenever I need to spend a block of time on something, I jot down a timestamp t1.
  2. Then when I finish the task, I jot down a timestamp t2.
  3. Finally I specify which task T I was working on, and set t1 as its start time, t2 as its end time.
  4. If I start another task T' immediately, t2 is the start time for T'.

It's a more lightweight and flexible workflow.

Logging code time usage is not easy, either (and how Heroku solves this problem)

More interestingly, if you think of time logging beyond our daily time usage, you'll find many other things that require time logging. One of them is logging time spent in different parts of our program. Heroku faced this exact problem before. In this post, Fred Hebert shared how they solved it.

Their first straightforward solution was like how I started logging my daily time:

  1. note t1
  2. doing the real work T
  3. note t2
  4. calculate the duration m

But this solution would couple the code for time logging and the code for doing the real work together.

Then a better solution was like what I am doing now:

  1. note t1 with a label l1: T started
  2. note t2 with a label l2: T stopped
  3. the analyzing library uses t1, l1 and t2, l2 to calculate how long task T took
  4. the analyzing library may use t1 or t2 for other task calculations like T'

With this solution, Heroku now fully decouples how the real work gets done (business logic) from how the time logs are analyzed (logging logic).

The power of data and deferring decisions

Optimizing time logging reminds me of Use Return Value to Defer Decisions.

In the case of Heroku, with the help of additional label l1 and l2, we can defer the decision of how to analyze time logs to later.

Again, this is the beauty of functional programming: constraints breed creativity. To quote Fred:

it was the structural constraint of already being in an immutable context that prompted the cleaner design.

Further reading