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