Birthday Coming Up In iCal "Birthdays".

This is certainly not the most elegant way to do this, but it gives me a heads up on my relative’s upcoming birthdays. Comments very welcome. Given my “handle” it will not surprise the reader that I live in Atlantic Canada. If you want to use this script in other time zones, edit the appropriate section. I could have automated that, but don’t have any way to test it.

-- This script calculates the days remaining before the next four birthdays in an iCal calendar named "Birthdays", where each birthday has been entered as an all day event (important).
-- I use Growl to display the result but a "display dialog" is the default.
-- Start of Script --
-- Get today's date with time set to midnight (iCal will have the time set to whenever you entered the data). Later days to go subtractions must have a time of day in common or differences can be off by one day.
set today to current date
set time of today to 0 -- midnight
--  Get the Birthday List "bDays" {name, date, name, date, ...}, then correct for GMT, then subtract today after correcting year to get { name, daysToGo, name, daysToGo, ...}
tell application "iCal"
	close window 1
	set bdCals to every calendar whose title contains "Birthdays"
	set bCal to item 1 of bdCals -- only one
	set toGo to {}
	-- Collect the date/name list as bCal
	set bCount to count of events of bCal
	repeat with n from 1 to bCount
		-- Start date is the birthday for an all day event. 
		-- Summary should be the person's name.
		-- iCal stores times of events in GMT even though it presents them in local time. They must be shifted back to a midnight base before days to go are calculated.
		set thisDay to {summary of event n of bCal, start date of event n of bCal}
		set thisTime to item 2 of thisDay
		-- The script is "hard-wired" for Atlantic Daylight Savings time, three hours before GMT, so midnight will appear as 9PM or 3600 * 21 seconds, so 3 hours, in seconds, must be added for DST, or 4 for standard time. Adjust this for your time zone.
		if time of thisTime = 75600 then -- 9:00 PM if ADST
			set time of thisTime to (time of thisTime) + 10800 -- add three hours in seconds
		else -- 8:00 PM if AST
			set time of thisTime to (time of thisTime) + 14400 -- add four hours in seconds.
		end if
		-- adjust the calendar year of the birthday ahead of now. iCal will only have one entry and computes others on the fly, so you have to move the day forward.
		repeat until (thisTime - today) > 0
			set year of thisTime to (year of thisTime) + 1
		end repeat
		set daysLeft to ((thisTime - today) / days) as integer
		set item 2 of thisDay to daysLeft
		set toGo to toGo & thisDay
	end repeat
	quit
end tell

set msgs to nearest_4(toGo, bCount)
set msg_C to " days until "
set AllBDNotes to ""
set r to return
set rr to r & r
set sp to space
repeat with mm from 1 to 4
	set msg_A to (item 1 of (item mm of msgs)) as text
	set msg_B to (item 2 of (item mm of msgs))
	-- add possessives for names: soAndso's Birthday
	if last character of msg_B = "s" then
		set msg_B to msg_B & "'"
	else
		set msg_B to msg_B & "'s"
	end if
	set BDNote to msg_A & msg_C & msg_B
	set AllBDNotes to AllBDNotes & BDNote & rr
end repeat

--Growl_It("Birthdays Coming Soon", AllBDNotes)
-- For those who like Growl notifications, comment out the display dialog line below and run "Growl_It" instead.

display dialog "Birthdays Coming Soon" & rr & AllBDNotes buttons {"Got It"} default button 1

-- Handlers --
on Growl_It(gTitle, gMessage)
	tell application "GrowlHelperApp"
		notify with name "Next4BD" title gTitle description (return & gMessage) application name "Birthdays" with sticky
	end tell
end Growl_It
----

on nearest_4(values_list, entries)
	set theNearest4 to {{}, {}, {}, {}}
	set scratchList to values_list
	repeat with m from 1 to 4
		set the low_amount to 0
		set itemNum to 0
		-- sort by disances to go ignoring names
		repeat with i from 2 to entries by 2
			set this_item to item i of the scratchList
			if the low_amount = 0 then
				set the low_amount to this_item
				set itemNum to i
			else if this_item < low_amount then
				set the low_amount to item i of scratchList
				set itemNum to i
			end if
		end repeat
		-- add new entry to result and get the name to go with it
		set item m of theNearest4 to {low_amount, item (itemNum - 1) of scratchList}
		set newList to {}
		if itemNum = 2 then
			repeat with jj from 3 to count of scratchList
				newList = newList & item jj of scratchList
			end repeat
		else
			repeat with kk from 1 to itemNum - 2
				set newList to newList & item kk of scratchList
			end repeat
			repeat with nn from itemNum + 1 to count of scratchList
				set newList to newList & item nn of scratchList
			end repeat
		end if
		set scratchList to newList
	end repeat
	return theNearest4
