Calculate Difference In Time Over Several Days

datetime

I've seen many questions and examples showing how to calculate the difference in two times by converting the times to epoch, and then converting the difference in seconds… However, what is the best way to calculate the difference in time that may span over several days in a $DAY days, $HOUR, hour(s), $MIN minute(s), and $SEC second(s)?

For my purpose, I'm using the date format, date +"%m/%d/%y %T" (08/17/18 09:03:31).

Let's say my first date is 08/15/18 16:22:05, and the second is 08/17/18 09:03:31. How can I calculate the difference by echoing "The difference is 1 day(s), 16, hours, 41 minutes, and 26 seconds."

But also, with the same script, how can it recognize if the time difference is less than a day to not report "The difference is 0 day…"?

EDIT: I have a mostly working script below, however, there's a lot going on in order to preserve proper formatting. Everything seems to work aside from the appropriate spaces and commas depending on if a value equals zero…

The idea is that it should format it with "and" in the appropriate spot when removing values that equal "0"…

So, all values:
02:05:23:44
2 days, 5 hours, 23 minutes, and 44 seconds

No hours:
02:00:23:44
2 days, 23 minutes, and 44 seconds

No seconds:
02:05:23:00
2 days, 5 hours, and 23 seconds

I had it so close awhile ago, but with certain readings, it'd break. Any help on formatting how I'd like? The script is also a long-mess… Any help cleaning it up would be nice. I'd like to see how to make it more efficient.

#!/bin/bash
TIME1="08/17/2018 11:36:40"
TIME2="08/17/2018 12:37:41"
SEC1=`date +%s -d "${TIME1}"`
SEC2=`date +%s -d "${TIME2}"`
DIFF=`expr ${SEC2} - ${SEC1}`

CONVERTTIME() {
  ((d=${1}/86400))
  ((h=(${1}%86400)/3600))
  ((m=(${1}%3600)/60))
  ((s=${1}%60))

  #DAYS
  if [ $d -eq 1 ]; then
    DAYLABEL=day
  else
    DAYLABEL=days
  fi
  DAYCOMMA=", "
  if [[ $d -gt 0 || $h && $m && $s -eq 0 ]]; then
    DAYCOMMA=""
  fi
  DAYTEXT="$d $DAYLABEL$DAYCOMMA"
  if [ $d -eq 0 ]; then
    DAYTEXT=
  else
    if [ $h -eq 0 ]; then
      DAYTEXT="$d $DAYLABEL$DAYCOMMA"
    fi
  fi
  #HOURS
  if [ $h -eq 1 ]; then
    HOURLABEL=hour
  else
    HOURLABEL=hours
  fi
  HOURCOMMA=", "
  if [[ $h -gt 0 || $m && $s -eq 0 ]]; then
    HOURCOMMA=""
  fi
  HOURTEXT="$h $HOURLABEL$HOURCOMMA"
  if [ $h -eq 0 ]; then
    HOURTEXT=""
  else
    if [ $m -eq 0 ]; then
      HOURTEXT="$h $HOURLABEL$HOURCOMMA"
    fi
  fi
  #MINUTES
  if [ $m -eq 1 ]; then
    MINLABEL=minute
  else
    MINLABEL=minutes
  fi
  MINCOMMA=", "
  if [[ $m -gt 0 || $s -eq 0 ]]; then
    MINCOMMA=""
  fi  
  MINTEXT="$m $MINLABEL$MINCOMMA"
  if [ $m -eq 0 ]; then
    MINTEXT=""
  else
    if [ $s -eq 0 ]; then
      MINTEXT="$d $MINLABEL$MINCOMMA"
    fi
  fi
  #SECONDS
  if [ $s -eq 1 ]; then
    SECLABEL=second
  else
    SECLABEL=seconds
  fi
  SECTEXT="$s $SECLABEL"
  if [ $s -eq 0 ]; then
    SECTEXT=""
  fi

  ANDHOUR=
  ANDMIN=
  ANDSEC=
  if [[ $d && $h && $m && $s -gt 0 || $d && $h && $s -gt 0 || $d && $m && $s -gt 0 || $h && $m && $s -gt 0 || $d && $s -gt 0 || $h && $s -gt 0 || $m && $s -gt 0 ]]; then
    ANDSEC="and "
  else
    #days hours AND minutes
    #days AND minutes
    #hours AND minutes
    if [[ $d && $h && $m -gt 0 || $d && $m -gt 0 || $h && $m -gt 0 ]]; then
      ANDMIN="and "  
    else
      #days AND hours
      if [[ $d && $h -gt 0 ]]; then
        ANDHOUR="and "
      fi
    fi  
  fi
  echo -e "$DAYTEXT$ANDHOUR$HOURTEXT$ANDMIN$MINTEXT$ANDSEC$SECTEXT"
}
echo
echo "TIME DIFFERENCE: $(CONVERTTIME $DIFF)"
echo

Best Answer

In some lines (assuming GNU date):

$ diff=$(($(date -ud '08/17/18 09:03:31' +'%s') - $(date -ud '08/15/18 16:22:05' +'%s')))
$ days=$(($(date -ud @$diff +'%-j')-1)) 
$ date -ud @"$diff" +"$days"'-day(s) %H:%M:%S'
1-day(s) 16:41:26

Valid up to (about) 365 days.

As an script:

#!/bin/sh

tosec(){ secs=$(date -ud "$1" +'%s'); }

tosec "$1"; sec1=$secs 
tosec "$2"; sec2=$secs
diff=$((sec2-sec1))
eval "$(date -ud "@$diff" +'days=%-j time=%H:%M:%S')"
printf '%s\n' "$((days-1))-day(s) $time"

Call it as:

$ ./script '08/15/18 16:22:05'   '08/17/18 09:03:31'
Related Question