Sunday, September 9, 2007

wow. Someone already emailed me about this. Let me take a minute to let everyone know that sda-unix-linux does not refer to removeable media and *nix, but rather to Seventh-day Adventists who use *nix (and other platforms/environments) to solve problems*. Basically this blog is a little place to post some code.

* sometimes the problems are arguably mundane, somtimes they are useful (sometimes they are silly)

When will my birthday be on sabbath?

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


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

where to begin...

maybe here, maybe not.

This is an odd blog.
The idea is to have a place where bits of code which relate to Seventh-day Adventist stuffs could be posted. Will it work? Who knows (well, God knows).

Why do this? Well, I frequently find the need to write a bit of code. In fact, anytime I have to do anything which is repetetive, I tend to start thinking about how the process could be automated.

I tend to use a lot of *nix now-a-days, and consequently will code in several different languages/scrips, depending on how I feel. However, I do work with other platforms, and try not to be constrained to using just one; so I dabble in 'Windows necessary' languages and OS X specific stuffs as well. I feel that any person who really wants to tackle a problem will look for the best tool possible, and that tool may be on a different environment, be it Linux, Unix, Windows, OS X, TI 89, MATLAB, etc.

Anyways, this isn't supposed to be a tirade; it's supposed to be useful.