DEV Community

Luís Costa
Luís Costa

Posted on • Edited on

go-timetracker | Part I - Why and What

Lately, I've been enjoying programming in go. I don't know what it is, but I feel good writing go code. Maybe it's because I have a background in dynamically typed languages (such as ruby and javascript) and so returning now to a statically typed and compiled language just seems like its a new, shiny thing and I like shiny new things. And what better way to learn a new programming language than to actually build something with it?

This post will be part of a series of posts, where I will describe my process of building a command-line application in go. In this first post, I'll go over what the project is, why I'm building it, and how I intend to build it!

Why

I've always heard that tracking your time is a good thing. It allows you to review where you're spending your time and it gives you a better view on whether you're using your time effectively or not. It can also tell you how much time you spend working and procrastinating. There are tons of tools that can do this for you, but I wanted something that could be used via simple commands from my terminal, and which doesn't get in the way of doing what I set out to do (i.e, I don't want to spent too much time learning a productivity tool, because that would be... unproductive).

What

The tool I will be writing (go-tt) will allow me to say something like I'm now working on unit tests and, a few minutes later, I can say I'm now writing documentation for feature X. If I want to consciously take a break, like reading an article, I can quickly say I'm now reading an article. I should then be able to see at a glance the tasks I've been working on in the last day, week, month, year or in any other random period in time.

Requirements

  • It should be accessible via the command line and be easily installable via popular package management systems (like homebrew, apt, etc). However, installing it from source should be easy too.
  • It should work on all major operating systems.
  • It should be fast, with a few set of commands, which must be easy to use (which implies a low number of arguments for each command) and good documentation.
  • It should be free for modification and distribution, i.e, open-sourced.
  • The code should be extensible:
    • Even though it will be primarily a CLI application, adding a GUI in the future should not be much of a trouble.
  • It should report the data in various formats: CSV, JSON, etc (more on this later).
  • It should contain as few dependencies as possible.
  • It should be well covered with tests.

Specification

Let's get into the nitty-gritty parts. In this section I will outline the major commands in the program, what they should do and how they should work. Note that things can (and most probably will) change during the development of the project.

Modules

  • UI: This module takes care of everything about displaying and receiving data from/to the user. Here we can have one submodule per UI layer: CLI, GUI, etc.
  • Persistence: This module takes care of writing/reading hard data. I will talk a bit more about the data we're going to need later.
  • Core: This module will contain most of the business logic. It will combine all the other modules to achieve the goals of the program.
  • Reporter: This module will handle the logic of taking the data and presenting it to the user (for instance, in CSV files). More on this later.

Operations

Do not confuse this with command-line commands, even though the syntax I will use may suggest that. This is an high level overview of the operations the program should accept.

  • add new activity

This operation creates a new activity in the system. For instance, one could create an activity named writing tests or attending meeting. An activity is identified by a unique identifier and can additionally have a description and a list of aliases. If the activity already exists, a helpful error message should be returned to the user.

  • start activity X | alias

Once this operation is executed, the program starts tracking the time that the user is taking to do the activity X. If activity X is not yet created, then it should automatically create it and start tracking it. Tracking here simply means recording a timestamp with the current system time. Only one activity can be tracked at the same time, i.e, the user cannot start two activities one after the other, unless the previous one is stopped (this is an explicit decision on my part: I don't want to encourage multi-tasking). An alias can be used instead to reference the activity.

  • stop activity X | alias

Once this operation is executed, the program records the current system time and calculates the total time spent on this activity by using the start time recorded early. If the activity X does not exist, or if it does exist but its not the current one being tracked, or if none activity at all is being tracked, an error message should be returned. An alias can be used instead to reference the activity.

  • delete activity X | alias

This operation deletes the activity X from the system. If the activity does not exist, an error is returned. This action is not reversible: deleting an activity will also delete all data associated with it. An alias can be used instead to reference the activity.

  • get report

This operation returns the tracked data of all activities or for a given specified list of activities in a given period. The user can decide which format he wishes the report to be in. More on this later.

  • list activities

This operation lists all activities currently registered in the system.

  • backup

This operation creates a compressed file (.zip or .tar, for instance) with all the data recorded for your activities since the beginning of time.

  • purge

This operation wipes out all the data in the system.

Data

As I've mentioned, in the first version, we're going to rely on the filesystem to persist data. The data we'll need is:

  • A list of tracked activities. (Activity metadata files)
  • A daily log where each activity start and end time is recorded (Activity Log files)

I can easily implement this using the file system with JSON files. (the choice here is arbitrary, we could easily just use plain .txt files, but I find working with JSON pleasant).

Activity Metadata

Each activity will have its own meta data file. The name of the file will be the identifier of the activity. For instance, for the activity activity-1:

{
  "identifier": "activity-1",
  "description": "Some description...",
  "aliases": ["alias1", "alias2"]
}

Activity Log

Each individual activity log has the following structure:

{
  "activity-1": [
    {
      "start": 1587231890,
      "end": 1587231901,
      "duration": "..."
    },
    {
      "start": 1587231920,
      "end": 1587231931,
      "duration": "..."
    }
  ]
}

I have yet to decide the data format for the duration key. The name of the activity log file will have the structure YYYY/MM/DD.json, for instance: 2020/10/01.json.

Each entry in the array for activity-1 corresponds to a cycle of starting and stopping the tracking.

For instance:

> go-tt start a1 # t0
# ...
> go-tt stop a1 # t1
> go-tt start a2 # t2
# ...
> go-tt stop a2 # t3
> go-tt start a1 # t4
# ...
> go-tt stop a1 # t5
> go-tt start a2 # t6
# ...
> go-tt stop a2 # t7

This would produce the following log file:

{
  "a1": [
    {
      "start": "t0",
      "end": "t1",
      "duration": "..."
    },
    {
      "start": "t4",
      "end": "t5",
      "duration": "..."
    },
  ],
  "a2": [
    {
      "start": "t2",
      "end": "t3",
      "duration": "..."
    },
    {
      "start": "t6",
      "end": "t7",
      "duration": "..."
    }
  ]
}

Combining every thing together, here's a possible folder hierarchy for the data describe above:

meta/
  activity-1.json
  activity-2.json
  ...
log/
  2020/
    01/
      1.json
      2.json
    02/
      10.json
  2021/
    03/
      1.json
  ... etc

This is madness!

Yes, I know! This solution has SO many disadvantages, including:

  • What if the files get corrupted?
  • What if the user manually changes the files?
  • What if the files disappear?
  • I have to load metadata and log files in memory to read/update activities and to create the reports! This is going to cause memory problems soon enough!
  • What about automatic/regular backups?
  • You're trying to be smarter than the guys that spent many years writing and optimizing databases for this kind of thing! Who do you think you are?

I know.

Since I'm new to the language, I think that learning how to do I\O is important, and this initial version will be the perfect playground to do it. Plus, if I write the code in a modular fashion, I can then plugin a DBMS like Postgres, SQLite or even Mongo easily. That will certainly come later on.

This is the kind of solution I would be ashamed to show publicly, and only work on it privately. But I want to share the process with you. I am aware this is not a good solution, but I must remind myself: this tool is built to be used by me first and foremost, and to learn go. On future iterations, I will certainly add a more robust layer of data persistence.

Reports

In this initial version, the reporting system is straightforward. It should accept three arguments: the format of the report (in this first version, I will write three report systems: JSON, CSV and directly in the stdout), the initial date and end dates (like: 2020-01-02 to 2020-01-04). The report (independently of the format) should present:

  1. A list of activities tracked in that period.
  2. For each activity, the total time spent on it.

For fun, we could show other aggregations like:

  • The average time spent in each activity per day/week/month.
  • The time of day where each activity is most performed.
  • The median of each activity.
  • The days of the week where each activity is most performed.
  • A graph (only possible in UI solutions) showing the evolution of the time spent in each activity in the period.
  • ... any other ideas? Let me know!

Some of these can certainly come later on.

Notes

  • Aren't you worried that people might steal your idea? Absolutely not! Please, feel free to go ahead and implement it on your own as well! It would be fun to compare implementations later on!

  • Are you going to host the project on Github? Yes.

  • What will be the schedule of the posts? I will not set an explicit hard schedule to write posts for this series. After I have finished a major milestone in the project, I will then write a post about it.


Thanks for reading! If you find any clear and alarming problem in this first naive implementation, or if you have any other ideas you'd like to see, please let me know!

Top comments (0)