#!/usr/bin/perl # Birthday Program # Adam J. Griff, Ph.D. # GriffMonster, LLC # email: computer at GriffMonster dot com # Description: Reminders of yearly events - birthdays, anniversaries, etc. ################ Copyright ################ # This program is Copyright 1991-2007 by Adam J. Griff, Ph.D. (GriffMonster, LLC) # This program is free software; you can redistribute it and/or # modify it under the terms of the Perl Artistic License or the # GNU General Public License as published by the Free Software # Foundation; either version 2 of the License, or (at your option) any # later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # If you do not have a copy of the GNU General Public License write to # the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, # MA 02139, USA. ################ Module Preamble ################ # 2006-12-11 V2.6.3 # Updated directions for Windows XP # 7/19/2004 V2.6.2 # Added info on creating MSWindows bat script # 4/27/2003 V2.6.1 # Changed email address and name # 6/18/98 V2.6 # Added the -i switch to handle international output - # Fixed the parser to accept white-space around month and day on input # 3/19/97 V2.5 # Fixed a bug with year calculation, when event is in the # following year. # Added info on writing a cron job script to email info daily # 6/15/96 V2.4 # as recommened by the geekboy includes are now allowed # also added date sorting on output to make the include work pretty # 1/29/96 V2.3 (translated from Pascal V2.2 1/1/91) # This program read a birthday file and notifies of upcoming # birthdays and events sub help { local($directions)= " Birthday Program \n". " V2.6.3 2006-12-11\n". " by\n". " Adam J. Griff, Ph.D.\n". " GriffMonster, LLC\n". " (email: computer at GriffMonster dot com)\n". "\n\n\n\n". " PAGE 1\n". "This program tells you when it is a person's birthday or\n". "any other special event.\n\n". "The data file .birthday is by default expected in your\n". "home directory.\n". "The file must be in the format:\n". " Date of birth (year,month,day)\n". " Optional spaces can be placed before and after the month and day.\n". " Name (first, at least one space, last, then enter)\n\n". " Example: for Apr 01 1963 type\n". " 19630401Adam Griff\n\n". " or for 1980 Dec 5th type\n". " 19801205 Adam Griff\n\n". "If this is not a birthday enter the '/' symbol after the date\n". "followed by text and terminated by '/'.\n". "New include feature: to add include file place the file name after \n". "the date\n enclosed in quotes.\n". "Lines without dates starting with the /'s are comment lines.\n\n". "Suppress the year count by entering the year as 19XX.\n". " Example: 19XX1031 /It is Halloween!/\n\n". " Example: 19XX0000 \"\/home\/foo\/bar\/birthday.extra\"". "\n\n\n\n\n". " PAGE 2\n\n". "Features to be used on the command line:\n". " '-a' gives all dates and ages for current year's event.\n". " '-d' gives all dates and current ages.\n". " '-n' stops the date and time from printing.\n". " '-h' for this help information.\n". " '#' is the number of days in advance you \n". " will be warned of a birthday or an event.\n". " '-f ... ' change the default\n". " input filename to a list of file names. Must be last flag\n". " '-i' causes the dates to print in international format\n". "The output is sorted in date order when -a or -d is used and in\n". "the order of closest to today first otherwise.\n". "The program is designed to warn of an event/birthday a week \n". "in advance and the day itself.\n\n". "$0 with no commands gives this help document.\n". "$0 must be followed by something to run.\n". " Ex: $0 -x (run without changing defaults)\n". " $0 7 (My recommendation. This will print \n". "time and date \n". " and warn you everyday for a week.)\n\n". "ENJOY and send me any comments or suggestions you may have.\n". "\n\n\n\n\n". " PAGE 3\n\n". "Below is a shell script that you can save and put into a cron job.\n". "This script will send you email if any events occurs in the next week.\n". "\n". "#!/bin/csh -f \n". "set f = \"/tmp/.birthday.\$USER\" \n". "rm -f \$f \n". "\$HOME/bin/birthday -n 7 > \$f \n". "if !(-z \$f) mail \$USER < \$f \n". "rm -f \$f \n". "\nThe following is a MSWindows .bat script you can put in your startup directory\n". "Assumes location of birthday program, data, and perl\n\n". "@echo off\n". "C:\n". "chdir C:/\Data/\Birthday\n". "c:\/cygwin/\bin/\perl birthday 7 -f C:/\Data/\Birthday/\birthday.doc\n". "pause\n"; print $directions; } # return 1 if satisfied and 0 if not sub testentry { local($day_count,$fday_count,$year,$fyear,$yearlength, $daysinadvance, $show_all,$current_count)=@_; local($retcode,$count); if ($show_all) { if ($current_count && ($fday_count > $day_count)) { $count=$year-$fyear-1; } else { $count=$year-$fyear; } $retcode=1; } else { if ($day_count == $fday_count || ($day_count+7)== $fday_count || (($day_count <= $fday_count) && (($day_count+$daysinadvance) >= $fday_count)) || (($day_count >= $fday_count) && ($day_count+$daysinadvance > $yearlength) && (($day_count+$daysinadvance - $yearlength) >= $fday_count))) { if ($fday_count < $day_count ) { $count=$year-$fyear+1; } else { $count=$year-$fyear; } $retcode=1; } else { $retcode=0; } } return ($retcode,$count); } # sorry about the mess, originally check_dates was only called once # return $day_count sub check_dates { local($infile,$mday,$mon,$year,$daysinadvance, $show_all,$current_count)=@_; local($line_num)=0; local(*DATA); if (open(DATA,"<$infile")) { local(@mon_offset,$day_count,$fday_count,$leapyear,$yearlength); # leapyear is years divisable by 4 but not 100 unless by 400 if (($year % 400) == 0 || (($year % 4) == 0 && ($year % 100) != 0)) { $leapyear=1; } else { $leapyear=0; } if ($leapyear) { $yearlength=366; @mon_offset=(0,31,60,91,121,152,182,213,244,274,305,335); } else { $yearlength=365; @mon_offset=(0,31,59,90,120,151,181,212,243,273,304,334); } if ($daysinadvance > $yearlength) { $daysinadvance = $yearlength; } $day_count=@mon_offset[$mon-1]+$mday; while ($item=) { $line_num++; chop $item; if ($item =~ /^\//) { # comment line so skip it } elsif ($item =~ /^(.{4})\s*?(\d{2})\s*?(\d{2})(.*?)(\r?)$/) { # includes the fix for reading dos files local($fyear,$fmon,$fmday,$message)=($1,$2,$3,$4); local($show_year)=0; local($retcode,$count); # check if it is an include file if ($message =~ /\"(.*)\"/) { $retcode = 0; local($temp); if (! defined $filenames{"$1"}) { $filenames{"$1"} = 1; &check_dates($1,$mday,$mon,$year,$daysinadvance, $show_all,$current_count); } } else { # check if the year is XX'ed out if ($fyear =~ /\d{4}/) { $show_year = 1; } else { $fyear = $year;} $fday_count=@mon_offset[$fmon-1]+$fmday; ($retcode,$count) = &testentry($day_count,$fday_count, $year,$fyear,$yearlength, $daysinadvance, $show_all,$current_count); if ($retcode) { $unique_count++; # fix the day number for proper sorting. if (! $show_all && $fday_count < $day_count) { $fday_count += 366; } $OUT_DATA{"$fday_count\0$unique_count"}= join("\0",$count,$show_year,$fmon,$fmday,$message); } } } else { print "Error in $infile input file format line $line_num :\n$item\n"; } } } else { print "Could not open the \"$infile\" input file.\n". "Help Usage: $0 -h\n"; } return $day_count; } # sort in date order, remove the unique id sub number_order { local($a_num,$b_num,$count); ($a_num,$count) = split("\0",$a); ($b_num,$count) = split("\0",$b); $a_num <=> $b_num;} # Main Body local(@infile); local($daysinadvance,$show_datentime,$show_all,$international_date)=(0,1,0,0); local($current_count)=0; # number as of today not at the time of event local($file_count)=0; $infile[0]="$ENV{'HOME'}/.birthday"; while ($ARGV = shift) { if ("$ARGV" eq "-a") { $show_all=1;} elsif ("$ARGV" eq "-d") { $show_all=1; $current_count=1;} elsif ("$ARGV" eq "-f") { while ($ARGV = shift) { $infile[$file_count] = $ARGV; $file_count++; } } elsif ("$ARGV" eq "-n") { $show_datentime=0;} elsif ("$ARGV" eq "-i") { $international_date=1;} elsif ("$ARGV" eq "-h") { &help(); exit;} elsif ("$ARGV" =~ /^\d+$/ ) { $daysinadvance=$ARGV;} } local($date_string); $date_string=localtime(time); if ($show_datentime) { print "$date_string\n"; } local($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time); $mon++; # make the month be from 1-12 $date_string =~ /([\d]{4})$/; $year=$1; # get the year as a 4 digit number instead of 2(bad) # some formats for check_dates output format TOP = Name or Message Count Date . format OTHER = ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< @<<<<<<< @<-@< $message, $agestr,$fmon,$fmday ~ ^<<<<<<<< $message . format OTHER_I = ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< @<<<<<<< @<-@< $message, $agestr,$fmday,$fmon ~ ^<<<<<<<< $message . format BIRTHDAY = @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< Birthday @<<<<<<< @<-@< $message, $agestr,$fmon,$fmday . format BIRTHDAY_I = @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< Birthday @<<<<<<< @<-@< $message, $agestr,$fmday,$fmon . $^ = 'TOP'; #set top-of-form format. $= = 100000; # set page length to very big to stop multiple headers $unique_count=0; foreach $i (0..$#infile) { if (! defined $filenames{"$infile[$i]"}) { $filenames{"$infile[$i]"} = 1; &check_dates($infile[$i],$mday,$mon,$year,$daysinadvance, $show_all,$current_count); } } foreach $item (sort number_order keys %OUT_DATA) { local($agestr); local($count,$show_year,$fmon,$fmday,$message) = split("\0",$OUT_DATA{$item}); # check if it is a birthday if ( $message =~ /^.*?\/(.*?)\/$/) { $message = $1; if ($show_year) { $agestr="$count years ";} if ($international_date) { $~ = 'OTHER_I'; } else { $~ = 'OTHER'; } write; } else { if ($show_year) { $agestr="Age $count ";} else {$agestr="";} if ($international_date) { $~ = 'BIRTHDAY_I'; } else { $~ = 'BIRTHDAY'; } write; } }