end nearest_4

Please try OmniGrowl on version tracker or macupdate. thanks.

Pretty cool Adam. I liked it so much I made the length of the list adjustable and added the age the person will be on their birthday to it. I used my own code though because I already had some that was similar so I just tweaked it to present the data like yours. Hopefully it works with all date formats from around the world… I think it will anyway. Let me know if anyone sees problems.

Note: I don’t use growl, so there’s no growl code in this.

(* This script calculates the days remaining before the birthdays listed in your birthday iCal calendar. A dialog box presents the data to you. Set your parameters in the first 2 lines below.*)

-- set your parameters here
set calName to "Birthdays" --> the name of your birthday calendar in iCal
set numBdays to 10 --> the number of upcoming birthdays you want presented to you

-- make a list of data for everyone on your birthday calendar
-- bdayList consists of {person, date born, date of their next birthday, age on birday, days until birthday}
set today to date (date string of (current date) & " 12:00:00 AM")
set bdayList to {}
tell application "iCal"
	set bdayCal to calendar calName
	set eventsCount to count of events of bdayCal
	repeat with i from 1 to eventsCount
		-- get the name and the date they were born from iCal
		set bdayPerson to summary of event i of bdayCal
		set bornDate to start date of event i of bdayCal
		
		-- calculate date of next birthday
		copy bornDate to next_bday
		tell next_bday to set its year to (year of today)
		if next_bday < today then tell next_bday to set its year to ((year of today) + 1)
		
		-- calculate age on next birthday and days to it
		set age_on_bday to ((year of next_bday) - (year of bornDate))
		set days_to_bday to ((next_bday - today) / (1 * days)) as integer
		
		-- compile the list of all their data
		set personData to {bdayPerson, bornDate, next_bday, age_on_bday, days_to_bday}
		set end of bdayList to personData
	end repeat
end tell

-- sort the list by the number of days until their birthday
set sortedbdayList to my sortListofLists(bdayList, 5)

-- make the dialog message
set msg_C to " days until "
set msg_D to " turns "
set AllBDNotes to ""
set r to return
set rr to r & r
set sp to space
if eventsCount < numBdays then set numBdays to eventsCount --> error check to make numBdays sure isn't too large
repeat with mm from 1 to numBdays
	set msg_A to (item 5 of (item mm of sortedbdayList)) as text
	
	-- fix the person's name to display properly
	set name_words to words of (item 1 of (item mm of sortedbdayList))
	if (item -2 of name_words) ends with "'s" or "'s" then
		set fixed_name to items 1 thru -3 of (item -2 of name_words) as string
		tell name_words to set item -2 to fixed_name
	end if
	set {TIDs, AppleScript's text item delimiters} to {AppleScript's text item delimiters, " "}
	set msg_B to (items 1 thru -2 of name_words) as string
	set AppleScript's text item delimiters to TIDs
	
	set msg_E to (item 4 of (item mm of sortedbdayList)) as text
	set BDNote to msg_A & msg_C & msg_B & msg_D & msg_E
	set AllBDNotes to AllBDNotes & BDNote & rr
end repeat

display dialog "----Birthdays Coming Soon----" & rr & AllBDNotes buttons {"Got It"} default button 1

(*===================== SUBROUTINES ======================*)
on sortListofLists(array, sortItemNum) --> this is a slight modification of the bublesort routine
	repeat with i from length of array to 2 by -1 -- go backwards
		repeat with j from 1 to i - 1 -- go forwards
			if (item sortItemNum of (item j of array)) > (item sortItemNum of (item (j + 1) of array)) then
				tell array to set {item j, item (j + 1)} to {item (j + 1), item j}
			end if
		end repeat
	end repeat
	return array
end sortListofLists