# Calculate Difference In Time Over Several Days

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
``````
• ...and to be careful about time zone changes and leap years! Your data doesn't have a time zone associated with it? Aug 17, 2018 at 13:20
• stackoverflow.com/q/12199631/1030675 , then just test `if (( DAYS > 0 ))`... Aug 17, 2018 at 13:20
• Or even just on U/L, from the related links on the right: unix.stackexchange.com/questions/27013/… Aug 17, 2018 at 13:42
• Good point about time zones... Everything about my environment is EST/EDT. Aug 17, 2018 at 14:05
• Aug 17, 2018 at 17:58

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'
``````

I worked on your script and came up with this:

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

CONVERTTIME() {
brief=false
[[ \$1 = "-b" ]] && shift && brief=true

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

narrative=()
list="\$w week"
list="\$d day"
list="\$h hour"
list="\$m minute"
list="\$s second"
for (( component=0; \$component < \${#list[@]}; ++component )); do
# Strip the verbage to get the number
val="\${list[\$component]/ [a-z]*}"
if [[ "\$val" -gt 0 ]]; then
if [[ "\$val" -gt 1 ]]; then
list[\$component]+='s'
fi
narrative+=( "\${list[\$component]}" )
fi
done

if \$brief; then
if [[ \${#narrative[@]} -eq 0 ]]; then
echo "Now"
else
fi
else
if [[ \${#narrative[@]} -eq 0 ]]; then
echo "Now"
elif [[ \${#narrative[@]} -eq 1 ]]; then
echo "\${narrative} ago"
elif [[ \${#narrative[@]} -eq 2 ]]; then
echo "\${narrative} and \${narrative} ago"
else
for (( index=0; \$index < \$((\${#narrative[@]}-1)); ++index )); do
echo -n "\${narrative[\$index]}, "
done
echo "and \${narrative[\$(( \${#narrative[@]}-1))]} ago"
fi
fi
}
echo
echo "TIME DIFFERENCE: \$(CONVERTTIME \$DIFF)"
echo
``````

So for example:

``````~ > CONVERTTIME 45
45 seconds ago
~ > CONVERTTIME 65
1 minute and 5 seconds ago
~ > CONVERTTIME 13985
3 hours, 53 minutes, and 5 seconds ago
~ > CONVERTTIME 139854
1 day, 14 hours, 50 minutes, and 54 seconds ago
~ > CONVERTTIME -b 139854