Skip to content

New minor version 1.2.0 #6

Description

@lost2

As I had change a lot of things, here it is as a new minor version.

#!/usr/bin/env bash


# Original v1.1.4 from https://strdr4605.com/building-a-command-line-time-tracker
# Copyright (c) 2021 Dragoș Străinu
#
# 2025-06-16 Changes by lost2 New minor version v1.2.0
# - Use local date not UTC date
# - Crontab code removed. It allows the script to run on Windows git-bash. Not
#   tested on MacOS
# - Added -c parameter to match --activity-name
# - Added -v parameter to match --version
# - Replaced [] for <> on activity-name parameter help text as square brackets
#   Around a parameter indicate that it can be omitted, and a default value will
#   be used if it's not provided.
# - Replaced colon by semicolon as field separator on log file to avoid conflict
#   with colon on date command output
# - Added new tot_time_activity option (total time grouped by activity)
# - Added new hidden feature (passing -e) to edit LOGS file using default editor


tt() {
    TT_VERSION="v1.2.0"
    # :- means that if TT_LOGS doesn't exit, it will assign $HOME/.tt_log (~/.tt_log)
    TT_LOGS="${TT_LOGS:-$HOME/.tt_logs}"
    TT_SESSION="${TT_SESSION:-$HOME/.tt_session}"
    if ! [ -f "$TT_SESSION" ]; then
        touch "$TT_SESSION"
    fi
    if ! [ -f "$TT_LOGS" ]; then
        echo "Date;Activity Name;Human Time Spent;Total Seconds" >"$TT_LOGS"
    fi
    # See end of function for starting of _options


    # Internal functions
    _options() {
        echo ""
        case "$1" in
        -c | --activity-name)
            # Shows the current activity name if there is one for this session
            activity_name=$(grep 'activity_name=' "$TT_SESSION" | sed -E "s/.*activity_name=(.+)$.*/\\1/")
            if [ -z "${activity_name}" ]; then
                echo "No activity started"
            else
                echo "Activity '$activity_name'"
            fi
            ;;
        -h | --help)
            # Help
            echo "tt - time tracker"
            echo ""
            echo "Tracks activity time with a simple start/stop syntax. Logs to CSV."
            echo "Allows one activity active at a time, per session."
            echo ""
            echo "usage: tt                                       # show this help"
            echo "usage: tt (--help or -h)                        # show this help"
            echo "usage: tt (--version or -v)                     # show tt current version"
            echo "usage: tt (--start or -s) <activity name>       # start a new activity"
            echo "usage: tt (--pause or -p)                       # pauses current activity"
            echo "usage: tt (--done or -d or --finish or -f)      # stop and log activity"
            echo "usage: tt (--abort or -a)                       # stop activity, no log"
            echo "usage: tt --clear-logs                          # delete log of previous activities"
            echo "usage: tt (--activity-name or -c)               # show activity for current session"
            echo "usage: tt (--logs or -l)                        # show logs of previous activities"
            echo "usage: tt (--tot_time_by_activity or -tta)      # show total time by activity"
            ;;
        -tta | --tot_time_activity)
            _tot_time_activity
            ;;
        -v | --version)
            echo "$TT_VERSION"
            ;;
        -e)
            ${EDITOR} "$TT_LOGS"
            ;;
        -p | --pause)
            _pause
            ;;
        -d | --done | -f | --finish)
            _finish
            ;;
        -a | --abort)
            echo "Abort activity"
            echo "" >"$TT_SESSION"
            #crontab -l | grep -v "tt" | crontab -
            ;;
        -l | --logs)
            cat "$TT_LOGS"
            ;;
        --clear-logs)
            echo "Logs cleared"
            echo "Date;Activity Name;Human Time Spent;Total Seconds" >"$TT_LOGS"
            ;;
        -s | --start)
            _start "$2"
            ;;

        *)
            echo "!!!! Invalid option !!!!"
            _options -h
            ;;
        esac
      }


    _start() {
        start_timestamp=$(date +%s)
        # No activity name passed
        if [ -z "$1" ]; then
            activity_name=$(grep 'activity_name=' "$TT_SESSION" | sed -E "s/.*activity_name=(.+)$.*/\\1/")
            if [ -z "$activity_name" ]; then
                echo "No activity started"
                return
            else
                echo "Restarting '$activity_name'"
                elapsed_sec=$(grep 'elapsed_sec=' "$TT_SESSION" | sed -E "s/.*elapsed_sec=([0-9]+).*/\\1/")
                echo "start_time=${start_timestamp}" >"$TT_SESSION"
                echo "elapsed_sec=${elapsed_sec}" >>"$TT_SESSION"
                echo "activity_name=${activity_name}" >>"$TT_SESSION"
            fi
            return
        fi
        # finish old activity if exists
        old_activity_name=$(grep 'activity_name=' "$TT_SESSION" | sed -E "s/.*activity_name=(.+)$.*/\\1/")
        if [ -n "$old_activity_name" ]; then
            _finish
        fi
        echo "Starting '$1'"
        echo "start_time=${start_timestamp}" >"$TT_SESSION"
        echo "elapsed_sec=0" >>"$TT_SESSION"
        echo "activity_name=$1" >>"$TT_SESSION"
      }


    _pause() {
        # Do we have an activity active for this session?
        start_time=$(grep 'start_time=' "$TT_SESSION" | sed -E "s/.*start_time=([0-9]+).*/\\1/")
        elapsed_sec=$(grep 'elapsed_sec=' "$TT_SESSION" | sed -E "s/.*elapsed_sec=([0-9]+).*/\\1/")
        activity_name=$(grep 'activity_name=' "$TT_SESSION" | sed -E "s/.*activity_name=(.+)$.*/\\1/")
        if [ -z "$start_time" ]; then
            echo "No activity started"
            return
        fi
        if [ "$start_time" = "0" ]; then
            echo "Activity '$activity_name' is already paused"
            return
        fi
        pause_timestamp=$(date +%s)
        sec_diff=$((pause_timestamp - start_time + elapsed_sec))
        hours=$((sec_diff / 3600))
        mins=$(((sec_diff - (hours * 3600)) / 60))
        echo "start_time=0" >"$TT_SESSION"
        echo "elapsed_sec=${sec_diff}" >>"$TT_SESSION"
        echo "activity_name=${activity_name}" >>"$TT_SESSION"
        echo "Activity '$activity_name' paused at ${hours}h ${mins}m"
    }


    _finish() {
        # Do we have an activity active for this session?
        start_time=$(grep 'start_time=' "$TT_SESSION" | sed -E "s/.*start_time=([0-9]+).*/\\1/")
        elapsed_sec=$(grep 'elapsed_sec=' "$TT_SESSION" | sed -E "s/.*elapsed_sec=([0-9]+).*/\\1/")
        activity_name=$(grep 'activity_name=' "$TT_SESSION" | sed -E "s/.*activity_name=(.+)$.*/\\1/")
        if [ -z "$activity_name" ]; then
            echo "No activity started"
            return
        fi
        # Activity was paused
        if [ "$start_time" = "0" ]; then
            _save "$activity_name" "$elapsed_sec"
            echo "" >"$TT_SESSION"
            #crontab -r
            return
        fi
        finish_timestamp=$(date +%s)
        sec_diff=$((finish_timestamp - start_time + elapsed_sec))
        _save "$activity_name" $sec_diff
        echo "" >"$TT_SESSION"
        #crontab -l | grep -v "tt" | crontab -
    }


    _save() {
        activity_name=$1
        sec_diff=$2
        hours=$((sec_diff / 3600))
        mins=$(((sec_diff - (hours * 3600)) / 60))
        #local_date=$(date)
        local_date=$(date '+%Y-%m-%d %a')
        echo "$local_date | $activity_name | ${hours}h ${mins}m"
        #log="$local_date,\"$activity_name\",${hours}h ${mins}m,$sec_diff"
        log="$local_date;$activity_name;${hours}h ${mins}m;$sec_diff"
        echo "$log" >>"$TT_LOGS"
    }


    _tot_time_activity() {

        # Function to convert seconds to d:h:m:s format
        convert_to_dhms() {
            local total_seconds=$1
            local days=$((total_seconds / 86400))        # 1 day = 86400 seconds
            local hours=$(((total_seconds % 86400) / 3600))  # 1 hour = 3600 seconds
            local minutes=$(((total_seconds % 3600) / 60))   # 1 minute = 60 seconds
            local seconds=$((total_seconds % 60))
            printf "%02d:%02d:%02d:%02d\n" $days $hours $minutes $seconds
        }

        # Function to process the file and list tasks with total time in d:h:m:s format
        process_task_file() {
            local input_file=$1
            declare -A task_times
            local first_line=true

            # Read through the file and extract task names and seconds
            while IFS=';' read -r date task_name human_time total_seconds; do
                # Skip the first line (header)
                if [ "$first_line" == true ]; then
                    first_line=false
                    continue
                fi

                # Skip lines that are empty or don't contain valid data
                if [[ -z "$task_name" || -z "$total_seconds" ]]; then
                    continue
                fi

                # Sum the seconds for each task
                task_times["$task_name"]=$((task_times["$task_name"] + total_seconds))
            done < "$input_file"

            # Display the results in d:h:m:s format
            echo "Activity Name;Human Time Spent (d:h:m:s)"
            for task in "${!task_times[@]}"; do
                total_seconds="${task_times[$task]}"
                time_in_dhms=$(convert_to_dhms $total_seconds)
                echo "$task;$time_in_dhms"
            done

        }
        process_task_file $TT_LOGS
    }

    # Parse params
    if [ $# -eq 0 ]; then
        # No parameters = show help
        _options -h
    else
        _options "$1" "$2"
    fi
  }


# Autocomplete
# taken https://askubuntu.com/questions/68175/how-to-create-script-with-auto-complete
_tt()
{
    #local cur prev opts
    local cur opts
    COMPREPLY=()
    cur="${COMP_WORDS[COMP_CWORD]}"
    #prev="${COMP_WORDS[COMP_CWORD-1]}"
    opts="--help --start --pause --done --finish --abort --clear-logs --activity-name --logs --tot_time_activity --version"

    if [[ ${cur} == -* ]] ; then
        COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
        return 0
    fi
}
complete -F _tt tt

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions