#!/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"