Article Listing 1 Listing 2 Listing 3 Listing 4
Sidebar jun2004.tar

Listing 2 datediff

#!/bin/ksh
# Listing 2: datediff
# prints the difference between two dates (output is in seconds)
# usage: datediff date1 time1 date2 time2
# date format: YYYY/mm/dd 
# time format: hh:mm:ss

. /etc/setenv_path
TRUE=0
FALSE=1

# julian_date() returns the number days from 1 January 4713 BC.
# The algorithm is good from years 1801 to 2099.
# See: http://aa.usno.navy.mil/faq/docs/JD_Formula.html 
# arguments: $1 = day, $2 = month, $3 = year in format YYYY

readonly JD_LOWER_LIMIT=1801
readonly JD_UPPER_LIMIT=2099

julian_date()
{
    echo $(($1-32075+1461*($3+4800+($2-14)/12)/4+367* \
      ($2-2-($2-14)/12*12)/12-3*(($3+4900+($2-14)/12)/100)/4))
}

is_leap_year()
{
    # a year is a leap year if it is even divisible by 4
    # but not evenly divisible by 100
    # unless it is evenly divisible by 400

    [[ -n $1 ]] && year=$1 || ( echo "missing year" && exit 1 )

    if (($year % 4 == 0)) && (($year % 100 != 0)) || (($year % 400 == 0)) 
    then
        return $TRUE
    else
        return $FALSE
    fi
}

year_days()
{
    [[ -n $1 ]] && year=$1 || ( echo "missing year" && exit 1 )

    if is_leap_year $year
    then
        echo 366
    else
        echo 365
    fi
}

month_days()
{
    [[ -n $1 ]] && year=$1 || ( echo "missing year" && exit 1 )
    [[ -n $2 ]] && month=$2 || ( echo "missing month" && exit 1 )

    case $month in
        1) echo 31 ;;
        2)
            if is_leap_year $year
            then
                echo 29
            else
                echo 28
            fi
        ;;
        3) echo 31 ;;
        4) echo 30 ;;
        5) echo 31 ;;
        6) echo 30 ;;
        7) echo 31 ;;
        8) echo 31 ;;
        9) echo 30 ;;
        10) echo 31 ;;
        11) echo 30 ;;
        12) echo 31 ;;
    esac
}

ordinal_day()
{
    [[ -n $1 ]] && year=$1 || ( echo "missing year" && exit 1 )
    [[ -n $2 ]] && month=$2 || ( echo "missing month" && exit 1 )
    [[ -n $3 ]] && day=$2 || ( echo "missing day" && exit 1 )

    sum=$day
    while (( $month > 1 )) 
    do
        month=$(($month - 1))
        days_in_month=$(month_days $year $month)
        sum=$(($sum + $days_in_month))
    done
    echo $sum
}

month_to_int()
{
    [[ -n $1 ]] && month=$1 || ( echo "missing month" && exit 1 )

    case $month in
        1 | 01 | jan* | JAN* | Jan*) echo 1 ;;
        2 | 02 | feb* | FEB* | Feb*) echo 2 ;;
        3 | 03 | mar* | MAR* | Mar*) echo 3 ;;
        4 | 04 | apr* | APR* | Apr*) echo 4 ;;
        5 | 05 | may* | MAY* | May*) echo 5 ;;
        6 | 06 | jun* | JUN* | Jun*) echo 6 ;;
        7 | 07 | jul* | JUL* | Jul*) echo 7 ;;
        8 | 08 | aug* | AUG* | Aug*) echo 8 ;;
        9 | 09 | sep* | SEP* | Sep*) echo 9 ;;
        10 | oct* | OCT* | Oct*) echo 10 ;;
        11 | nov* | NOV* | Nov*) echo 11 ;;
        12 | dec* | DEC* | Dec*) echo 12 ;;
        *) echo 0 ;;
    esac
}

# Verify that 4 parameters were passed to the program.

