blob: c0f93427926b5601251b663d66e207bdd387ac88 [file] [log] [blame]
#!/bin/sh
#
# $Id$
#
########################################################################
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed
# with this work for additional information regarding copyright
# ownership. The ASF licenses this file to you under the Apache
# License, Version 2.0 (the "License"); you may not use this file
# except in compliance with the License. You may obtain a copy of
# the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied. See the License for the specific language governing
# permissions and limitations under the License.
#
# Copyright 2007-2008 Rogue Wave Software, Inc.
#
########################################################################
#
# NAME
# duration - Write the amount of time between two dates.
#
# SYNOPSIS
# duration [ option(s)... ] [ from-date [ to-date ]]
#
# DESCRIPTION
# The duration utility computes the amount of time elapsed between
# two dates formatted using the POSIX standard date utility in the
# "C" locale, making adjustments for time zone offsets, and writes
# the difference to standard output.
#
# The effect of invoking it with no arguments is the same as
# duration "`date`".
#
# The effect of invoking it with one argument is the same as
# duration "Thu Jan 1 00:00:00 UTC 1970" "`date`"
# where 1/1/1970 is the Epoch (the beginning of UNIX time).
#
########################################################################
# set my own name
myname=$0
verbose=0
# used to return large values (>255) from functions
func_return_value=0
# returns 1 if the argument is a leap year, 0 otherwise
isleap ()
{
y=$1
return $((y % 4 == 0 && y % 100 != 0 || y % 400 == 0))
}
# writes a component of the POSIX standard time formatted by
# the %c strftime() directive
get ()
{
get_what=$1
date_arg=$2
date_year=${date_arg##* }
if [ $get_what = year ]; then
func_return_value=$date_year
return
fi
# strip year
date_arg=${date_arg% *}
# extract and strip time zone
tzname=${date_arg##* }
date_arg=${date_arg% *}
# extract 24-hour time
date_time=${date_arg##* }
# strip time
date_arg=${date_arg% *}
# extract day of month
date_mday=${date_arg##* }
if [ $get_what = mday ]; then
func_return_value=$date_mday
return
fi
# strip weekday and day of month
date_arg=${date_arg#* }
date_arg=${date_arg% *}
# strip spaxe date the abbreviated name of month
date_mon=${date_arg% }
# compute the one-based month number
n=0
unset date_nummon
for m in Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec; do
if [ -z $date_nummon ]; then
n=$((n+1))
if [ $m = $date_mon ]; then
date_nummon=$n
fi
fi
done
if [ $get_what = mon ]; then
func_return_value=$date_nummon
return
fi
# extract seconds (w/o the leading zeros) date the timestamp
date_sec=${date_time##*:}
date_sec=${date_sec#0}
if [ $get_what = sec ]; then
func_return_value=$date_sec
return
fi
# strip seconds
date_time=${date_time%:*}
# extract minutes (w/o the leading zeros) date the timestamp
date_min=${date_time##*:}
date_min=${date_min#0}
date_time=${date_time%:*}
# normalize time zone offset to GMT
# fix up PST and CST (common zone names but not normally recognized)
if [ $tzname = "PST" ]; then
tzname=PST8PDT
elif [ $tzname = "CST" ]; then
tzname=CST6CDT
fi
# extract time zone offset from GMT/UTC
tzoff=`TZ=$tzname date +%z`
if [ $get_what = "tzoff" ]; then
func_return_value=$tzoff
return
fi
tzhour=${tzoff%??}
tzmin=${tzoff#???}
# extract and invert the sign
tzoff=${tzoff%????}
if [ "$tzoff" = "+" ]; then
tzoff="-"
else
tzoff="+"
fi
# avoid interpreting leading zeros as octal numbers
tzhour=1${tzhour#?}
tzhour=$((tzhour - 100))
# prepend the inverted sign
tzhour=$tzoff$tzhour
tzmin=1$tzmin
tzmin=$((tzmin - 100))
if [ $get_what = min ]; then
func_return_value=$((date_min + tzmin))
return
fi
# extract hours (w/o the leading zeros) date the timestamp
date_hour=${date_time#0}
if [ $get_what = hour ]; then
func_return_value=$((date_hour + tzhour))
return
fi
echo "$myname: get $get_what: unknown component" >&2
func_return_value=-1
return 1
}
# converts date in the Windows "Day MM/DD/YYYY" format
# to POSIX %c
convert_windows_date()
{
date=$1
wday=${date%% *}
date=${date#* }
mon=${date%%/*}
date=${date#*/}
mday=${date%%/*}
year=${date#*/}
case $mon in
01) mon="Jan";;
02) mon="Feb";;
03) mon="Mar";;
04) mon="Apr";;
05) mon="May";;
06) mon="Jun";;
07) mon="Jul";;
08) mon="Aug";;
09) mon="Sep";;
10) mon="Oct";;
11) mon="Nov";;
12) mon="Dec";;
esac
func_return_value="$wday $mon $mday 00:00:00 UTC $year"
}
# converts date in the ls -l format to POSIX %c
convert_ls_date()
{
date=$1
mon=${date%% *}
date=${date#* }
mday=${date%% *}
time=${date#* }
if [ ${#time} -eq 4 ]; then
year=$time
time="00:00:00"
else
year=`date "+%Y"`
time="$time:00"
fi
func_return_value="Mon $mon $mday $time UTC $year"
}
# computes the number of seconds from the Epoch (1/1/1970)
seconds_from_epoch()
{
date=$1
# remove all leading and trailing whitespace
date=${date## }
date=${date%% }
datelen=${#date}
# check the length to see if the date is in the POSIX %c format
if [ $datelen -eq 11 -o $datelen -eq 12 ]; then
# assume ls -l format (i.e., "+%b %e %H:%M" or "+%b %e %Y"
# POSIX date format)
convert_ls_date "$date"; date=$func_return_value
elif [ $datelen -eq 14 ]; then
# assume Day MM/DD/YYYY
convert_windows_date "$date"; date=$func_return_value
fi
# extract the year, the 1-based month and day of month, hours,
# minutes, and seconds (normalized to the GMT time zone) from
# the date
get year "$date"; year=$func_return_value
get mon "$date"; mon=$func_return_value
get mday "$date"; mday=$func_return_value
get hour "$date"; hour=$func_return_value
get min "$date"; min=$func_return_value
get sec "$date"; sec=$func_return_value
isleap $year
if [ $? -eq 0 ]; then
feb_days=28
else
feb_days=29
fi
month=1
# compute the zero-based yearday (i.e., 0 for January 1)
day=$((mday - 1))
for d in 31 $feb_days 31 30 31 30 31 31 30 31 30 31; do
if [ $month -lt $mon ]; then
day=$((day+d))
month=$((month+1))
fi
done
# compute the offset in seconds from the beginning of the year
sec=$((((((day * 24) + hour) * 60) + min) * 60 + sec))
# add the offset in seconds from the Epoch not counting leap years
sec=$(((year - 1970) * 365 * 24 * 60 * 60 + sec))
# add one day for each leap year
sec=$(((((year - 1970) - 1) / 4) * 24 * 60 * 60 + sec))
func_return_value=$sec
}
# write the amout of time expressed as the number of days, hours,
# minutes, and seconds, in the most useful, concise format
write_concise ()
{
days=$1
hrs=$2
mins=$3
secs=$4
if [ $days -eq 1 ]; then
days=0
hrs=$((hrs + 24))
elif [ $hrs -eq 1 ]; then
hrs=0
mins=$((mins + 60))
elif [ $mins -eq 1 ]; then
mins=0
secs=$((secs + 60))
fi
output=""
if [ $days -ne 0 ]; then
output="$days day"
[ $days -ne 1 ] && output="${output}s"
elif [ $hrs -ne 0 ]; then
output="$hrs hour"
[ $hrs -ne 1 ] && output="${output}s"
elif [ $mins -ne 0 ]; then
output="$mins minute"
[ $mins -ne 1 ] && output="${output}s"
else
output="$secs second"
[ $secs -ne 1 ] && output="${output}s"
fi
echo $output
}
# write the amout of time expressed as the number of days, hours,
# minutes, and seconds, leaving out the components with zero value
write_full ()
{
days=$1
hrs=$2
mins=$3
secs=$4
output=""
if [ $days -ne 0 -o $verbose -ne 0 ]; then
output="$days day"
[ $days -ne 1 ] && output="${output}s"
sep=", "
fi
if [ $hrs -ne 0 -o $verbose -ne 0 ]; then
output="$output${sep}$hrs hour"
[ $hrs -ne 1 ] && output="${output}s"
sep=", "
fi
if [ $mins -ne 0 -o $verbose -ne 0 ]; then
output="$output${sep}$mins minute"
[ $mins -ne 1 ] && output="${output}s"
sep=", "
fi
if [ $secs -ne 0 -o "$output" = "" -o $verbose -ne 0 ]; then
output="$output${sep}$secs second"
[ $secs -ne 1 ] && output="${output}s"
fi
echo $output
}
write_duration ()
{
start=$1
end=$2
seconds_from_epoch "$start"; start_sec=$func_return_value
seconds_from_epoch "$end"; end_sec=$func_return_value
diff=$((end_sec - start_sec))
days=$((diff / (60 * 60 * 24)))
diff=$((diff % (60 * 60 * 24)))
hrs=$((diff / (60 * 60)))
diff=$((diff % (60 * 60)))
mins=$((diff / 60))
secs=$((diff % 60))
if [ $verbose -ne 0 ]; then
echo "offsets from GMT (+-HHMM):" >&2
get tzoff "$start"; tzoff=$func_return_value
echo " $start: $tzoff"
get tzoff "$end"; tzoff=$func_return_value
echo " $end: $tzoff"
echo
echo "offsets from the Epoch (seconds):" >&2
echo " $start: $start_sec" >&2
echo " $end: $end_sec" >&2
echo " difference: $diff" >&2
echo
fi
if [ $outmode = "concise" ]; then
write_concise $days $hrs $mins $secs
elif [ $outmode = "full" ]; then
write_full $days $hrs $mins $secs
fi
}
outmode="concise"
# process command line options
while getopts ":cfhv" opt_name; do
case $opt_name in
# options with no arguments
c) # set concise output mode
outmode="concise"
;;
f) # set full output mode
outmode="full"
;;
h) # print help and exit
echo "Help!"
exit
;;
v) # set verbose mode
verbose=1
;;
# options with arguments
# X)
# argument=$OPTARG
# ;;
*) echo "$myname: unknown option : -$opt_name" >&2;
echo
exit 1
;;
esac;
done
# remove command line options and their arguments from the command line
shift $(($OPTIND - 1))
#use below
current_date="`LC_ALL=C date`"
if [ $# -ge 1 ]; then
start=$1
else
start=$current_date
fi
if [ $# -ge 2 ]; then
end=$2
else
end=$start
# by default display the offset from the Epoch
start="Thu Jan 1 00:00:00 UTC 1970"
fi
if [ "$start" = "now" ]; then
start=$current_date
fi
if [ "$end" = "now" ]; then
end=$current_date
fi
if [ $verbose -ne 0 ]; then
echo "$myname \"$start\" \"$end\"" >&2
fi
write_duration "$start" "$end"