Velocity Reviews > How to find difference in years between two dates?

# How to find difference in years between two dates?

thebjorn
Guest
Posts: n/a

 07-26-2006
For the purpose of finding someone's age I was looking for a way to
find how the difference in years between two dates, so I could do
something like:

age = (date.today() - born).year

but that didn't work (the timedelta class doesn't have a year
accessor).

I looked in the docs and the cookbook, but I couldn't find anything, so
I came up with:

def age(born):
now = date.today()
birthday = date(now.year, born.month, born.day)
return now.year - born.year - (birthday > now and 1 or 0)

i.e. calculate the "raw" years first and subtract one if he hasn't had
his birthday yet this year... It works, but I'd rather use a standard
and generic approach if it exists...?

-- bjorn

John Machin
Guest
Posts: n/a

 07-26-2006
thebjorn wrote:
> For the purpose of finding someone's age I was looking for a way to
> find how the difference in years between two dates, so I could do
> something like:
>
> age = (date.today() - born).year
>
> but that didn't work (the timedelta class doesn't have a year
> accessor).
>
> I looked in the docs and the cookbook, but I couldn't find anything, so
> I came up with:
>
> def age(born):
> now = date.today()
> birthday = date(now.year, born.month, born.day)

Bad luck if the punter was born on 29 Feb and the current year is not a
leap year.

> return now.year - born.year - (birthday > now and 1 or 0)

Holy code bloat, Batman! Try this:

return now.year - born.year - (birthday > now)

>
> i.e. calculate the "raw" years first and subtract one if he hasn't had
> his birthday yet this year... It works, but I'd rather use a standard
> and generic approach if it exists...?
>

It's the irregular-size months that cause the problems. If you can work
out the months difference, then just floor_div by 12 to get the years
difference.

Below is some code from the ancient times when everybody and his dog
each had their own date class

HTH,
John

8<--- methods from a date class

def months_until(self, to_date):
"""Return number of months between from_date (self) and to_date.
"""
from_date = self
signum = 1
if from_date > to_date:
from_date, to_date = to_date, from_date
signum = -1
d1, m1, y1 = from_date.day, from_date.month, from_date.year
d2, m2, y2 = to_date.day, to_date.month, to_date.year
mdiff = (y2 - y1) * 12 + m2 - m1
if d2 < d1 and (d2 < 28 or d2 != last_day_of_month(y2, m2)):
# the test d2 < 28 is not necessary; it is an optimisation
# to avoid calling last_day_of_month unnecessarily
mdiff = mdiff - 1
return mdiff * signum

def years_until(self, to_date):
"""Return number of years between from_date (self) and to_date.
"""
md = self.months_until(to_date)
if md >= 0:
return md // 12
else:
# ensure division truncates towards zero
return -((-md) // 12)

8<--- module-level functions and constants

# days in month
_dim = (None, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31)

def last_day_of_month(y, m):
"""Return day (1..31) which is last day of month m in year y
"""
if m == 2:
return 28 + _leap(y)
else:
if not (1 <= m <= 12):
raise DateError, "month not in 1..12"
return _dim[m]

def _leap(y):
if y % 4: return 0
if y % 100: return 1
if y % 400: return 0
return 1

8<----

Bruno Desthuilliers
Guest
Posts: n/a

 07-26-2006
thebjorn wrote:
> For the purpose of finding someone's age I was looking for a way to
> find how the difference in years between two dates, so I could do
> something like:
>
> age = (date.today() - born).year
>
> but that didn't work (the timedelta class doesn't have a year
> accessor).
>
> I looked in the docs and the cookbook, but I couldn't find anything, so
> I came up with:
>
> def age(born):
> now = date.today()
> birthday = date(now.year, born.month, born.day)
> return now.year - born.year - (birthday > now and 1 or 0)
>
> i.e. calculate the "raw" years first and subtract one if he hasn't had
> his birthday yet this year... It works, but I'd rather use a standard
> and generic approach if it exists...?

You may want to have a look at mxDatetime, which has a RelativeDateTime
type that seems to do what you want:
http://www.egenix.com/files/python/mxDateTime.html

--
bruno desthuilliers
python -c "print '@'.join(['.'.join([w[::-1] for w in p.split('.')]) for
p in '(E-Mail Removed)'.split('@')])"

Roy Smith
Guest
Posts: n/a

 07-26-2006
"thebjorn" <(E-Mail Removed)> wrote:

