python :: how old are you?
It's a simple question, any ~5 y/o can answer it "I'm five years, four months, and two days". So, how do we calculate that using Python? Subtracting date objects only produces objects giving the difference in days, which is very excact, and very unhelpful -- still it's the only sane option for a datetime library...
One problem is how old a person is who is born in the leap-year 2000, on date(2000, 2, 29) if today was date(2003, 2, 28). I decided they were 2 years, 11 months, and 30 days, and that on date(2003, 3, 1) they were 3 years and 1 day. That seems very logical to me, although I know there are lot's of people that disagree with me... Feel free to come up with code that works for you
The code turned out to be relatively short and simple and works on the simple principle of "I'm one year older if my birthday is today or was earlier this year".
-
from datetime import date as _date
-
from calendar import monthrange as _monthrange
-
-
def age(dob, today=_date.today()):
-
y = today.year - dob.year
-
m = today.month - dob.month
-
d = today.day - dob.day
-
-
while m <0 or d <0:
-
while m <0:
-
y -= 1
-
m = 12 + m # m is negative
-
if d <0:
-
m -= 1
-
days = days_previous_month(today.year, today.month)
-
d = max(0, days - dob.day) + today.day
-
-
return y, m, d
-
-
def days_previous_month(y, m):
-
m -= 1
-
if m == 0:
-
y -= 1
-
m = 12
-
_, days = _monthrange(y, m)
-
return days
The calendar module is way underutilized most of the time, so it was nice to get to use it...
You might want to look at pyfdate: http://www.ferg.org/pyfdate
This Python program:
==============================================================================
from pyfdate import *
birthday = Time(2000,2,29)
today = Time(2003,2,28)
years, months, period = today.diffym(birthday)
print “On”, today.d
print “person is”, years, “years”, months,”months”, period.shortest
today = Time(2003,3,1)
years, months, period = today.diffym(birthday)
print “On”, today.d
print “person is”, years, “years”, months,”months”, period.shortest
==============================================================================
produces this output
==============================================================================
On February 28, 2003
person is 3 years 0 months
On March 1, 2003
person is 3 years 0 months 1 day
==============================================================================
As you can see, pyfdate’s answer for the age of the person on Feb. 28, 2003 differs from yours. Ask yourself: what is Feb 29 2000 plus 3 years? Since it obviously can’t be Feb 29 2003, I think that pretty much by universal convention it is Feb 28 2003. That’s the date on which the person is 3 years old.
According to your reasoning, there is *no* date on which this person is exactly 3 years old. I think most people would disagree with an algorithm for calculating age that would *never* report people with certain birthdays as being 3 years old.
Comment by Stephen Ferg — December 18, 2007 @ 5:53 am
Hi Stephen,
You have correctly identified the “battle lines” for this issue: do people born on Feb 29 have a birthdate every year? According to my uncle, next year will be special since we’ll be able to celebrate my grandma’s 84th on her birthday.
My approach was that it doesn’t neccessarily make sense to ask (let’s switch to ISO formatted dates) “what is 2000-02-29 + 3 years”, however you can always ask “what is 2003-02-28 - 3 years” (i.e. subtraction is always well defined over years). Or to put it in Python terms, what would the following print out for you
[python]
t = Time(2000,2,29)
t += Period(year=3)
t -= Period(year=3)
print t
[/python]
(I haven’t looked too closely at your code, but it seems you’re aware of this problem since you can’t actually add three years to a Time..?)
I will say that a lot of people agree with you, very strongly, on this issue — and I think that’s absolutely fine! As I said above “feel free to come up with code that works for you”, and you certainly have
If you want some critique of it, I think you should probably revisit the naming of your Time class — the semantics are surprising given the standard datetime module. Also, when it comes to internationalization (i18n), please always use Unicode! However in this case you can save yourself a lot of work by using the calendar and locale modules:
[python]
>>> import calendar
>>> list(calendar.day_name)
[’Monday’, ‘Tuesday’, ‘Wednesday’, ‘Thursday’, ‘Friday’, ‘Saturday’, ‘Sunday’]
>>> import locale
>>> locale.setlocale(locale.LC_ALL, ‘NO’)
‘Norwegian (Bokm\xe5l)_Norway.1252′
>>> list(calendar.day_name)
[’mandag’, ‘tirsdag’, ‘onsdag’, ‘torsdag’, ‘fredag’, ‘l\xf8rdag’, ’s\xf8ndag’]
>>>
[/python]
I’ve created a module pyfdate_local_norsk.py for Norwegian if you’re interested.
Comment by tb — December 18, 2007 @ 9:47 am
Yes, you’re right about some of the paradoxical consequences of year arithmetic on dates.
>>> from pyfdate import *
>>> t = Time(2000,2,29)
>>> t = t.add(years=3)
>>> print t
2003-02-28 00:00:00
>>> t = t.subtract(years=3)
>>> print t
2000-02-28 00:00:00
>>> t=Time(2000,2,29)
>>> t.d
‘February 29, 2000′
>>> t = t.add(years=3)
>>> t.d
‘February 28, 2003′
>>> t = t.minus(years=3)
>>> t.d
‘February 28, 2000′
Thanks for the tip on setlocal. Although I’m a strong advocate of internationalization, I’m not yet very good at it! I’ll look into using setlocal. It might allow me to reduce or remove pyfdate’s use of localization files. (And thanks for pyfdate_local_norsk!)
Naming these things is difficult. I think “datetime” and “timedelta” are rather geekish — that interferes with one of my goals: getting “ordinary” people (e.g. people whose sole programming experience consists of hacking Microsoft batch files) to feel comfortable moving from batch files to Python. The philosophically correct name for a datetime is probably something like PointInTime — but that was both too long and too philosophical, so I settled on just “Time”. It is short, easy to remember, and has natural-language equivalents in most languages (e.g. “temps” in French). As for a timedelta, I thought about “Duration” and “PeriodOfTime” or “TimePeriod”, and finally settled on just “Period”. In short, I chose names that may not seem natural to Python programmers familiar with the datetime module, but will (I hope) seem natural and easy-to-use to non-programmers that I would like to recruit to Python.
Comment by Stephen Ferg — December 18, 2007 @ 4:19 pm
I applaud your goals. Making things easy is usually very hard
Date/time issues don’t make it any easier, of course (although everyone seems to think it _should_ be easy). A long time ago my team managed to cause the trainstation in Paris to show times the were an hour off for almost an entire day while we tracked down a daylight savings time (DST) bug. If your module becomes popular, I’m sure you too will be asked to implement correct semantics for a train leaving Paris at 1am and arriving in London 4 hours later, while accounting for both timezones and DST changing
If you’re aiming at attracting new programmers to Python, it might be better to make your module interact well with the stdlib modules (I’m thinking about time in the datetime module being something different from Time in your module).
One thing you might also consider, given your target audience, is that most of them will probably find
t.minus(year=3, month=2)
to be “unnatural”, compared to e.g.:
t - (year(3) + month(2))
or perhaps
t - (3*year + 2*month)
(just a though
Comment by tb — December 18, 2007 @ 7:02 pm