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
As I had change a lot of things, here it is as a new minor version.