> def age(born):
> now = date.today()
> birthday = date(now.year, born.month, born.day)
> return now.year - born.year - (birthday > now and 1 or 0)

I don't get that last line. There's two things in particular that are
puzzling me.

1) What does "birthday > now" mean? It sounds like you haven't been born
yet.

2) I find the "and 1 or 0" part very confusing. I can't remember all the
minor rules about operator precedence, but I'm sure this works out to some
clever hack involving boolean short-circuit evaluation to get around the
lack of a ternary operator in python. If I need to pull out the reference
manual to decipher what an expression means, it's too complicated. Try
something like:

if birthday > now:
return now.year - born.year - 1
else:
return now.year - born.year

It takes up a little more space, but it's bog easy to understand without
obscure language details.

John Machin
Guest
Posts: n/a

 07-26-2006
Bruno Desthuilliers wrote:
> thebjorn wrote:
> > For the purpose of finding someone's age I was looking for a way to
> > find how the difference in years between two dates, so I could do
> > something like:
> >
> > age = (date.today() - born).year
> >
> > but that didn't work (the timedelta class doesn't have a year
> > accessor).
> >
> > I looked in the docs and the cookbook, but I couldn't find anything, so
> > I came up with:
> >
> > def age(born):
> > now = date.today()
> > birthday = date(now.year, born.month, born.day)
> > return now.year - born.year - (birthday > now and 1 or 0)
> >
> > i.e. calculate the "raw" years first and subtract one if he hasn't had
> > his birthday yet this year... It works, but I'd rather use a standard
> > and generic approach if it exists...?

>
> You may want to have a look at mxDatetime, which has a RelativeDateTime
> type that seems to do what you want:
> http://www.egenix.com/files/python/mxDateTime.html
>

Which pieces of the following seem to be working to you?

>>> import mx.DateTime
>>> f = mx.DateTime.RelativeDateTimeDiff
>>> d = mx.DateTime.Date
>>> f(d(2000, 2, 29), d(2001, 2, 2)

<RelativeDateTime instance for 'YYYY-(-11)-(-2 HH:MM:SS' at 0xaee170>
>>> f(d(2000, 2, 29), d(2001, 3, 1))

<RelativeDateTime instance for '(-0001)-MM-(-01) HH:MM:SS' at 0xb06530>
>>> f(d(2001, 1, 31), d(2001, 2, 2)

<RelativeDateTime instance for 'YYYY-MM-(-2 HH:MM:SS' at 0xaee170>
>>> g = lambda x, y: f(y, x)
>>> g(d(2000, 2, 29), d(2001, 2, 2)

<RelativeDateTime instance for 'YYYY-(+11)-(+30) HH:MM:SS' at 0xb06580>
>>> g(d(2000, 2, 29), d(2001, 3, 1))

<RelativeDateTime instance for '(+0001)-MM-DD HH:MM:SS' at 0xaee170>
>>> g(d(2001, 1, 31), d(2001, 2, 2)

<RelativeDateTime instance for 'YYYY-MM-(+2 HH:MM:SS' at 0xb06580>
>>>

and going the other way, adding one month to 31 January gives you some
date in March which is ludicrous.

Bruno Desthuilliers
Guest
Posts: n/a

 07-26-2006
John Machin wrote:
> Bruno Desthuilliers wrote:
>
>>thebjorn wrote:
>>
>>>For the purpose of finding someone's age I was looking for a way to
>>>find how the difference in years between two dates, so I could do
>>>something like:
>>>
>>> age = (date.today() - born).year
>>>
>>>but that didn't work (the timedelta class doesn't have a year
>>>accessor).
>>>
>>>I looked in the docs and the cookbook, but I couldn't find anything, so
>>>I came up with:
>>>
>>> def age(born):
>>> now = date.today()
>>> birthday = date(now.year, born.month, born.day)
>>> return now.year - born.year - (birthday > now and 1 or 0)
>>>
>>>i.e. calculate the "raw" years first and subtract one if he hasn't had
>>>his birthday yet this year... It works, but I'd rather use a standard
>>>and generic approach if it exists...?

>>
>>You may want to have a look at mxDatetime, which has a RelativeDateTime
>>type that seems to do what you want:
>>http://www.egenix.com/files/python/mxDateTime.html
>>

>
>
> Which pieces of the following seem to be working to you?

John, it seems you failed to notice the use of "may" and "seems" in my
post. IIRC, both are supposed to strongly suggest a lack of certitude.

--
bruno desthuilliers
python -c "print '@'.join(['.'.join([w[::-1] for w in p.split('.')]) for
p in '(E-Mail Removed)'.split('@')])"

John Machin
Guest
Posts: n/a

 07-26-2006

Bruno Desthuilliers wrote:
> John Machin wrote:
> > Bruno Desthuilliers wrote:

>
> >
> > Which pieces of the following seem to be working to you?

>
> John, it seems you failed to notice the use of "may" and "seems" in my
> post. IIRC, both are supposed to strongly suggest a lack of certitude.
>
>

I didn't fail to notice that you were seeming. Re-read my question:
It's asking you which bits you were seeming.

bearophileHUGS@lycos.com
Guest
Posts: n/a

 07-26-2006
Roy Smith:
> 2) I find the "and 1 or 0" part very confusing. I can't remember all the
> minor rules about operator precedence, but I'm sure this works out to some
> clever hack involving boolean short-circuit evaluation to get around the
> lack of a ternary operator in python. If I need to pull out the reference
> manual to decipher what an expression means, it's too complicated. Try
> something like:

