calculating days and displaying them as year, months, days between two dates shell script

2.3k Views Asked by At

I am trying to write a shell script which is going to determine the difference in years, months and days between the present date and Easter from a user input year. For example the user inputs 1995 and the script should calculate how many years have passed since then and to convert these days into years, months and days and display the results. I'm pasting all of my code

#!/bin/bash

echo "This script will show you on which day is Easter for the chosen year of the Gregorian calendar!"

x=0
read x
A=$((x % 19))
B=$((x / 100))
C=$((x % 100))
D=$((B / 4))
E=$((B % 4))
G=$(((8 * B + 13) / (25)))
H=$(((19 * A + B - D - G + 15) % (30)))
M=$(((A + 11 * H) / (319)))
J=$((C / 4))
K=$((C % 4))
L=$(((2 * E + 2 * J - K - H + M + 32) % (7)))
N=$(((H - M + L + 90) / (25)))
P=$(((H - M + L + N + 19) % (32)))

Z=$(date --date="$x-$N-$P" +%A)
echo
echo "Easter is `date --date="$x-$N-$P"`"

([ "`cal 2 $x | grep 29`" != "" ] &&  echo -e "\n$x is a leap year\n")
([ "`cal 2 $x | grep 29`" = "" ] &&  echo -e "\n$x is not a leap year\n")

yearnow=$(date +%Y)
year=$(($x - $yearnow))
year1=$(($yearnow - $x))

if (($x > $yearnow))
then
echo "There are $year years until Easter in $x."
else
echo "$year1 years have passed since Easter in $x."
fi

pmonths=0
if (($x > $yearnow))
then
pmonths=$(($year * 12))
echo "There are $pmonths months until Easter."
else
pmonths=$(($year1 * 12))
echo "$pmonths months have passed since Easter in $x."
fi


#checking and counting how many leap and normal years are there between the present year and the chosen one
counter=1
leapycounter=0
nycounter=0
if (($x > $yearnow))
then
while (($counter < $year))
do
leapy=$(($x + $counter))
if (($leapy == (($leapy / 4) - ($leapy / 100) + ($leapy / 400))))
then leapycounter=$(($leapycounter + 1))
else nycounter=$(($nycounter + 1))
fi
counter=$(($counter + 1))
done
fi

#checking if the present year is leap so the days left from this year can be calculated
if (($x > $yearnow))
then
datenow=$(date +%j)
datenow=`echo $datenow|sed 's/^0*//'`
        if (($yearnow == (($yearnow / 4) - ($yearnow / 100) + ($yearnow / 400))))
          then
                datenow=$((366 - $datenow))
        else
                datenow=$((365 - $datenow))
        fi
datethen=$(date --date="$x-$N-$P" +%j)
datethen=`echo $datethen|sed 's/^0*//'`
days=$(($datethen + $datenow))
lyc=$((($leapycounter * 366) + ($nycounter * 365)))
dayspassed=$(($lyc + $days))
echo "There are $dayspassed days until Easter."
else
                datethen=$(date --date="$x-$N-$P" +%j)
                datethen=`echo $datethen|sed 's/^0*//'`
        if (($yearnow == (($yearnow / 4) - ($yearnow / 100) + ($yearnow / 400))))
        then
                datethen=$((366 - $datethen))
        else
                datethen=$((365 - $datethen))
        fi
datenow=$(date +%j)
datenow=`echo $datenow|sed 's/^0*//'`
days=$(($datethen + $datenow))
lyc=$((($leapycounter * 366) + ($nycounter * 365)))
dayspassed=$(($lyc + $days))
echo "$dayspassed days have passed since Easter in $x."
fi

#this should be converting the days into years, months and days
dtomconst=$(((((365/12)*3)+(366/12))/4))
months=$(($dayspassed / $dtomconst))
monthsleft=$(($months % 12))
years=$(($months / 12))
daysleft=$((($dayspassed - ($monthsleft * $dtomconst)) - (365*$years)))
echo "months are $months"
echo "daysleft are $daysleft"
echo $years
months=$(($months + $monthsleft))
echo $monthsleft
echo "months after calculations: $months"

