views:

2660

answers:

13

I need to check if some number of years have been since some date. Currently I've got timedelta from datetime module and I don't know how to convert it to years.

A: 

How exact do you need it to be? td.days / 365.25 will get you pretty close, if you're worried about leap years.

eduffy
I'm really worried about leap years. It should check if person is over 18 years old.
Migol
Then there's no easy one-liner, you're going to have to parse the two dates and figured out if the person has passed their 18th birthday or not.
eduffy
+3  A: 

First off, at the most detailed level, the problem can't be solved exactly. Years vary in length, and there isn't a clear "right choice" for year length.

That said, get the difference in whatever units are "natural" (probably seconds) and divide by the ratio between that and years. E.g.

delta_in_days / (365.25)
delta_in_seconds / (365.25*24*60*60)

...or whatever. Stay away from months, since they are even less well defined than years.

MarkusQ
That is NOT what anybody means or uses when it's a question of how many years service or has a person attained a particular age.
John Machin
Your 365.25 should be 365.2425 to take the 400 year exception of the Gregorian calendar into account.
brianary
+14  A: 

If you're trying to check if someone is 18 years of age, using timedelta will not work correctly on some edge cases because of leap years. For example, someone born on January 1, 2000, will turn 18 exactly 6575 days later on January 1, 2018 (5 leap years included), but someone born on January 1, 2001, will turn 18 exactly 6574 days later on January 1, 2019 (4 leap years included). Thus, you if someone is exactly 6574 days old, you can't determine if they are 17 or 18 without knowing a little more information about their birthdate.

The correct way to do this is to calculate the age directly from the dates, by subtracting the two years, and then subtracting one if the current month/day precedes the birth month/day.

Adam Rosenfield
A: 

Sorry guys, but non of your answer was useful. This is the answer:

def haveMoreYearsGone(self, d, years):
    today = date.today()
    if (d.year + years < today.year):
        return True
    if (d.year + years > today.year):
        return False

    if (d.day == 29 and d.month == 2):
        d.replace(day=28)
    if (today.day == 29 and today.month == 2):
        today.replace(day=28)
    d.replace(year=today.year)
    span = today - d

    return (span.days >= 0)

Tough luck it can't be made in lambda (in non-obstruficated way).

Migol
This won't work for a couple of reasons: first, d.today() always gives you the current date (you probably meant d.date()). Second, d.replace(year=today.year) will raise an exception if d is on February 29th.
Rick Copeland
Thanks Rick, this was an issue
Migol
You do realize that in addition to the problems noted in the previous comment, this doesn't even do what you asked for in the question, right? This returns a boolean, where as in the question you asked for a number of years.
MarkusQ
This may be true, but the comments indicate that the OP was really looking for something that returned a boolean. (And I forgot to mention, the use of "self" in the solution above is extraneous).
Rick Copeland
+7  A: 