>From the manual, 5.10:
>(Note that neither and nor or restrict the value and type they return to False and True, but rather return the last evaluated argument. This is sometimes useful, e.g., if s is a string that should be replaced by a default value if it is empty, the expression s or 'foo' yields the desired value. Because not has to invent a value anyway, it does not bother to return a value of the same type as its argument, so e.g., not 'foo' yields False, not ''.)<

Then are such things something good to remove from Python 3.0 (making
or and and always return True or False), to simplify the language, and
make it more clear and reduce the possibility of bugs?

Bye,
bearophile

Bruno Desthuilliers
Guest
Posts: n/a

 07-26-2006
John Machin wrote:
> Bruno Desthuilliers wrote:
>
>>John Machin wrote:
>>
>>>Bruno Desthuilliers wrote:

>>
>>>Which pieces of the following seem to be working to you?

>>
>>John, it seems you failed to notice the use of "may" and "seems" in my
>>post. IIRC, both are supposed to strongly suggest a lack of certitude.
>>

>
> I didn't fail to notice that you were seeming. Re-read my question:
> It's asking you which bits you were seeming.

OP problem:
age = (date.today() - born).year

Possible solution:

import mx.DateTime as dt
def age(date):
return dt.Age(dt.today(), date).years
born = dt.Date(1967, 5, 1)
assert age(born) == 39

--
bruno desthuilliers
python -c "print '@'.join(['.'.join([w[::-1] for w in p.split('.')]) for
p in '(E-Mail Removed)'.split('@')])"

thebjorn
Guest
Posts: n/a

 07-26-2006
Roy Smith wrote:
> "thebjorn" <(E-Mail Removed)> wrote:
>
> > def age(born):
> > now = date.today()
> > birthday = date(now.year, born.month, born.day)
> > return now.year - born.year - (birthday > now and 1 or 0)

>
> I don't get that last line. There's two things in particular that are
> puzzling me.
>
> 1) What does "birthday > now" mean? It sounds like you haven't been born
> yet.

I'm making a (perhaps tenous) semantic distinction between birthdate,
the date you were born on, and birthday, an annual event that may or
may not have happened yet this year

> 2) I find the "and 1 or 0" part very confusing. I can't remember all the
> minor rules about operator precedence, but I'm sure this works out to some
> clever hack involving boolean short-circuit evaluation to get around the
> lack of a ternary operator in python.

You're correct the line was originally:

return now.year - born.year - (1 if birthday > now else 0)

which gave a nice traceback on the production server that didn't have
2.5 on it The and/or short-circuit is a fairly well established
(yet divisive) pydiom, and I was going to say something about people
really ought to learn a few simple precedence rules, but then I
realized the parenthesis aren't needed in the above <ehm..> The
parenthesis are needed in a version someone else mentioned:

return now.year - born.year - (birthday > now)

but I wouldn't write that, just like I wouldn't write 1 + True..

> If I need to pull out the reference manual to decipher what an expression means,
> it's too complicated.

Nah, that's a little too restrictive I think. I will agree that the
and/or is more cute than functional, at least in this case. Since it

def yeardiff(a, b):
y = a.year - b.year
if (a.month, a.day) < (b.month, b.day): # tuple comparison
y -= 1
return y

def age(born): return yeardiff(date.today(), born)

> if birthday > now:
> return now.year - born.year - 1
> else:
> return now.year - born.year

I prefer to hoist the common expression out of the branches so they
don't have an opportunity to get out of sync, but I get your point.

-- bjorn