Wrong Dates in iCal Birthday Calendar
To keep track of people's birthdays, I use Mac OS X's Birthday Calendar feature of Address Book/iCal. I was going through my calendar the other day, and I noticed that a birthday which I knew was sometime in January wasn't showing up. It was on the corresponding Address Book contact, though. I deleted the birthday from this contact and reentered it, which fixed that entry, but on the suspicion that more birthdays might be missing, I flipped through my calendar and found:
The Address Book birthday field has the misfeature that it forces a year to be specified. What a rude thing for Address Book to be asking! Anyway, I'd arbitrarily picked year 1 for the year for any contacts whose birth years I didn't know. Maybe, I thought, the Gregorian reform was throwing things off. However, changing the year to 1900 didn't help matters, and in fact made them worse:
Turning the birthday calendar off (which wipes out iCal's backing store for the calendar) and on didn't help matters. A web search turned up some other people having the same problem, but the only useful solution they came up with was deleting and recreating entire contacts by hand.
I wanted to see if the raw data was wrong in Address Book's database. Address Book uses Core Data in a way that makes the database difficult to work with at the SQLite command-line level, so instead I hacked /Developer/Examples/Python/PyObjC/AddressBook/Scripts/exportBook.py to emit the birthday field by adding ('Birthday', AddressBook.kABBirthdayProperty) to FIELD_NAMES and the following to encodeField:
elif isinstance(value, AppKit.NSCalendarDate):
return value.descriptionWithCalendarFormat_("%Y-%m-%d")
It turns out that a number of entries had negative years, e.g. -1900-03-23 instead of 1900-03-23. I'm not sure how this happened, but here's a script (which you can download) to fix it:
#!/usr/bin/python
"""
Fix negative birthday years in Address Book.
This work is hereby released into the Public Domain.
"""
import AddressBook
import AppKit
def personName(person):
return "%s %s" % (
person.valueForProperty_(AddressBook.kABFirstNameProperty),
person.valueForProperty_(AddressBook.kABLastNameProperty)
)
def formatDate(date):
return date.descriptionWithCalendarFormat_("%Y-%m-%d")
def fixBirthday(birthday):
year = int(birthday.descriptionWithCalendarFormat_("%Y"))
if year < 0:
return birthday.dateByAddingYears_months_days_hours_minutes_seconds_(
-year * 2, 0, 0, 0, 0, 0)
else:
return None
def fixPersonBirthday(person):
birthdayProp = AddressBook.kABBirthdayProperty
birthday = person.valueForProperty_(birthdayProp)
if birthday == None: return
fixedBirthday = fixBirthday(birthday)
if fixedBirthday != None:
print "Fixing up %s: %s -> %s" % (
personName(person),
formatDate(birthday),
formatDate(fixedBirthday)
)
person.setValue_forProperty_(fixedBirthday, birthdayProp)
book = AddressBook.ABAddressBook.sharedAddressBook()
for person in book.people():
fixPersonBirthday(person)
book.save()