improve this?

Wrote this script to find the date x days before or after today. Is there any way that this script can be speeded up or otherwise improved?

#!/usr/bin/sh

check_done() {
        if [ $month -eq 1 -o $month -eq 3 -o $month -eq 5 -o $month -eq 7 -o $month -eq 8 -o $month -eq 10 -o $month -eq 12 ]
        then
                daysofmth=31
        elif [ $month -eq 2 ]
        then
                if [ `expr $year % 100` -eq 0 -a `expr $year % 400` -eq 0 ]
                then
                        daysofmth=29
                elif [ `expr $year % 100` -ne 0 -a `expr $year % 4` -eq 0 ]
                then
                        daysofmth=29
                else
                        daysofmth=28
                fi
        elif [ $month -eq 4 -o $month -eq 6 -o $month -eq 9 -o $month -eq 11 ]
        then
                daysofmth=30
        fi
        julday=`expr $julday - $daysofmth`
        if [ $julday -lt 0 ]
        then
                done=1
                julday=`expr $daysofmth + $julday`
        elif [ $julday -eq 0 ]
        then
                done=1
                julday=$daysofmth
        fi
}
#########  main script starts here
if [ $# -ne 1 ]
then
        echo "Usage: tonfro <delta>"
        exit 1
fi
julday=`expr \`date +%j\` + $1`
year=`date +%Y`
month=0
while [ $julday -le 0 ]
do
        year=`expr $year - 1`
        if [ `expr $year % 100` -eq 0 -a `expr $year % 400` -eq 0 ]
        then
                julday=`expr $julday + 366`
        elif [ `expr $year % 100` -ne 0 -a `expr $year % 4` -eq 0 ]
        then
                julday=`expr $julday + 366`
        else
                julday=`expr $julday + 365`
        fi
done

done=0

while [ $done -ne 1 ]
do
        month=`expr $month + 1`
        if [ $month -eq 13 ]
        then
                year=`expr $year + 1`
                month=1
        fi
        check_done
done

printf "%d-%.2d-%.2d\n" $year $month $julday

Here is a thought.

How is it different from

date --date="2 days ago"
date --date="2 days later"
and likewise...

Another thing. Isnt expr an external command ? Why not use $( ) construct, instead ?

For eg.

`expr $year % 100` changes to $(($year % 100))

Vino

It does exactly what those commands do. Just that we dont have GNU 'date' and aren't allowed to install that either. So I came up with this. Just that the extreme cases like a difference of 100000 days takes a lot of time. Here is the output of uname -a:
SunOS xxxxxxx 5.8 Generic_117350-24 sun4u sparc SUNW,Ultra-Enterprise

There is almost no load on the server (uptime reports 0.06...).

Here are some 'time' command outputs for my script.

# time ./tonfro.sh 100
2005-11-11

real 0m0.31s
user 0m0.08s
sys 0m0.20s

# time ./tonfro.sh 10000
2032-12-19

real 0m7.84s
user 0m2.49s
sys 0m4.81s

# time ./tonfro.sh 100000
2279-05-19

real 1m16.19s
user 0m23.08s
sys 0m47.55s

--EDIT--
Is $() valid in sh? I am not using ksh. Also, assuming it is valid, will it run any faster than expr?
--/EDIT

I think you are looking for this.

From man sh

  Arithmetic Expansion
       Arithmetic expansion allows the evaluation of an arithmetic  expression
       and  the  substitution of the result.  The format for arithmetic expan-
       sion is:

              $((expression))

       The expression is treated as if it were within  double  quotes,  but  a
       double  quote  inside  the  parentheses  is not treated specially.  All
       tokens in the expression undergo parameter expansion, string expansion,
       command  substitution, and quote removal.  Arithmetic substitutions may
       be nested.

       The evaluation is performed according to the rules listed  below  under
       ARITHMETIC EVALUATION.  If expression is invalid, bash prints a message
       indicating failure and no substitution occurs.

How about combining the first two conditions into 1 ?

                if [ `expr $year % 100` -eq 0 -a `expr $year % 400` -eq 0 ]
                then
                        daysofmth=29
                elif [ `expr $year % 100` -ne 0 -a `expr $year % 4` -eq 0 ]
                then
                        daysofmth=29
                else
                        daysofmth=28
                fi

to

                if [ (($(($year % 100)) -eq 0 -a $(($year % 400)) -eq 0)) -o \
                (($(($year % 100)) -ne 0 -a $(($year % 4)) -eq 0)) ]
                then
                        daysofmth=29
                else
                        daysofmth=28
                fi

Haven't tested it tho'.

Aided by man sh

       ((expression))
              The expression is evaluated according  to  the  rules  described
              below  under ARITHMETIC EVALUATION.  If the value of the expres-
              sion is non-zero, the return status is 0; otherwise  the  return
              status is 1.  This is exactly equivalent to let "expression".

Vino

Sorry vino, the sh man page on my system does not show anything about Arithmetic Expansion. May be its not supported on SunOS 5.8?

Very much possible !

I am on a GNU based system.

Linux xxxxx 2.4.21-27.ELsmp #1 SMP Wed Dec 1 21:59:02 EST 2004 i686 i686 i386 GNU/Linux

Use ksh as your shell, and I think performance will improve.

Vino

Hey vino, thanks!

Apparently it was the 'expr' command that was slowing things down. I changed to '#!/usr/bin/ksh' and got a major speed up!
Here are the new 'time' outputs:

# time ./tonfro.sh 100
2005-11-11

real 0m0.18s
user 0m0.08s
sys 0m0.10s

# time ./tonfro.sh 10000
2032-12-19

real 0m4.02s
user 0m1.19s
sys 0m2.60s

# time ./tonfro.sh 100000
2279-05-19

real 0m38.60s
user 0m12.39s
sys 0m24.11s

Doesn't anyone read the faqs? Look up Yesterdays Date/Date Arithmetic which mentions my script called datecalc. Switch to that and see what timings you get.

Perderabo,
I know that your script is blinding quick (literally.. over a 1000 times faster). But you seem to be using some arithmetic that fits for your date range. Can you explain what exactly you are using?

I got that formula on the internet. Actually I got several that did the same thing, timed them, and picked the fastest. I don't know why it works. But I do that that it works. I used the forumula to compute the mjd of of the first date in my range. Then I incremented the the calendar date and recomputed the mjd and verified that it increased by one. So the forumla works over the entire range that I support. I needed a finite range to run that verification. Where the range came from: 1860 is the first year that Alaska used the Gregorian calendar. Prior to that, it followed Russia which was on the old Julian calendar. So in 1860, for the first time, no Julian calendar was in use anywhere in the USA. 3999 is the last year where there is unanimous agreement in the calculation of leap year. I didn't want to join the year 4000 fight.

Another way to cumpute any date in the past or in the future from today :

#------------------------------------------------------------------------
# Fonction .. : getDate [days [format]]
# Args ...... : $1 = days   : Offet from today [+|-]n  (def=0)
#               $2 = format : Format for the date display
# Example ... : o Yesterday => GetDate -1
#               o Tomorrow, yyyymmdd => GetDate +1 '+%Y%m%d'
#------------------------------------------------------------------------

function getDate { # GetDate days [format]

   l_days=0
   l_format=''

   if [ $# -ge 1 ]
   then
      l_days=$1
      shift
   fi

   l_format="$*"

   l_local_offset=$(echo $TZ | sed 's![^-0-9]*\([-0-9]*\).*!\1!')
   l_new_offset=`expr $l_local_offset - 24 \* $l_days`
   l_new_tz="`echo $TZ | sed 's!^\([^-0-9]*\)[-0-9]*\(.*\)$!\1'${l_new_offset}'\2!'`"

   TZ="$l_new_tz" date "$l_format"

   unset l_days l_format l_local_offset l_new_offset l_new_tz
}

#########  main script starts here
if [ $# -ne 1 ]
then
        echo "Usage: tonfro <delta>"
        exit 1
fi

getDate $1 '+%Y-%m-%d'

On my AIX box, the offset is limited in days to the range [-37854,+1855].
The original KSH GetDate function was written by PHV in the 'Unix Scripting FAQ' of www.tek-tips.com

time ./tonfro.sh 1
2005-08-05

r�el 0m0,07s
util 0m0,01s
sys 0m0,03s

time ./tonfro.sh 10000
2032-12-20

r�el 0m0,06s
util 0m0,01s
sys 0m0,03s