Autotel

Time tracking simple tool

Explanation

I hate time tracking apps. They are invariably too heavy for their very basic function. Often they are as blatant as being built over electron.

With some clients, for which I work exclusively on the computer, I created a very simple way to count worked time; and it uses very little resources. It's based on Node js. Though I know that it would be more efficient to use just batch, node is a lot faster for me to use, and probably customize in the future as I need to tweak the tool.

The way I use this is: on my client's folder, I create a "time tracking" folder, where I put these files that I will outline below. Then I can call entry "the task name" and leave it running until I finish. Then I can just close the window or end the process with ctrl + c key combination.

If you call entry -get, it will sum all the worked time and display it in an overly precise way. You can delete or rename the "tracking.yaml" file when you bill the hours to the client. In case you mess up something and you need to sort out the entries manually, this yaml file is very human readable, so you can easily make changes. Note, however that the time is counted in the basis of ms, if you need to tweak the time intervals.

The file entry needs execution permission (chmod +x ./entry). This file sugar-coats the call, so you don't need to call node ./tracking.js. The file that does the job, of course, is the tracking.js file.

entry shell file

node "./tracker.js" $2 -entryName "$1"

tracker.js file

It's a bit messy, but it works. I might improve it in the future.

const fs = require("fs");
const trackingFileName = "./tracking.yaml";

let entryName;
let entryNameArgvIndex = process.argv.indexOf("-entryName") + 1;
let getTotalIndex = process.argv.indexOf("-get") + 1;

const pad = (n, z) => {
    z = z || 2;
    return ('00' + n).slice(-z);
}

const getTotalTime = () => {
    const text = fs.readFileSync(trackingFileName, 'utf8');
    const msEntries = text.match(/ms\:\s*(\d+)/g).map(
        a=>parseInt(a.match(/\d+/))
    );
    console.log(
        niceTime(
            msEntries.reduce((a,b)=>a+b)
        )
    );
}

const niceTime = (milliseconds) => {
    var ms = milliseconds % 1000;
    milliseconds = (milliseconds - ms) / 1000;
    var secs = milliseconds % 60;
    milliseconds = (milliseconds - secs) / 60;
    var mins = milliseconds % 60;
    var hrs = (milliseconds - mins) / 60;

    return hrs + 'hrs, ' + mins + 'mins, ' + secs + '.' + pad(ms,3) + ' secs';
}

const getTextEntry = () => {
    
    const intervalMs = endDate - startDate;
    const entry = `\n\ndate: ${
        startDate.getFullYear() }-${
        startDate.getMonth() }-${
        startDate.getDate() } at ${
        startDate.getHours() }:${
        startDate.getMinutes() }:${
        startDate.getSeconds()
    }\n\t- worked: ${niceTime(intervalMs)}\n\t- on: ${entryName}\n\t- ms: ${intervalMs}`;
    return entry;
}
if(getTotalIndex){
    getTotalTime();
    process.exit();
}
if(entryNameArgvIndex){
    entryName=process.argv[entryNameArgvIndex];
}
const startDate = new Date();

console.log("started at",startDate);

console.log("task name",entryName);

let exitHandled = false;
const exitHandle = () => {
    if(exitHandled) return;
    exitHandled = true;
    const entry = getTextEntry();
    fs.appendFileSync(trackingFileName,entry);
    console.log("Caught interrupt signal");
    console.log("saved entry", entry);
    process.exit();
};

process.on('beforeExit',exitHandle);
process.on('exit',exitHandle);
process.on('SIGINT',exitHandle);
process.on('SIGTERM',exitHandle);
process.on('SIGHUP',exitHandle);

let endDate = new Date();

setInterval(()=>{
    endDate = new Date();
    console.clear();
    console.log(getTextEntry());
},1000);