You need more than a timedelta to tell how many years have passed; you also need to know the beginning (or ending) date. (It's a leap year thing.)

Your best bet is to use the dateutil.relativedelta object, but that's a 3rd party module. If you want to know the datetime that was n years from some date (defaulting to right now), you can do the following::

from dateutil.relativedelta import relativedelta

def yearsago(years, from_date=None):
    if from_date is None:
        from_date = datetime.now()
    return from_date - relativedelta(years=years)

If you'd rather stick with the standard library, the answer is a little more complex::

from datetime import datetime
def yearsago(years, from_date=None):
    if from_date is None:
        from_date = datetime.now()
    try:
        return from_date.replace(year=from_date.year - years)
    except:
        # Must be 2/29!
        assert from_date.month == 2 and from_date.day == 29 # can be removed
        return from_date.replace(month=2, day=28,
                                 year=from_date.year-years)

If it's 2/29, and 18 years ago there was no 2/29, this function will return 2/28. If you'd rather return 3/1, just change the last return statement to read::

    return from_date.replace(month=3, day=1,
                             year=from_date.year-years)

Your question originally said you wanted to know how many years it's been since some date. Assuming you want an integer number of years, you can guess based on 365.25 days per year and then check using either of the yearsago functions defined above::

def num_years(begin, end=None):
    if end is None:
        end = datetime.now()
    num_years = int((end - begin).days / 365.25)
    if begin > yearsago(num_years, end):
        return num_years - 1
    else:
        return num_years
Rick Copeland
You can be fully accurate with 365.2425 (instead of 365.25), which takes the 400 year exception into account for the Gregorian calendar.
brianary
A: 

Even though this thread is already dead, might i suggest a working solution for this very same problem i was facing. Here it is (date is a string in the format dd-mm-yyyy):

def validatedate(date):
    parts = date.strip().split('-')

    if len(parts) == 3 and False not in [x.isdigit() for x in parts]: 
        birth = datetime.date(int(parts[2]), int(parts[1]), int(parts[0]))
        today = datetime.date.today()

        b = (birth.year * 10000) + (birth.month * 100) + (birth.day)
        t = (today.year * 10000) + (today.month * 100) + (today.day)

        if (t - 18 * 10000) >= b:
            return True

    return False
A: 

this function returns the difference in years between two dates (taken as strings in ISO format, but it can easily modified to take in any format)

import time
def years(earlydateiso,  laterdateiso):
    """difference in years between two dates in ISO format"""

    ed =  time.strptime(earlydateiso, "%Y-%m-%d")
    ld =  time.strptime(laterdateiso, "%Y-%m-%d")
    #switch dates if needed
    if  ld < ed:
        ld,  ed = ed,  ld            

    res = ld[0] - ed [0]
    if res > 0:
        if ld[1]< ed[1]:
            res -= 1
        elif  ld[1] == ed[1]:
            if ld[2]< ed[2]:
                res -= 1
    return res
Mauro Bianchi
A: 

Well, question seems rather easy. You need to check the number of 'full' years, and only if it's equal to 18 you need to bother with months and days. The edge case is: endDate.year - startDate.year == 18 and it splits to two cases: startDate.month != endDate.month and startDate.month == endDate.month, when you just have to check days:

 def isOfAge(birthDate, age=18):
     endDate = date.today()
     years = endDate.year - birthDate.year
     if years == age:
         return (birthDate.month < endDate.month or 
                  (birthDate.month == endDate.month and birthDate.day < endDate.day))         
     return years > age

It's still more than one-liner-lambda, but it's still pretty short, and seems quick in execution.

Abgan
+1  A: 
def age(dob):
    import datetime
    today = datetime.date.today()

    if today.month < dob.month or \
      (today.month == dob.month and today.day < dob.day):
        return today.year - dob.year - 1
    else:
        return today.year - dob.year

>>> import datetime
>>> datetime.date.today()
datetime.date(2009, 12, 1)
>>> age(datetime.date(2008, 11, 30))
1
>>> age(datetime.date(2008, 12, 1))
1
>>> age(datetime.date(2008, 12, 2))
0
John Mee
A person born on 29 February will be treated as having attained age 1 on the following 28 February.
John Machin
Ok. Corrected to accommodate the 0.08% of the population born on the 29th by inverting the test from "is birthday after today" to "is birthday before today". Does that solve it?
John Mee
No, it doesn't solve it. Don't you believe in testing?
John Machin
It does work correctly for your example!?!If I set "today" to 28th February 2009, and the date-of-birth to 29th February 2008 it returns Zero- at least for me; not 1 as you suggest (Python 2.5.2).There's no need to rude Mr Machin. Exactly what two dates are you having trouble with?
John Mee
I'll try again: A person born on 29 February will be treated by most people for most legal purposes as having attained age 1 on the following 28 February. Your code produces 0, both before and after your "correction". In fact, the two versions of your code produce EXACTLY the same output for ALL 9 input possibilities (month < == > X day < == >). BTW, 100.0/(4*365+1) produces 0.068, not 0.08.
John Machin
Oh, well there you go. I didn't know the legalities and you failed to make them unambiguous. Sounds like it was better off the first time around. Unfortunately I'm not able to determine what your 9 scenarios are from the comment. But don't bother to explain; it is certainly time for you to share with us your solution; one that works the way you like.
John Mee
Sigh. (0) Addressing 29 February issues is essential in any date arithmetic; you just ignored it. (1) Your code wasn't better off first time; what don't you understand in "produce EXACTLY the same output"? (2) Three possible atomic results (<, ==, >) comparing today.month and dob.month; three possible atomic results comparing today.day and dob.day; 3 * 3 == 9 (3) http://stackoverflow.com/questions/2217488/age-from-birthdate-in-python/2218654
John Machin
A: 

I'll suggest Pyfdate

What is pyfdate?

Given Python's goal to be a powerful and easy-to-use scripting language, its features for working with dates and times are not as user-friendly as they should be. The purpose of pyfdate is to remedy that situation by providing features for working with dates and times that are as powerful and easy-to-use as the rest of Python.

the tutorial

twils
A: 

Yet another 3rd party lib not mentioned here is mxDateTime (predecessor of both python datetime and 3rd party timeutil) could be used for this task.

The aforementioned yearsago would be:

from mx.DateTime import now, RelativeDateTime

def years_ago(years, from_date=None):
    if from_date == None:
        from_date = now()
    return from_date-RelativeDateTime(years=years)

First parameter is expected to be a DateTime instance.

To convert ordinary datetime to DateTime you could use this for 1 second precision):

def DT_from_dt_s(t):
    return DT.DateTimeFromTicks(time.mktime(t.timetuple()))

or this for 1 microsecond precision:

def DT_from_dt_u(t):
    return DT.DateTime(t.year, t.month, t.day, t.hour,
  t.minute, t.second + t.microsecond * 1e-6)

And yes, adding the dependency for this single task in question would definitely be an overkill compared even with using timeutil (suggested by Rick Copeland).

Antony Hatchkins
A: 

Get the number of days, then divide by 365.2425 (the mean Gregorian year) for years. Divide by 30.436875 (the mean Gregorian month) for months.

brianary
A: 

In the end what you have is a maths issue. If every 4 years we have an extra day lets then dived the timedelta in days, not by 365 but 365*4 + 1, that would give you the amount of 4 years. Then divide it again by 4. timedelta / ((365*4) +1) / 4 = timedelta * 4 / (365*4 +1)

Norberto