How to find the total time I spent on my laptop in this year?

Trying with bash script and extending your command. I use dateutils to add up the time duration.

Hence to use this script one needs the dateutils package available through apt. ( sudo apt install dateutils )

This script takes into account the current uptime (present session) also, hence more accurate. Seconds are not counted. Lowest unit reported is minute.

#!/bin/bash
# Total uptime reported.
temp=$(last reboot --since 2016-01-01 --until 2016-12-31 | grep -o '(.*)' | grep  -v '-' | sed 's/(\([0-9]\{1,\}\)+\([0-9]\{1,\}\):\([0-9]\{1,\}\))/\1d\2h\3m/g ;s/(\([0-9]\{1,\}\):\([0-9]\{1,\}\))/\1h\2m/g'  )
curr=$( cat /proc/uptime |  perl -ne '/(\d*)/ ; printf "%02d:%02d:%02d\n",int($1/86400),int(($1%86400)/3600),int(($1%3600)/60)'  )
echo "Total run time (days:hours:minutes)"
curr="2015-01-01T"$curr
org="2015-01-01T00:00:00"
new=$(dateutils.dadd $curr $temp )
dateutils.ddiff $org $new -f "%dd %Hh %Mm"
  • First the uptimes from last reboot is listed and formatted to extract the day, hours, minutes and second information. This is then saved in temp.
  • A reference fake date is set called org=2015-01-01 to which current uptime is added.
  • Then all cumulative uptimes are added to the variable new
  • The duration between org and the net uptime new is found by difference.

Output:

vayu@helix:$ ./uptime_record.sh
Total run time (days:hours:minutes)
57d 20h 36m

The following script is for the uptime in exactly one year from the day the script is run.

#!/bin/bash
# Total uptime reported since exactly 1 year (from the time script is run).
now="$(date +'%Y-%m-%d')" ;
last_y=$(dateutils.dadd $now -1y)
temp=$(last reboot --since "$last_y" --until "$now" | grep -o '(.*)' | grep  -v '-' | sed 's/(\([0-9]\{1,\}\)+\([0-9]\{1,\}\):\([0-9]\{1,\}\))/\1d\2h\3m/g ;s/(\([0-9]\{1,\}\):\([0-9]\{1,\}\))/\1h\2m/g'  )
curr=$( cat /proc/uptime |  perl -ne '/(\d*)/ ; printf "%02d:%02d:%02d\n",int($1/86400),int(($1%86400)/3600),int(($1%3600)/60)'  )
echo "Total run time in one year (days:hours:minutes)"
curr="1980-01-01T"$curr
org="1980-01-01T00:00:00"
new=$(dateutils.dadd $curr $temp )
dateutils.ddiff $org $new -f "%dd %Hh %Mm"

Here is a python implementation of what I want, but I am sure there is an elegant way to do it with bash:

import subprocess
output = subprocess.run("last reboot --since 2016-01-01 --until 2016-12-31 | grep -o '(.*)' | grep  -v '-'| sed -e 's/(//g; s/)//g; s/+/:/g'", shell=True, stdout=subprocess.PIPE, universal_newlines=True)
mins_total = 0

for line in output.stdout.split('\n')[:-1]:
    try:
        (hrs, mins) = [int(k) for k in line.split(':')]
        days = 0
    except:
        (days, hrs, mins) = [int(k) for k in line.split(':')]
    mins_total += (days*24 + hrs)*60 + mins

print("Hours: " + str(mins_total/60))
print("Days: " + str(mins_total/60/24))

Here's an awk + awk version:

last ... | awk '/reboot/{print $NF}' |
    awk -F '[(+:)]' '
        {
            d += $(NF - 3); h += $(NF - 2); m += $(NF - 1)
        }
        END {
            carry = m / 60; h += carry;
            carry = h / 24; d += carry;
            printf "%d days, %d hours, %d minutes\n", d, h % 24, m % 60
        }'

last's last column is in the format (<days>+hours:minutes), where days+ is dropped if the period is less than 1 day.

Here, the first awk command outputs the last column, the duration of interest, for reboot entries.

For the second awk command:

  1. FS is [(+:)], i.e., parentheses or + or :. So, (h:m) is split to , h, m and (first and last fields empty), and (d+h:m) is split to , d, h, m and (again, first and last fields empty).
  2. Then we take the second-last field for minutes, third-last for hours and fourth-last for days. The fourth-last field will be the first, empty, field if days is not present. So, we'll be simply adding 0 in this case.
  3. Then we bump up the hours and days by if minutes and hours are above 60 and 24 respectively. Note that awk does floating point division, so h and d may now have fractional parts.
  4. Then we print the numbers as integers (%d), so any fractional part is ignored.