This is a very simple timeclock I built for tracking my working hours. The overriding goal of the program was to keep the user experience as simple and uncomplicated as possible for the common case of clocking in and out of something.
To this end, the program is very permissive of what it will accept. Times are taken in about any format you can imagine,
from now
or in an hour
to 2023-07-06T14:48:58+0000
(the same goes for dates of course). Very nearly anything you
feed in will be a valid input, and if it isn't what you meant... Well, there are commands to fix it.
Config is read from $XDG_CONFIG_HOME/sctime
, if $XDG_CONFIG_HOME
is empty or unset $HOME/.config/sctime
is used.
Inside this directory there should be a file named config.ini
. The default contents of this file are as follows:
logfile="$HOME/sctime.log"
codefile="$CONFIG/codes.txt"
reportdir="$CONFIG/reports"
$CONFIG
is a special variable set to the current configuration directory. Otherwise, you may use any environment
variable you like.
logfile
is the path to your timelog.
codefile
is the path to a file containing all of your timecodes, one per line.
reportdir
is the path to a folder containing the report templates.
Any file inside reportdir
matching *.tmpl
will be read as a report template. Reports defined in reportdir
will override builtin reports. Reports are templated with go's text/template.
go install github.com/milochristiansen/timeclock
You will, of course, need to have the latest Go complier installed.
Note: If you call the timeclock binary with the name timetool
it will suppress any user interactive elements
(prompting for more input for example). The best way to do this is add a symlink to the binary with that name. I'm going
to assume that if you need this functionality, you will know enough to figure it out for yourself from there.
This should be a full list of everything you can do with this program.
By far the most common thing you do with a timeclock is creating a time event. To do that with this program, all you
need to do is specify a time (most commonly now
).
timeclock now
Additionally, you can specify a time code and/or a description, but they are not required.
timeclock now :Customer Did a thing.
This will result in a event with the description "Did a thing." coded to Customer
that happened at the time the
command was run.
Order is not generally important. You can even mix them together!
timeclock Did a thing for :Customer at 10:00am
This will result in a event with the description "Did a thing for Customer at 10:00am" coded to Customer
that happened
at 10:00am on the day the command was run.
So, how does this work?
Pretty simply really. First, the program attempts to identify the time. It does this by searching the entire input string for anything that could possibly be a time or date. If it finds anything that it can interpret as a time, it picks the first such item and uses it as the time for the event. After doing that, it goes through the entire input looking for any of the timecodes it knows about. To keep parsing sane, timecodes must be a single token and prefixed by a colon. If you want to use spaces in your timecodes, quote them. The prefixing colon will be stripped if the timecode text survives the next step. Speaking of which, as a final step if the time code or the string that was parsed to get the event time prefix the input (in any order) it will strip them off. Any remaining text will then be used as an event description.
Adding an existing timecode to an event is easy, but what if you need to create a new one? For this you need the code
subcommand.
timeclock code NewCode
This sets the timecode for the last time event to NewCode
. This code does not have to be a new code, you can also use
this command to fix a case where you forgot to add a time code or specified the wrong one. This subcommand does not use
fuzzy matching for timecodes! Make sure you specify the code you want exactly.
Memory like mine? Just need a quick refresher on what the currently known codes are?
timeclock info
This simply prints out a list of all known timecodes.
If you decide to add a description to an event that didn't have one, or you want to change the existing description, you
can use the desc
subcommand (also has a note
alias).
timeclock desc New description.
This simply sets the description of the last event to the new description.
Sometimes you forget if you clocked in, or otherwise want to know what the timeclock thinks is going on. To this end you
can use the status
subcommand.
timeclock status
This will print the last time event to standard output in the form:
2023/07/06 09:36AM [TimeCode] Description Text.
Day dragging on forever? Clocked in at an odd time and want to see how long you have been working without doing any
math? The since
command is here for you!
timeclock since
This will print the last time event, the elapsed time till now, and the current time in the following format:
2023/07/06 06:18AM [timecode] Description
== 2.5h ==>
2023/07/06 08:45AM
A timeclock isn't any good if you can't print out a report of what you spent time on.
timeclock report last year
This will print a time report with only events that have happened in the last year. This report will be very noisy, and full of garbage.
To clean it up, you can specify a timecode.
timeclock report last year :Customer
This will only show events for Customer
, however that is rarely what you actually want. Generally, what you really
want is all times that are coded to anything at all. For this you can use the special code all
. This timecode only
exists for this command, and tells the program to show all events that have a code.
timeclock report last year :all
If you want to get a report for a specific month, you also need an end time.
timeclock report june 1st july 1st :all
If you really want all events you can also use the special code empty
. This will filter in events that have no
timecode.
timeclock report last year :all :empty
If you have created a report template, you may use it by adding its name.
timeclock report june 1st july 1st :all csv.tmpl
Like the event adding code, the report code simply searches for times in the entire given input, but it will always use the first two it finds. If it only finds one, it will print a report from that time to the current time, if it finds two it will use them as start and end times. These times can be in any order. Similarly, the timecode used for filtering and the report template are found via a search of the entire given input (although the timecode still needs to be prefixed with a colon as normal).
If you have timecodes arranged in a hierarchical manner, with children separated from parents with a colon
(eg parent:child:child
), a report filtered for parent
will not automatically include its children. You can add :...
after a parent to force its children to also be included.
If you ever find yourself wondering how this slightly demented program will parse your input, you can use the test
command.
timeclock test Go last month yourself.
This will act just like the input was being used to specify a new event, but it won't write anything to the timelog.
2023/06/06 12:36PM [] Go last month yourself.
No time code found, use 'code' to specify one.
(That output was from a test run on July 6th 2023)
The timelog is stored on disk in a plain text format lightly inspired by Ledger CLI. Each event is stored on a single line in the following format:
yyyy/mm/dd hh:mmPM [timecode] description
The timecode field is left padded with spaces so that every timecode is the same length in the entire file, but that is purely to make the fields vertically aligned for easier reading should you ever want to look at the file manually. This is not needed for the file to parse cleanly.