Wiek od daty urodzenia w Pythonie

159

Jak znaleźć wiek w Pythonie od dzisiejszej daty i datę urodzenia osoby? Data urodzenia pochodzi z DateField w modelu Django.

tkalve
źródło
4
Gdy standardowy datetimemoduł to za mało, możesz spróbować: labix.org/python-dateutil
Tomasz Zieliński
1
Prawie na pewno zostało to rozwiązane przez:dateutil.relativedelta.relativedelta
Williams

Odpowiedzi:

288

Można to zrobić znacznie prościej, biorąc pod uwagę, że int (True) to 1, a int (False) to 0:

from datetime import date

def calculate_age(born):
    today = date.today()
    return today.year - born.year - ((today.month, today.day) < (born.month, born.day))
Danny W. Adair
źródło
4
dzióbek: date.today()zwraca datę w lokalnej strefie czasowej, która może być inna niż miejsce urodzenia. Być może będziesz musiał jawnie użyć stref czasowych
jfs
10
To prawdopodobnie zależy od twojej definicji „wieku”. Ze względów praktycznych data urodzenia jest zwykle podawana jako data, a nie jako data i godzina uwzględniająca strefę czasową (tj. „Data urodzenia” nie zawiera szczegółów). Większość ludzi nie rodzi się ostro o północy (więc zwykle obserwuje się przedwcześnie :-)), a będąc w innej strefie czasowej, zakładam, że większość ludzi obchodzi swoje urodziny w czasie lokalnym (tak właśnie robię, żyję 10-12h naprzód) mojego miejsca urodzenia). Gdyby „born” było datą uwzględniającą strefę czasową, można by użyć arytmetyki pytz i normalize () - może być interesujące dla oprogramowania do astrologii?
Danny W. Adair
2
Zgadzam się całkowicie w kontekście ludzkich wieków, ale pańska formuła może być użyta w szerszym kontekście. Chociaż osobiście nigdy nie obchodzę swoich urodzin nawet godzinę wcześniej ze względu na rodzinną tradycję i bycie programistą, obliczanie czasu, gdziekolwiek jestem, jest banalne.
jfs
@pyd: born is a date / datetime
kjagiello
68
from datetime import date

def calculate_age(born):
    today = date.today()
    try: 
        birthday = born.replace(year=today.year)
    except ValueError: # raised when birth date is February 29 and the current year is not a leap year
        birthday = born.replace(year=today.year, month=born.month+1, day=1)
    if birthday > today:
        return today.year - born.year - 1
    else:
        return today.year - born.year

Aktualizacja: użyj rozwiązania Danny'ego , tak jest lepiej

mpen
źródło
2
Z zasady exceptblok powinien wychwytywać tylko jeden wyjątek, który można zgłosić.
Daenyth
1
@Daenyth: Dobra rozmowa ... Myślę, że to ValueError. Zaktualizowano.
mpen
Posunąłem się nawet do przetestowania przesłania wyjątku, aby upewnić się, że tego właśnie oczekuję. Nawet przy powyższym kodzie istnieje możliwość, że zostanie wyrzucony błąd ValueError, ale nie jest to oczekiwany błąd ValueError.
Randy Syring,
+ za wyjątkiem, ale czy jest jakiś problem w moim ? Myślę, że to całkiem proste. def calculate_age(dob)
Grijesh Chauhan
1
@GrijeshChauhan: Tak, twój nie działa. datetime.date(2014, 1, 1)daje -1, to powinno dać 0. today > dobSprawdzasz, czy data urodzenia jest w przeszłości, a nie wcześniej w tym samym roku. datetime.date.today()zawiera informacje o roku, dlatego w moim rozwiązaniu zastępuję je rokiem bieżącym.
mpen
18
from datetime import date

days_in_year = 365.2425    
age = int((date.today() - birth_date).days / days_in_year)

W Pythonie 3 można wykonać podział na datetime.timedelta:

from datetime import date, timedelta

