Wrong Dates in iCal Birthday Calendar
To keep track of people’s birthdays, I use Mac OS X’s1 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:2

The Address Book birthday field has the misfeature that it forces a year to be specified.3 What a rude thing for Address Book to be asking! Anyway, I’d arbitrarily picked year 14 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 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()
-
10.5.1, MacBook Pro Core 2 Duo ↩
-
Names have been changed to protect the innocent. ↩
-
There’s also an implementation flaw; I have my date format set to
YYYY-MM-DD, and when I try to enter a year in the field, whether or not pressing a number on the keyboard will actually result in a digit appearing in the input field appears to be random. It also behaves very weirdly if there are four digits in the field already and I press another digit. I wish I could get a video of all this, but it’s not quite worth the effort of taking a screencast and a video of my fingers on the keyboard and then splice them together… ↩ -
Anno Domini, not Anno Antidomini ↩