5

Is it possible to use bash to subtract variables containing 24-hour time?

#!/bin/bash
var1="23:30" # 11:30pm
var2="20:00" # 08:00pm

echo "$(expr $var1 - $var2)"

Running it produces the following error.

./test 
expr: non-integer argument

I need the output to appear in decimal form, for example:

./test 
3.5
  • Thanks for pointing that you. You're probably right. I've updated the question. – user328302 Dec 24 '18 at 12:37
  • 1
    What are your constraints? Shell builtins only, or external programs like bc or awk ? – Mark Plotnick Dec 24 '18 at 12:39
  • Related: unix.stackexchange.com/q/24626/315749 – fra-san Dec 24 '18 at 12:41
  • @fra-san, the dates are static, in variables, so it's kinda confusing to use date to solve this problem. @MarkPlotnick, no constraints, which ever method requires the least code. I'm ready to accept your Answer – user328302 Dec 24 '18 at 12:47
  • See also my answer here: stackoverflow.com/questions/8385627/… – Victoria Stuart Apr 25 at 19:27
8

The date command is pretty flexible about its input. You can use that to your advantage:

#!/bin/bash
var1="23:30"
var2="20:00"

# Convert to epoch time and calculate difference.
difference=$(( $(date -d "$var1" "+%s") - $(date -d "$var2" "+%s") ))

# Divide the difference by 3600 to calculate hours.
echo "scale=2 ; $difference/3600" | bc

Output:

$ ./test.bash
3.50
5

Using only bash, with no external programs, you could do so something like this:

#!/bin/bash

# first time is the first argument, or 23:30     
var1=${1:-23:30}
# second time is the second argument, or 20:00
var2=${2:-20:00}

# Split variables on `:` and insert pieces into arrays
IFS=':' read -r -a t1 <<< "$var1"
IFS=':' read -r -a t2 <<< "$var2"

# strip leading zeros (so it's not interpreted as octal
t1=("${t1[@]##0}")
t2=("${t2[@]##0}")

# check if the first time is before the second one
if (( t1[0] > t2[0] || ( t1[0] == t2[0] && t1[1] > t2[1]) ))
then
  # if the minutes on the first time are less than the ones on the second time
  if (( t1[1] < t2[1] ))
  then
    # add 60 minutes to time 1
    (( t1[1] += 60 ))
    # and subtract an hour
    (( t1[0] -- ))
  fi
  # now subtract the hours and the minutes
  echo $((t1[0] -t2[0] )):$((t1[1] - t2[1]))
  # to get a decimal result, multiply the minutes by 100 and divide by 60
  echo $((t1[0] -t2[0] )).$(((t1[1] - t2[1])*100/60))
else
  echo "Time 1 should be after time 2" 2>&1
fi

Test:

$ ./script.sh 
3:30
3.50

$ ./script.sh 12:10 11:30
0:40
0.66

$ ./script.sh 12:00 11:30
0:30
0.50

If you want more complex time differences, that could span different days etc, then it's probably best to use GNU date.

  • Wow, thanks. Would you be willing to add a few quick comments above some of the lines of code that explain what's happening? – user328302 Dec 24 '18 at 12:55
  • @user328302: I added some comments, let me know if anything is still unclear. I also fixed a bug that wouldn't allow times like 09:08 (it was treated as octal) – user000001 Dec 24 '18 at 13:17

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.