[[ -n $1 ]] && date1=$1 || ( echo "missing date1" && exit 1 )
[[ -n $2 ]] && time1=$2 || ( echo "missing time1" && exit 1 )
[[ -n $3 ]] && date2=$3 || ( echo "missing date2" && exit 1 )
[[ -n $4 ]] && time2=$4 || ( echo "missing time2" && exit 1 )

# Parse the date/time strings.

readonly DELIMITER_TIME=":"
readonly DELIMITER_DATE="/"
readonly DELIMITER_ORIGINAL=$IFS

IFS=$DELIMITER_DATE

echo "$date1" | read year1 month1 day1
echo "$date2" | read year2 month2 day2

IFS=$DELIMITER_TIME

echo "$time1" | read hr1 min1 sec1
echo "$time2" | read hr2 min2 sec2

IFS=$DELIMITER_ORIGINAL

# Convert the month to an integer.

month1=$(month_to_int $month1)
month2=$(month_to_int $month2)

if (( $JD_LOWER_LIMIT <= year1 )) && (( year1 <= $JD_UPPER_LIMIT )) \
&& (( $JD_LOWER_LIMIT <= year2 )) && (( year2 <= $JD_UPPER_LIMIT ))
then

    jd1=$(julian_date $day1 $month1 $year1)
    jd2=$(julian_date $day2 $month2 $year2)

    seconds1=$(($jd1*24*3600 + $hr1*3600 + $min1*60 + $sec1))
    seconds2=$(($jd2*24*3600 + $hr2*3600 + $min2*60 + $sec2))

    echo $(( $seconds2 - $seconds1 ))

else

    # Calculate the "ordinal second" for each date/time.

    ordinal_day1=$(ordinal_day $year1 $month1 $day1)
    ordinal_sec1=$(($ordinal_day1*24*3600 + $hr1*3600 + $min1*60 + $sec1))

    ordinal_day2=$(ordinal_day $year2 $month2 $day2)
    ordinal_sec2=$(($ordinal_day2*24*3600 + $hr2*3600 + $min2*60 + $sec2))

    # Determine whether the difference should be positive or negative,
    # and set the small-year and big-year variables appropriately.

    if (( $year1 > $year2 ))
    then
        big_year=$year1
        ordinal_day_by=$ordinal_day1
        ordinal_sec_by=$ordinal_sec1
        small_year=$year2
        ordinal_day_sy=$ordinal_day2
        ordinal_sec_sy=$ordinal_sec2
        sign="-"
    elif (( $year2 > $year1 ))
    then
        big_year=$year2
        ordinal_day_by=$ordinal_day2
        ordinal_sec_by=$ordinal_sec2
        small_year=$year1
        ordinal_day_sy=$ordinal_day1
        ordinal_sec_sy=$ordinal_sec1
        sign=""
    else

        # If the years are the same, just do the subtraction.

        diff_secs=$(($ordinal_sec2 - $ordinal_sec1))
        echo $diff_secs
        exit 0
    fi

    # Calculate the seconds remaining in the small year.

    year_days_sy=$(year_days $small_year)
    yearsecs_sy=$(($year_days_sy * 24 * 3600))
    remaining_secs_sy=$(($yearsecs_sy - $ordinal_sec_sy))

    # Calculate seconds in the years between the big year and the small year.

    sum_days=0
    next_year=$(($small_year + 1))
    while (( $next_year < $big_year ))
    do
        year_days_ny=$(year_days $next_year)
        sum_days=$(($sum_days + $year_days_ny))
        next_year=$(($next_year + 1))
    done
    sum_secs_ny=$(($sum_days * 24 * 3600))

    # Add: the seconds left in the small year + the seconds in the years 
    # between the small and the bigs years + the seconds elapsed in the 
    # big year.

    diff_secs=$(($remaining_secs_sy + $sum_secs_ny + $ordinal_sec_by))

    # Print the result with the appropriate sign.

    echo "$sign$diff_secs"

fi