Date computations without GNU tools

date

I have to perform some date calculations and conversion in a shell script.
For example computing the difference in day between a date formatted as Nov 28 20:27:19 2012 GMT and today.

There are several possibilities (GNU date, gawk, perl, etc.) but I would like to be able to run it on systems without GNU tools (e.g., BSDs, Solaris, etc.)

At the moment the most portable solution I have is Perl but it could require the installation of additional modules for the date conversion.

What should I choose?

Edit: looking at the comments I realized that I need to clarify that the tool I am writing will be publicly distributed. I am not looking on a way to perform computations on my machine or on how to install GNU tools.

I want to use tools that are likely to be found on the majority of the machines.

Best Answer

I had to do this in the past with brute force parsing and calculation in shell script.

Doing it manually in shell script is highly error-prone and slow. You need to account for days per month, leap years, time zones. It will fail with different locales and different languages.

I fixed up one of my old scripts, this would probably need to be modified for different shell environments, but there shouldn't be anything that can't be changed to work in any shell.

#!/bin/sh 

datestring="Nov 28 20:27:19 2012 GMT"
today=`date`

function date2epoch {

month=$1
day=$2
time=$3
year=$4
zone=$5

# assume 365 day years
epochtime=$(( (year-1970) * 365 * 24 * 60 * 60 ))

# adjust for leap days
i=1970
while [[ $i -lt $4 ]]
do
    if [[ 0 -eq $(( i % 400 )) ]]
    then
        #echo $i is a leap year
        # divisible by 400 is a leap year
        epochtime=$((epochtime+24*60*60))
    elif [[ 0 -eq $(( i % 100 )) ]]
    then
        #echo $i is not a leap year
        epochtime=$epochtime
    elif [[ 0 -eq $(( i % 4 )) ]]
    then
        #echo $i is a leap year
        # divisible by 4 is a leap year
        epochtime=$((epochtime+24*60*60))
    #   epoch=`expr $epoch + 24 * 60 * 60`
    fi

    i=$((i+1))
done    

dayofyear=0
for imonth in Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec
do
    if [[ $month == $imonth ]]
    then
        break
    fi

    case $imonth in
    'Feb')
        if [[ 0 -eq $(( year % 400 )) ]]
        then
            days=29
        elif [[ 0 -eq $(( year % 100 )) ]]
        then
            days=28
        elif [[ 0 -eq $(( year % 4 )) ]]
        then
            days=29
        fi
    ;;

    Jan|Mar|May|Jul|Aug|Oct|Dec) days=31 ;;
    *) days=30 ;;
    esac

    #echo $imonth has $days days
    dayofyear=$((dayofyear + days))
done
## add the day of the month 
dayofyear=$((dayofyear+day))
#echo $dayofyear

########## Add the day fo year (-1) to the epochtime
#(-1, since eg. Jan 1 is not 24 hours into Jan1 )

epochtime=$((epochtime + (dayofyear -1) * 24*60*60))
#echo $epochtime
################## hours, minutes, seconds
OFS=$IFS
IFS=":"
set -- $time
hours=$1
minutes=$2
seconds=$3
epochtime=$((epochtime + (hours * 60 * 60) + (minutes * 60) + seconds))
IFS=$OFS

################## Time zone

case $zone in
'GMT') zonenumber=0
break;;
'EST') zonenumber=-5
break;;
'EDT') zonenumber=-4
break;;
esac

epochtime=$((epochtime + zonenumber * 60 * 60 ))

echo $epochtime
}

result=`date2epoch $datestring`

echo $result

I probably made a mistake somewhere and there may be a better way. Let me know if you find a bug, or a better way.

Once you have the epoch time, you can do some useful calculations... although converting that back into a date without the gnu utils... requires doing the above in reverse...

Related Question