Posts Tagged ‘iCal’

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

Address Book says Mar 23, iCal says Mar 21

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:

Address Book says Mar 23, iCal says June 23

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:

  1. #!/usr/bin/python
  2. """
  3. Fix negative birthday years in Address Book.
  4. This work is hereby released into the Public Domain.
  5. """
  6. import AddressBook
  7. import AppKit
  8.  
  9. def personName(person):
  10. return "%s %s" % (
  11. person.valueForProperty_(AddressBook.kABFirstNameProperty),
  12. person.valueForProperty_(AddressBook.kABLastNameProperty)
  13. )
  14.  
  15. def formatDate(date):
  16. return date.descriptionWithCalendarFormat_("%Y-%m-%d")
  17.  
  18. def fixBirthday(birthday):
  19. year = int(birthday.descriptionWithCalendarFormat_("%Y"))
  20. if year < 0:
  21. return birthday.dateByAddingYears_months_days_hours_minutes_seconds_(
  22. -year * 2, 0, 0, 0, 0, 0)
  23. else:
  24. return None
  25.  
  26. def fixPersonBirthday(person):
  27. birthdayProp = AddressBook.kABBirthdayProperty
  28.  
  29. birthday = person.valueForProperty_(birthdayProp)
  30. if birthday == None: return
  31.  
  32. fixedBirthday = fixBirthday(birthday)
  33. if fixedBirthday != None:
  34. print "Fixing up %s: %s -> %s" % (
  35. personName(person),
  36. formatDate(birthday),
  37. formatDate(fixedBirthday)
  38. )
  39. person.setValue_forProperty_(fixedBirthday, birthdayProp)
  40.  
  41. book = AddressBook.ABAddressBook.sharedAddressBook()
  42.  
  43. for person in book.people():
  44. fixPersonBirthday(person)
  45.  
  46. book.save()

  1. 10.5.1, MacBook Pro Core 2 Duo 

  2. Names have been changed to protect the innocent. 

  3. 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… 

  4. Anno Domini, not Anno Antidomini