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