So the problem is that it doesn't calculate the days properly especially for past years. Also if the user inputs a year like 1888 the script displays a mistake and I don't know why. If somebody can say a word or two about my problem I would be really grateful. Thank you in advance.

1

There are 1 best solutions below

2
On BEST ANSWER

As pointed out in the comments, the challenge with the script will be determining the day on which Easter occurred for a given year as the date varies from year to year given the order of weeks within each year. Further complicating the difference calculate is the concept of month as leap-year varies the length of February. There is also the occasional leap-second thrown in for good measure.

However, as indicated in the comment, once you have arrived at Easter for a given year, you can let the date function do most of the remaining work for you. Given any date, you can pass that value to the date function and covert the value into seconds since epoch. Note, this doesn't directly help for years prior to 1970, but you can extend further back manipulating the number of seconds per year as a close approximation.

Once you have Easter, getting the current time, expressed in terms of seconds since epoch, is trivial with date. You then have a difference to work with, converting the number of seconds that represent the time from the chosen Easter and now that can then be expressed in terms of years, days, hours, minutes, seconds. Granted these will have to be augmented to account for leap effects depending on the level of exactness required.

The following is a simple example of approaching the time difference problem. The function included, provides the difference given the time in seconds and then declares (as needed) the years, days, hours, minutes and seconds represented by the time given as an argument. This doesn't solve all of your issues, but hopefully it will help as a framework for handling that information in an easier manner. Let me know if you have any questions about the content:

#!/bin/bash

## time difference function takes an argument in seconds, and then calculates,
#  declares and fills variables 'years' 'days' 'hours' 'minutes' and 'seconds'
#  representing the time in seconds given as the argument. The values are only
#  declared as necessary allowing a test for their presence.
function sec2ydhms {

    [ -n $1 ] || { printf "%s() error: insufficient arguments\n" "$FUNCNAME"; return 1; }

    local secperday=$((24 * 3600))
    local secperyr=$((365 * secperday))
    local remain=$1

    # printf "\nremain: %s\n\n" "$remain"

    if [ "$remain" -ge "$secperyr" ]; then
        declare -g years=$((remain/secperyr))
        remain=$((remain - (years * secperyr)))
    fi

    if [ "$remain" -ge "$secperday" ]; then
        declare -g days=$((remain/secperday))
        remain=$((remain - (days * secperday)))
    fi

    if [ "$remain" -ge 3600 ]; then
        declare -g hours=$((remain/3600))
        remain=$((remain - (hours * 3600)))
    fi

    if [ "$remain" -ge 60 ]; then
        declare -g minutes=$((remain/60))
    fi

    declare -g seconds=$((remain - (minutes * 60))) 
}

oifs=$IFS                       # save old IFS, and set to only break on newline
IFS=$'\n'                       # allowing date formats containing whitespace
printf "\n Enter the date for Easter (in past): "
read edate                      # read date entered

eepoch=$(date -d "$edate" +%s)  # convert Easter date to seconds since epoch
now=$(date +%s)                 # get current time since epoch

sec2ydhms $((now-eepoch))       # compute time from Easter in Y,D,H,M,S

## print time since Easter
printf "\n Time since %s:\n\n" "$(date -d @"${eepoch}")"
[ -n "$years"   ] && printf "  %4s  years\n" "$years"
[ -n "$days"    ] && printf "  %4s  days\n" "$days"
[ -n "$hours"   ] && printf "  %4s  hours\n" "$hours"
[ -n "$minutes" ] && printf "  %4s  minutes\n" "$minutes"
[ -n "$seconds" ] && printf "  %4s  seconds\n\n" "$seconds"

exit 0

output:

$ bash easter.sh

 Enter the date for Easter (in past): 03/21/1985

 Time since Thu Mar 21 00:00:00 CST 1985:

    29  years
   254  days
    21  hours
    12  minutes
    16  seconds