My wife asked me an intersting question the other day.
Could you write a program to find out in what years my birthday falls on a Sabbath?
She doesn't usually ask me to code, well anything, ever. I told her that it seems very possible.
I thought it would be a simple thing to use the unix date command and add some seconds to the epoch time. Well, this wasn't the case. What's so special about Mon Jan 18 22:14:07 EST 2038? This is the largest date that date -r [seconds] will go to. In fact most utilities start falling apart around then, like (the ever so useful) clock scan function in tclsh. Just try the two commands and tell me which one fails:
echo "puts [clock scan {Dec 31 23:59:59 2037}]" | tclsh
echo "puts [clock scan {Jan 1, 2038}]" | tclsh
I remembered that there was some trick that could be used to figure out the day of the week for any date. A little googling and it all came together. (Ah, Google. So evil and yet so nice!)
#!/bin/bash
# This uses the modified Doomsday method as proposed here:
# http://en.wikipedia.org/wiki/Calculating_the_day_of_the_week
# (the Doomsday method was first proposed by John Horton Conway).
# The next update should encorporate Sohael S. Babwani's
# more efficient formulation.
show_help()
{
base=${0##*/}
cat << EOF >&2
'${base}' finds all years in which a date falls on the sabbath
Usage:
$base [month] [date] [start year] [end year]
[month] is the month (January, February, etc.)
[day] is the date (1 through 31)
[start year] can be any year after 1000
[end year] can be any year before 2599
Options:
-help this help
-license shows the license
EOF
}
show_license()
{
base=${0##*/}
echo "
This work (\"${base}\") is licensed by O. Ganesh under the
Creative Commons Attribution-NonCommercial-ShareAlike 2.5 License
http://creativecommons.org/licenses/by-nc-sa/2.5/legalcode
any derivative works based on this work shall reference the author
"
}
month_table()
{
# month table
mmm=$1; leap=$2;
case $mmm in
Jan )
if [ $leap ]; then m_val=6;
else m_val=0;fi;;
Feb )
if [ $leap ]; then m_val=2;
else m_val=3;fi;;
Mar ) m_val=3;;
Apr ) m_val=6;;
May ) m_val=1;;
Jun ) m_val=4;;
Jul ) m_val=6;;
Aug ) m_val=2;;
Sep ) m_val=5;;
Oct ) m_val=0;;
Nov ) m_val=3;;
Dec ) m_val=5;;
esac
echo $m_val
}
century_table()
{
cu=$1
case $cu in
10 ) cu_val=1;;
11 ) cu_val=0;;
12 ) cu_val=6;;
13 ) cu_val=5;;
14 ) cu_val=4;;
15 ) cu_val=3;;
16 ) cu_val=2;;
17a ) cu_val=1;; # dates from 1700 to 1752
17 ) cu_val=4;; # dates after 1752
18 ) cu_val=2;;
19 ) cu_val=0;;
20 ) cu_val=6;;
21 ) cu_val=4;;
22 ) cu_val=2;;
23 ) cu_val=0;;
24 ) cu_val=6;;
25 ) cu_val=4;;
esac
echo $cu_val
}
need_help()
{
echo "invalid input"
base=${0##*/};echo "try '$base -help' for help"
exit 1
}
case $# in
1)
# possible option call
case $1 in
help | h | -help | -h | --help | --h )
show_help; exit 0;;
lic | license | -lic | -license | --lic | --license )
show_license; exit 0;;
*) need_help;;
esac;;
4)
# sanity checking
if [ $3 -lt 1000 -o $4 -gt 2599 -o $3 -gt $4 ];then
need_help
fi;;
*)
# erroneous input
need_help
esac
# warning about calendar changes
if [ $3 -lt 1753 ];then
echo "dates before 1753 may not be correct due to calendar changes"
fi
# find epoch seconds with tclsh
bdate="$1 ${2}"
epoch_sec=`echo "puts [clock scan { $bdate }]" | tclsh`
if [ ! $epoch_sec ]; then
echo "invalid date"
show_help
exit 1
fi
# nice date check
birthdate=`date -r $epoch_sec `
birthday=`echo $birthdate | cut -d" " -f1`
case $birthday in
Sun ) bday="Sunday";;
Mon ) bday="Monday";;
Tue ) bday="Tuesday";;
Wed ) bday="Wednesday";;
Thu ) bday="Thursday";;
Fri ) bday="Friday";;
Sat ) bday="Saturday";;
esac
echo "This year that date is a ${bday} (`echo $birthdate | cut -d" " -f2-3`, 2007)."
# (cu + cl + round(cl/4) + m + d)%7 = 6
# and the answer of 7 * 6 (wrongly) is 42
# coincidentally this is the answer to life, the universe, and everything
# (this is a joke, don't email me about it)
d_val=`echo $birthdate | cut -d" " -f3`
mmm=`echo $birthdate | cut -d" " -f2`
yyyy=`echo $birthdate | cut -d" " -f6`
# prep for leap year check
leap_ck400=`echo "scale=2;$yyyy / 400" | bc | sed 's/\.00//'`
leap_ck100=`echo "scale=2;$yyyy / 100" | bc | sed 's/\.00//'`
leap_ck4=`echo "scale=2;$yyyy / 4" | bc | sed 's/\.00//'`
# leap year check
# good help from wikipedia to clarify the issue
# http://en.wikipedia.org/wiki/Leap_year
if [ "$leap_ck400" = "$(($yyyy/400))" ]; then
leap="yes" # leap year
elif [ "$leap_ck100" = "$(($yyyy/100))" ]; then
not_a_leap=1 # not a leap year
elif [ "$leap_ck4" = "$(($yyyy/4))" ]; then
leap="yes" # leap year
else
not_a_leap=1 # not a leap year
fi
m_val=`month_table $mmm $leap`
# time the search, start the timer
timer_start=`date +%s`
# now to search out the answer
i=0
for yyyy in $(seq $3 $4 )
do
echo "Checking years: $yyyy"
if [ $yyyy -gt 1699 -a $yyyy -lt 1753 ]; then
# years from 1700 to 1752 use the old calendar
cu_pval="17a"
else
cu_pval=`echo $yyyy | cut -c1-2`
fi
cu_val=`century_table $cu_pval`
cl_val=`echo $yyyy | cut -c3-4 | bc`
val=`echo "($cu_val + $cl_val + $(($cl_val/4)) + $m_val + $d_val)%7" | bc`
if [ $val -eq 6 ];then
resulting_years[i]=`echo "$yyyy"`
let i+=1
fi
tput cuu1
done
# stop the timer
timer_end=`date +%s`
# clean output
echo "$(($timer_end - $timer_start )) seconds to complete the search [$3 to ${4}]."
echo "The date falls on the sabbath in these years:"
for m in $( seq 0 ${#resulting_years[@]} )
do
echo -e "${resulting_years[m]\n}"
done | pr -6 -t -w 80 -l 50
# This uses the modified Doomsday method as proposed here:
# http://en.wikipedia.org/wiki/Calculating_the_day_of_the_week
# (the Doomsday method was first proposed by John Horton Conway).
# The next update should encorporate Sohael S. Babwani's
# more efficient formulation.
show_help()
{
base=${0##*/}
cat << EOF >&2
'${base}' finds all years in which a date falls on the sabbath
Usage:
$base [month] [date] [start year] [end year]
[month] is the month (January, February, etc.)
[day] is the date (1 through 31)
[start year] can be any year after 1000
[end year] can be any year before 2599
Options:
-help this help
-license shows the license
EOF
}
show_license()
{
base=${0##*/}
echo "
This work (\"${base}\") is licensed by O. Ganesh under the
Creative Commons Attribution-NonCommercial-ShareAlike 2.5 License
http://creativecommons.org/licenses/by-nc-sa/2.5/legalcode
any derivative works based on this work shall reference the author
"
}
month_table()
{
# month table
mmm=$1; leap=$2;
case $mmm in
Jan )
if [ $leap ]; then m_val=6;
else m_val=0;fi;;
Feb )
if [ $leap ]; then m_val=2;
else m_val=3;fi;;
Mar ) m_val=3;;
Apr ) m_val=6;;
May ) m_val=1;;
Jun ) m_val=4;;
Jul ) m_val=6;;
Aug ) m_val=2;;
Sep ) m_val=5;;
Oct ) m_val=0;;
Nov ) m_val=3;;
Dec ) m_val=5;;
esac
echo $m_val
}
century_table()
{
cu=$1
case $cu in
10 ) cu_val=1;;
11 ) cu_val=0;;
12 ) cu_val=6;;
13 ) cu_val=5;;
14 ) cu_val=4;;
15 ) cu_val=3;;
16 ) cu_val=2;;
17a ) cu_val=1;; # dates from 1700 to 1752
17 ) cu_val=4;; # dates after 1752
18 ) cu_val=2;;
19 ) cu_val=0;;
20 ) cu_val=6;;
21 ) cu_val=4;;
22 ) cu_val=2;;
23 ) cu_val=0;;
24 ) cu_val=6;;
25 ) cu_val=4;;
esac
echo $cu_val
}
need_help()
{
echo "invalid input"
base=${0##*/};echo "try '$base -help' for help"
exit 1
}
case $# in
1)
# possible option call
case $1 in
help | h | -help | -h | --help | --h )
show_help; exit 0;;
lic | license | -lic | -license | --lic | --license )
show_license; exit 0;;
*) need_help;;
esac;;
4)
# sanity checking
if [ $3 -lt 1000 -o $4 -gt 2599 -o $3 -gt $4 ];then
need_help
fi;;
*)
# erroneous input
need_help
esac
# warning about calendar changes
if [ $3 -lt 1753 ];then
echo "dates before 1753 may not be correct due to calendar changes"
fi
# find epoch seconds with tclsh
bdate="$1 ${2}"
epoch_sec=`echo "puts [clock scan { $bdate }]" | tclsh`
if [ ! $epoch_sec ]; then
echo "invalid date"
show_help
exit 1
fi
# nice date check
birthdate=`date -r $epoch_sec `
birthday=`echo $birthdate | cut -d" " -f1`
case $birthday in
Sun ) bday="Sunday";;
Mon ) bday="Monday";;
Tue ) bday="Tuesday";;
Wed ) bday="Wednesday";;
Thu ) bday="Thursday";;
Fri ) bday="Friday";;
Sat ) bday="Saturday";;
esac
echo "This year that date is a ${bday} (`echo $birthdate | cut -d" " -f2-3`, 2007)."
# (cu + cl + round(cl/4) + m + d)%7 = 6
# and the answer of 7 * 6 (wrongly) is 42
# coincidentally this is the answer to life, the universe, and everything
# (this is a joke, don't email me about it)
d_val=`echo $birthdate | cut -d" " -f3`
mmm=`echo $birthdate | cut -d" " -f2`
yyyy=`echo $birthdate | cut -d" " -f6`
# prep for leap year check
leap_ck400=`echo "scale=2;$yyyy / 400" | bc | sed 's/\.00//'`
leap_ck100=`echo "scale=2;$yyyy / 100" | bc | sed 's/\.00//'`
leap_ck4=`echo "scale=2;$yyyy / 4" | bc | sed 's/\.00//'`
# leap year check
# good help from wikipedia to clarify the issue
# http://en.wikipedia.org/wiki/Leap_year
if [ "$leap_ck400" = "$(($yyyy/400))" ]; then
leap="yes" # leap year
elif [ "$leap_ck100" = "$(($yyyy/100))" ]; then
not_a_leap=1 # not a leap year
elif [ "$leap_ck4" = "$(($yyyy/4))" ]; then
leap="yes" # leap year
else
not_a_leap=1 # not a leap year
fi
m_val=`month_table $mmm $leap`
# time the search, start the timer
timer_start=`date +%s`
# now to search out the answer
i=0
for yyyy in $(seq $3 $4 )
do
echo "Checking years: $yyyy"
if [ $yyyy -gt 1699 -a $yyyy -lt 1753 ]; then
# years from 1700 to 1752 use the old calendar
cu_pval="17a"
else
cu_pval=`echo $yyyy | cut -c1-2`
fi
cu_val=`century_table $cu_pval`
cl_val=`echo $yyyy | cut -c3-4 | bc`
val=`echo "($cu_val + $cl_val + $(($cl_val/4)) + $m_val + $d_val)%7" | bc`
if [ $val -eq 6 ];then
resulting_years[i]=`echo "$yyyy"`
let i+=1
fi
tput cuu1
done
# stop the timer
timer_end=`date +%s`
# clean output
echo "$(($timer_end - $timer_start )) seconds to complete the search [$3 to ${4}]."
echo "The date falls on the sabbath in these years:"
for m in $( seq 0 ${#resulting_years[@]} )
do
echo -e "${resulting_years[m]\n}"
done | pr -6 -t -w 80 -l 50
The calendar gets odd around 1752 (Gregorian/Julian calendar switch), so before that date the computed day will be off. Don't know my this is? C'mon, most 'good' Adventists know! I'll make the fix at a later time. If you want to check the results, use the cal command.
edit: new version is up, it fixes most calendar issues
No comments:
Post a Comment