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
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()