#!/local/bin/perl # # signature start #Name: weekday #Version: 1.0 #Project: Alice #Author: b_rostad@yahoo.com #Date: 09.08.1999 #Contents: This script emobodies a method proposed by British mathematician # Charles Lutwidge Dodgson (27.01.1832-14.01.1898) in a letter to # Nature magazine in 1887. Dodgson is best known under the alias # Lewis Carroll which he used when writing "Alice in wonderland". # # The method for calculating the day of week for a given date is # split in four operations, each operation treats one part (or item # as Carroll labels it): century, the 2-digit year, the month and # the day of the month. Each part is added to the previous part to # obtain a total. If a part or the total exceeds 7 replace it by the # modulus value MOD(part,7), i.e. keep the remainder after dividing # by 7. Carroll labels the result of the modulus operation as # "surplus". Some of the recipe is quoted directly from Carroll. # # 1) CENTURY_ITEM: If using Julian calendar (up to 1700 in Denmark, # Norway and Germany; to 1752 in the british empire) "subract # from 18". If using Gregorian calendar "divide by 4, take # overplus from 3, multiply remainder by 2." # # 2) YEAR_ITEM: "Add together the number of dozens, the overplus # and the number of 4's in the overplus". # # 3) MONTH_ITEM: "If it begins or ends with a vowel, subtract the # number denoting its place in the year from 10. This, plus its" # number of days, gives the item for the following month." # # 4) DAY_ITEM: "is the day of the month". # # "The total, thus reached, must be corrected, by deducting 1 (first # adding 7, if the total be 0), if the date be January or February # in a Leap Year." From the final value, in the range 1-7, the day # of week is given by: # # 1->Monday, 2->Tuesday, 3->Wednesday, 4->Thursday, 5->Friday, # 6->Saturday, 7->Sunday # # This script will dump the result or a suitable error message to # STDOUT. # #Limitation: Will only work for Gregorian (new) calendar dates, but it is # fairly simple to add support for Julian calendar, but then it # is necessary to add a control parameter to tell which one to # use! # # signature end # Variable for accumulating the total value $total = 0; # The user specified date parsed from @ARGV and a parameter # $is_leap_year which is '1' for leap years and 0 otherwise. my($day, $month, $year, $is_leap_year) = parse_input(@ARGV); # Add century and year item # century = substr($year,0,2); # 2digit year = substr($year,2,2); &add_century_item(\$total,substr($year,0,2)); &add_year_item(\$total,substr($year,2,2)); # Add month and day items &add_month_item(\$total,$month); &add_day_item(\$total,$day); # dump a suitable result print "$day.$month.$year is a ", &get_weekday(\$total,$year,$month,$is_leap_year),"\n"; # # -- METHOD SUBROUTINES # # CENTURY_ITEM: Divide the century by 4, take overplus from 3, multiply # remainder by 2. sub add_century_item { my($tot, $century) = @_; $$tot = $$tot + 2*(3 - ($century%4)); } # YEAR_ITEM: Add together the number of dozens, the overplus and the number # of 4's in the overplus in the two digits representing the year. sub add_year_item { my($tot, $year) = @_; my($remainder) = $year%12; $$tot = ($$tot + (int $year/12) + $remainder + (int $remainder/4)) % 7; } # MONTH_ITEM: If it begins or ends with a vowel, subtract the number # denoting its place in the year from 10. This, plus its number of days, # gives the item for the following month. sub add_month_item { my($tot, $month_num) = @_; # Precalculated MONTH_ITEM values my(%month_item) = (1 => '0', 2 => '3', 3 => '3', 4 => '6', 5 => '1', 6 => '4', 7 => '6', 8 => '2', 9 => '5', 10 => '0', 11 => '3', 12 => '12' ); if (exists $month_item{$month_num}) { $$tot = ($$tot + $month_item{$month_num}) % 7; } else { warn "System-error: Illegal month number '$month_num'!"; } } # DAY_ITEM: Is the day of the month. sub add_day_item { my($tot, $day) = @_; $$tot = ($$tot + $day) % 7; } sub get_weekday { my($tot, $year, $month, $is_leap_year) = @_; if ($month < 3 && $is_leap_year == 1) { # do this only for January and February in leap years $$tot = $$tot -1; } # add 7 if total so far is below 1. $$tot = ($$tot < 1 ? $$tot+7 : $$tot); my(%weekday) = (1 => 'Monday', 2 => 'Tuesday', 3 => 'Wednesday', 4 => 'Thursday', 5 => 'Friday', 6 => 'Saturday', 7 => 'Sunday'); return $weekday{$$tot}; } # # --- HELPERS # sub parse_input { my(@input) = @_; if ($input[0] =~ m|^-h$|) { print_man_and_exit(); } elsif (scalar @input != 3) { print_usage_and_exit(); } else { my($day, $month, $year) = @ARGV; if (0 < $day && 0 < $month && $month < 13 && length $year == 4) { my($is_leap) = is_leap_year($year); if ($day <= days_in_month($month,$is_leap)) { return ($day, $month, $year, $is_leap); } else { print_error("Month $month in $year hasn't got $day days"); } } else { print_date_spec_error_and_exit(); } } } sub is_leap_year { my($year) = @_; my($is_leap_year) = 0; # it's a leap year if divisible by 4, but not if it's # divisible by 100 unless it's also divisible by 400. if ($year%4 == 0) { if ($year%100 == 0) { if ($year%400==0) { $is_leap_year = 1; } } else { $is_leap_year = 1; } } return $is_leap_year; } sub days_in_month { my($month, $is_leap_year) = @_; my(%month) = ('1' => '31', '2' => '28', '3' => '31', '4' => '30', '5' => '31', '6' => '30', '7' => '31', '8' => '31', '9' => '30', '10' => '31', '11' => '30', '12' => '31'); if ($is_leap_year) { $month{'2'}++; } return $month{$month}; } sub print_error { my($msg) = @_; print "\nError: $msg!\n"; print_usage_and_exit(); } sub print_date_spec_error_and_exit { print <<EOF; You must provide a valid date in the following format: <day> <month> <year> where <day> is the day of month, <month> is a number (1 for January through to 12 for December), <year> is a four-digit year (thus use 1999 instead of 99) EOF exit(); } sub print_usage_and_exit { print "\nUsage: \n"; print " perl weekday <day> <month-number> <four-digit-year>\n"; print " perl weekday -h\n\n"; exit(0); } sub print_man_and_exit { print <<EOT; weekday NAME weekday - will compute the weekday for a given Gregorian date, where date is given as day, month number and a 4 digit year. SYNOPSIS perl weekday [ -h ] day month year DESCRIPTION The weekday utility will return the day of the week for a given date, specified on the commandline. It will only return days in the Gregorian calendar, thus asking for weekdays before AD 1700 makes no sense. The algorithm this utility is based on was proposed by British mathematician Charles Lutwidge Dodgson (27.01.1832-14.01.1898) in a letter to Nature magazine in 1887. Dodgson is best known under the alias Lewis Carroll which he used when writing "Alice in wonderland". OPTIONS The following options are supported: -h Displays this man page OPERANDS The following operands are supported (if -h option is not used these operands are required): day The day of the month (1-[28|29|30|31]) month The month number (January=1, ..., December=12) year The four digit year. CONTACT b_rostad\@yahoo.com Last change: 9 August 1999 EOT exit(0); }