I was working on a problem the other day that required me to calculate the age of a person in years. Rather than visiting Stack Overflow and grabbing a ready made solution I put together the following function:

def age(when, on=None):
    if on is None:
        on = datetime.date.today()
    on_unix = time.mktime(on.timetuple())
    when_unix = time.mktime(when.timetuple())
    return int((on_unix - when_unix) / 3.15569e7)

# age of someone from today (2014/09/04)
age(date(2000, 1, 1))  # 14
age(date(1997, 9, 4))  # 17
age(date(1997, 9, 5))  # 16

# age of someone from 2014/01/01
age(date(2000, 1, 1), date(2014, 1, 1))  # 14
age(date(1997, 9, 4), date(2014, 1, 1))  # 16
age(date(1997, 9, 5), date(2014, 1, 1))  # 16

While this approach was working as expected, it was very slow, and I suspected it was the time.mktime that was the source of it. To Stack Overflow!

I found the Age from birthdate in python question and some of the solutions that were in there were overly complex for the problem at hand. I didn’t even test the speed of them because my code was much easier to read.

Then I found this beautiful piece of code and modified it to behave like my function above:

def age(when, on=None):
    if on is None:
        on = datetime.date.today()
    was_earlier = (on.month, on.day) < (when.month, when.day)
    return on.year - when.year - (was_earlier)

The magic is that True and False are automatically converted to 1 and 0 when interpreted as integers.

The performance numbers speak for themselves. Using timeit I called the function 100000 times. The average speed of my implementation over 5 runs was 3.164 seconds, whereas Danny’s was 0.121 seconds!