age = (date.today() - birth_date) // timedelta(days=365.2425)
Jeden
źródło
2
każdy czwarty rok jest rokiem przestępnym, z wyjątkiem każdego setnego roku nie jest rokiem przestępnym, chyba że każdy czterysta lat jest rokiem przestępnym. try days_in_year = 365,2425
Dan
3
@Dan: różnica między juliańskim ( 365.25) a gregoriańskim rokiem kalendarzowym ( 365.2425) jest mniejsza niż jeden dzień, jeśli żyjesz mniej niż 130 lat.
jfs
4
To nie działa dla niektórych dat: (date(2017, 3, 1) - date(2004, 3, 1)) / timedelta(days=365.2425)powinien zwrócić 13, ale zwraca 12. Rezultat jest bez podłogi 12.999582469181433.
href_
13

Zgodnie z sugestią @ [Tomasz Zieliński] i @Williams python-dateutil może to zrobić tylko w 5 liniach.

from dateutil.relativedelta import *
from datetime import date
today = date.today()
dob = date(1982, 7, 5)
age = relativedelta(today, dob)

>>relativedelta(years=+33, months=+11, days=+16)`
Arun Kumar Khattri
źródło
10

Najprostszym sposobem jest użycie python-dateutil

import datetime

import dateutil

def birthday(date):
    # Get the current date
    now = datetime.datetime.utcnow()
    now = now.date()

    # Get the difference between the current date and the birthday
    age = dateutil.relativedelta.relativedelta(now, date)
    age = age.years

    return age
user1569050
źródło
7
To nie działa poprawnie, gdy urodziny przypadają 29 lutego, a dzisiejsza data to 28 lutego (będzie działać tak, jakby dzisiaj była 29 lutego).
Trey Hunner
6
from datetime import date

def age(birth_date):
    today = date.today()
    y = today.year - birth_date.year
    if today.month < birth_date.month or today.month == birth_date.month and today.day < birth_date.day:
        y -= 1
    return y
gzerone
źródło
data lub jakiś podobny obiekt, docs.python.org/3/library/datetime.html#datetime.date, poprawiono literówkę.
gzerone
5

Niestety, nie możesz po prostu użyć timedelata, ponieważ największą używaną jednostką jest dzień, a lata przestępne spowodują, że obliczenia będą nieprawidłowe. Dlatego znajdźmy liczbę lat, a następnie dostosujmy o jeden, jeśli ostatni rok nie jest pełny:

from datetime import date
birth_date = date(1980, 5, 26)
years = date.today().year - birth_date.year
if (datetime.now() - birth_date.replace(year=datetime.now().year)).days >= 0:
    age = years
else:
    age = years - 1

Aktualizacja:

To rozwiązanie naprawdę powoduje wyjątek, gdy w grę wchodzi 29 lutego. Oto poprawne sprawdzenie:

from datetime import date
birth_date = date(1980, 5, 26)
today = date.today()
years = today.year - birth_date.year
if all((x >= y) for x,y in zip(today.timetuple(), birth_date.timetuple()):
   age = years
else:
   age = years - 1

Upd2:

Wywoływanie wielokrotnych wezwań do now()hitów wydajnościowych jest śmieszne, nie ma to znaczenia we wszystkich, ale w wyjątkowo szczególnych przypadkach. Prawdziwym powodem używania zmiennej jest ryzyko niespójności danych.

Alexander Lebedev
źródło
Dziękuję, dowiedziałem się o tym, wykonując kilka testów - i skończyło się na podobnym kodzie znalezionym w linku AndiDog.
tkalve
3
Ostrzeżenie 1: używasz datetime.datetime zamiast datetime.date. Ostrzeżenie 2: Twój kod jest brzydki i nieefektywny. Wywołanie datetime.now () TRZY razy ?? Ostrzeżenie 3: Data urodzenia 29 lutego 2004 r. I dzisiejsza data 28 lutego 2010 r. Powinny zwracać wiek 6 lat, a nie umierać z krzykiem „Błąd wartości: dzień jest poza zakresem miesiąca”. Wyszedłeś!
John Machin,
Przepraszamy, Twój kod „Upd” jest jeszcze bardziej barokowy i zepsuty niż pierwsza próba - nie ma to nic wspólnego z 29 lutego; zawodzi w WIELU prostych przypadkach, takich jak 2009-06-15 do 2010-07-02 ... osoba ma oczywiście nieco ponad 1 rok, ale odliczasz rok, ponieważ test w dniach (2> = 15) kończy się niepowodzeniem. I oczywiście nie testowałeś go - zawiera błąd składniowy.
John Machin,
4

Klasyczne rozwiązanie w tym scenariuszu dotyczy tego, co zrobić z osobami urodzonymi 29 lutego. Przykład: musisz mieć ukończone 18 lat, aby głosować, prowadzić samochód, kupować alkohol itp. ... jeśli urodziłeś się 29.02.2004, jaki jest pierwszy dzień, w którym możesz robić takie rzeczy: 2022-02 -28 czy 2022-03-01? ODPOWIEDŹ, głównie pierwsza, ale kilku zabójczych radości może powiedzieć to drugie.

Oto kod, który obsługuje 0,068% (w przybliżeniu) populacji urodzonej tego dnia:

def age_in_years(from_date, to_date, leap_day_anniversary_Feb28=True):
    age = to_date.year - from_date.year
    try:
        anniversary = from_date.replace(year=to_date.year)
    except ValueError:
        assert from_date.day == 29 and from_date.month == 2
        if leap_day_anniversary_Feb28:
            anniversary = datetime.date(to_date.year, 2, 28)
        else:
            anniversary = datetime.date(to_date.year, 3, 1)
    if to_date < anniversary:
        age -= 1
    return age

if __name__ == "__main__":
    import datetime

    tests = """

    2004  2 28 2010  2 27  5 1
    2004  2 28 2010  2 28  6 1
    2004  2 28 2010  3  1  6 1

    2004  2 29 2010  2 27  5 1
    2004  2 29 2010  2 28  6 1
    2004  2 29 2010  3  1  6 1

    2004  2 29 2012  2 27  7 1
    2004  2 29 2012  2 28  7 1
    2004  2 29 2012  2 29  8 1
    2004  2 29 2012  3  1  8 1

    2004  2 28 2010  2 27  5 0
    2004  2 28 2010  2 28  6 0
    2004  2 28 2010  3  1  6 0

    2004  2 29 2010  2 27  5 0
    2004  2 29 2010  2 28  5 0
    2004  2 29 2010  3  1  6 0

    2004  2 29 2012  2 27  7 0
    2004  2 29 2012  2 28  7 0
    2004  2 29 2012  2 29  8 0
    2004  2 29 2012  3  1  8 0

    """

    for line in tests.splitlines():
        nums = [int(x) for x in line.split()]
        if not nums:
            print
            continue
        datea = datetime.date(*nums[0:3])
        dateb = datetime.date(*nums[3:6])
        expected, anniv = nums[6:8]
        age = age_in_years(datea, dateb, anniv)
        print datea, dateb, anniv, age, expected, age == expected

Oto wynik:

2004-02-28 2010-02-27 1 5 5 True
2004-02-28 2010-02-28 1 6 6 True
2004-02-28 2010-03-01 1 6 6 True

2004-02-29 2010-02-27 1 5 5 True
2004-02-29 2010-02-28 1 6 6 True
2004-02-29 2010-03-01 1 6 6 True

2004-02-29 2012-02-27 1 7 7 True
2004-02-29 2012-02-28 1 7 7 True
2004-02-29 2012-02-29 1 8 8 True
2004-02-29 2012-03-01 1 8 8 True

2004-02-28 2010-02-27 0 5 5 True
2004-02-28 2010-02-28 0 6 6 True
2004-02-28 2010-03-01 0 6 6 True

2004-02-29 2010-02-27 0 5 5 True
2004-02-29 2010-02-28 0 5 5 True
2004-02-29 2010-03-01 0 6 6 True

2004-02-29 2012-02-27 0 7 7 True
2004-02-29 2012-02-28 0 7 7 True
2004-02-29 2012-02-29 0 8 8 True
2004-02-29 2012-03-01 0 8 8 True
John Machin
źródło
Niedawno dowiedziałem się o sekundzie przestępnej .
Bobort
3

Jeśli chcesz wydrukować to na stronie przy użyciu szablonów django, to może wystarczyć:

{{ birth_date|timesince }}
Anoyz
źródło
4
Nie używaj Django |timesincedo obliczania timedelta przez kilka lat, ponieważ nie uwzględnia lat przestępnych, a tym samym daje niedokładne wyniki. Zobacz bilet Django nr 19210, aby uzyskać więcej informacji na ten temat.
jnns
Nie wiedziałem tego. Dzięki.
Anoyz,
2

Oto rozwiązanie pozwalające znaleźć wiek osoby jako lata, miesiące lub dni.

Powiedzmy, że data urodzenia osoby to 2012-01-17T00: 00: 00 W związku z tym jej wiek na dzień 2013-01-16T00: 00: 00 będzie wynosił 11 miesięcy

lub jeśli urodził się 2012-12-17T00: 00: 00 , jego wiek z dnia 2013-01-12T00: 00: 00 wyniesie 26 dni

lub jeśli urodził się 2000-02-29T00: 00: 00 , jego wiek 2012-02-29T00: 00: 00 będzie wynosił 12 lat

Będziesz musiał zaimportować datę i godzinę .

Oto kod:

def get_person_age(date_birth, date_today):

"""
At top level there are three possibilities : Age can be in days or months or years.
For age to be in years there are two cases: Year difference is one or Year difference is more than 1
For age to be in months there are two cases: Year difference is 0 or 1
For age to be in days there are 4 possibilities: Year difference is 1(20-dec-2012 - 2-jan-2013),
                                                 Year difference is 0, Months difference is 0 or 1
"""
years_diff = date_today.year - date_birth.year
months_diff = date_today.month - date_birth.month
days_diff = date_today.day - date_birth.day
age_in_days = (date_today - date_birth).days

age = years_diff
age_string = str(age) + " years"

# age can be in months or days.
if years_diff == 0:
    if months_diff == 0:
        age = age_in_days
        age_string = str(age) + " days"
    elif months_diff == 1:
        if days_diff < 0:
            age = age_in_days
            age_string = str(age) + " days"
        else:
            age = months_diff
            age_string = str(age) + " months"
    else:
        if days_diff < 0:
            age = months_diff - 1
        else:
            age = months_diff
        age_string = str(age) + " months"
# age can be in years, months or days.
elif years_diff == 1:
    if months_diff < 0:
        age = months_diff + 12
        age_string = str(age) + " months" 
        if age == 1:
            if days_diff < 0:
                age = age_in_days
                age_string = str(age) + " days" 
        elif days_diff < 0:
            age = age-1
            age_string = str(age) + " months"
    elif months_diff == 0:
        if days_diff < 0:
            age = 11
            age_string = str(age) + " months"
        else:
            age = 1
            age_string = str(age) + " years"
    else:
        age = 1
        age_string = str(age) + " years"
# The age is guaranteed to be in years.
else:
    if months_diff < 0:
        age = years_diff - 1
    elif months_diff == 0:
        if days_diff < 0:
            age = years_diff - 1
        else:
            age = years_diff
    else:
        age = years_diff
    age_string = str(age) + " years"

if age == 1:
    age_string = age_string.replace("years", "year").replace("months", "month").replace("days", "day")

return age_string

Niektóre dodatkowe funkcje używane w powyższych kodach to:

def get_todays_date():
    """
    This function returns todays date in proper date object format
    """
    return datetime.now()

I

def get_date_format(str_date):
"""
This function converts string into date type object
"""
str_date = str_date.split("T")[0]
return datetime.strptime(str_date, "%Y-%m-%d")

Teraz musimy się pożywić podać get_date_format () ciągami takimi jak 2000-02-29T00: 00: 00

Przekształci go w obiekt typu data, do którego ma zostać dostarczony get_person_age (data_urodzin, data_dzień) .

Funkcja get_person_age (date_birth, date_today) zwróci wiek w formacie ciągu.

Shubham L.
źródło
2

Rozwijanie rozwiązań Danny'ego , ale z różnymi sposobami zgłaszania wieku młodszych ludzi (uwaga, dzisiaj jest datetime.date(2015,7,17)):

def calculate_age(born):
    '''
        Converts a date of birth (dob) datetime object to years, always rounding down.
        When the age is 80 years or more, just report that the age is 80 years or more.
        When the age is less than 12 years, rounds down to the nearest half year.
        When the age is less than 2 years, reports age in months, rounded down.
        When the age is less than 6 months, reports the age in weeks, rounded down.
        When the age is less than 2 weeks, reports the age in days.
    '''
    today = datetime.date.today()
    age_in_years = today.year - born.year - ((today.month, today.day) < (born.month, born.day))
    months = (today.month - born.month - (today.day < born.day)) %12
    age = today - born
    age_in_days = age.days
    if age_in_years >= 80:
        return 80, 'years or older'
    if age_in_years >= 12:
        return age_in_years, 'years'
    elif age_in_years >= 2:
        half = 'and a half ' if months > 6 else ''
        return age_in_years, '%syears'%half
    elif months >= 6:
        return months, 'months'
    elif age_in_days >= 14:
        return age_in_days/7, 'weeks'
    else:
        return age_in_days, 'days'

Przykładowy kod:

print '%d %s' %calculate_age(datetime.date(1933,6,12)) # >=80 years
print '%d %s' %calculate_age(datetime.date(1963,6,12)) # >=12 years
print '%d %s' %calculate_age(datetime.date(2010,6,19)) # >=2 years
print '%d %s' %calculate_age(datetime.date(2010,11,19)) # >=2 years with half
print '%d %s' %calculate_age(datetime.date(2014,11,19)) # >=6 months
print '%d %s' %calculate_age(datetime.date(2015,6,4)) # >=2 weeks
print '%d %s' %calculate_age(datetime.date(2015,7,11)) # days old

80 years or older
52 years
5 years
4 and a half years
7 months
6 weeks
7 days
Dannid
źródło
1

Ponieważ nie widziałem poprawnej implementacji, przekodowałem swoją w ten sposób ...

    def age_in_years(from_date, to_date=datetime.date.today()):
  if (DEBUG):
    logger.debug("def age_in_years(from_date='%s', to_date='%s')" % (from_date, to_date))

  if (from_date>to_date): # swap when the lower bound is not the lower bound
    logger.debug('Swapping dates ...')
    tmp = from_date
    from_date = to_date
    to_date = tmp

  age_delta = to_date.year - from_date.year
  month_delta = to_date.month - from_date.month
  day_delta = to_date.day - from_date.day

  if (DEBUG):
    logger.debug("Delta's are : %i  / %i / %i " % (age_delta, month_delta, day_delta))

  if (month_delta>0  or (month_delta==0 and day_delta>=0)): 
    return age_delta 

  return (age_delta-1)

Założenie, że 28 lutego urodził się 29 lutego, jest „18”, jest po prostu błędne. Zamiana granic może zostać pominięta ... to tylko osobista wygoda dla mojego kodu :)

Tomasz
źródło
1

Rozszerz odpowiedź na Danny W. Adair , aby otrzymać również miesiąc

def calculate_age(b):
    t = date.today()
    c = ((t.month, t.day) < (b.month, b.day))
    c2 = (t.day< b.day)
    return t.year - b.year - c,c*12+t.month-b.month-c2
SmartManoj
źródło
1
import datetime

Dzisiejsza data

td=datetime.datetime.now().date() 

Twoja data urodzenia

bd=datetime.date(1989,3,15)

Twój wiek

age_years=int((td-bd).days /365.25)
Aseem
źródło
0

import datetime

def age(date_of_birth):
    if date_of_birth > datetime.date.today().replace(year = date_of_birth.year):
        return datetime.date.today().year - date_of_birth.year - 1
    else:
        return datetime.date.today().year - date_of_birth.year

W Twoim przypadku:

import datetime

# your model
def age(self):
    if self.birthdate > datetime.date.today().replace(year = self.birthdate.year):
        return datetime.date.today().year - self.birthdate.year - 1
    else:
        return datetime.date.today().year - self.birthdate.year
doomatel
źródło
0

Nieznacznie zmodyfikowane rozwiązanie Danny'ego dla łatwiejszego czytania i rozumienia

    from datetime import date

    def calculate_age(birth_date):
        today = date.today()
        age = today.year - birth_date.year
        full_year_passed = (today.month, today.day) < (birth_date.month, birth_date.day)
        if not full_year_passed:
            age -= 1
        return age
Pecos Pest
źródło