Edad desde la fecha de nacimiento en python

159

¿Cómo puedo encontrar una edad en Python desde la fecha de hoy y la fecha de nacimiento de una persona? La fecha de nacimiento es de un DateField en un modelo Django.

tkalve
fuente
44
Cuando el datetimemódulo estándar no es suficiente, puede intentar: labix.org/python-dateutil
Tomasz Zieliński
1
Esto casi seguramente ha sido resuelto por:dateutil.relativedelta.relativedelta
Williams

Respuestas:

288

Eso se puede hacer mucho más simple considerando que int (True) es 1 e int (False) es 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
fuente
44
un punto crítico: date.today()devuelve la fecha en la zona horaria local que puede ser diferente del lugar de nacimiento. Es posible que necesite usar zonas horarias explícitamente
jfs
10
Eso probablemente depende de su definición de "edad". Para todos los propósitos prácticos, un cumpleaños generalmente se da como una fecha, no como fecha / hora (es decir, "nacido" no tiene los detalles). La mayoría de las personas no nacen a la medianoche en punto (por lo general, observan prematuramente :-)), y cuando están en una zona horaria diferente, supongo que la mayoría de las personas observan su cumpleaños en la hora local (eso es lo que hago, vivo 10-12h por delante de mi tiempo de lugar de nacimiento). Si "nacido" era una fecha y hora que reconoce la zona horaria, podría usar la aritmética de pytz y normalizar (), ¿tal vez sea de interés para un software de astrología?
Danny W. Adair
2
Estoy completamente de acuerdo en el contexto de las edades humanas, pero su fórmula puede usarse en un contexto más amplio. Aunque personalmente nunca celebro mi cumpleaños ni una hora antes debido a una tradición familiar y a ser un programador, es trivial calcular el tiempo donde quiera que esté.
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

Actualización: use la solución de Danny , es mejor

mpen
fuente
2
Solo como una cuestión de principio, su exceptbloqueo debe detectar solo una excepción específica que podría generarse.
Daenyth
1
@Daenyth: Buena decisión ... creo que es una ValueError. Actualizado.
mpen
Incluso llego al extremo de probar el mensaje de la excepción para asegurarme de que sea lo que estoy esperando. Incluso con el código anterior, existe la posibilidad de que se arroje un ValueError, pero no es el ValueError lo que espera.
Randy Syring
+ por excepción pero, ¿hay algún problema en mi ? Creo que es bastante simple. def calculate_age(dob)
Grijesh Chauhan
1
@GrijeshChauhan: Sí, el tuyo no funciona. datetime.date(2014, 1, 1)da -1, debería dar 0. Tu today > dobestá comprobando si el DOB está en el pasado, no a principios de este mismo año. datetime.date.today()incluye información del año, por lo que lo reemplazo con el año actual en mi solución.
mpen
18
from datetime import date

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

En Python 3, puede realizar la división en datetime.timedelta:

from datetime import date, timedelta

age = (date.today() - birth_date) // timedelta(days=365.2425)
El único
fuente
2
cada cuarto año es bisiesto, excepto que cada centésimo año no es bisiesto, excepto que cada cuatrocientos años es bisiesto. pruebe days_in_year = 365.2425
Dan
3
@Dan: la diferencia entre Julian ( 365.25) y el año calendario gregoriano ( 365.2425) es menos de un día si vives menos de 130 años.
jfs
44
Esto no funciona para algunas fechas: (date(2017, 3, 1) - date(2004, 3, 1)) / timedelta(days=365.2425)debería regresar 13, pero regresa 12. Sin piso, el resultado es 12.999582469181433.
href_
13

Según lo sugerido por @ [Tomasz Zielinski] y @Williams python-dateutil pueden hacerlo solo 5 líneas.

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
fuente
10

La forma más simple es usar 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
usuario1569050
fuente
77
Esto no funciona correctamente cuando el cumpleaños es el 29 de febrero y la fecha de hoy es el 28 de febrero (actuará como si hoy fuera el 29 de febrero).
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
fuente
fecha de instancia o algún obj como ese, docs.python.org/3/library/datetime.html#datetime.date, error tipográfico corregido.
gzerone
5

Desafortunadamente, no puede simplemente usar timedelata ya que la unidad más grande que usa es el día y los años bisiestos harán que sus cálculos no sean válidos. Por lo tanto, busquemos el número de años y luego ajustemos por uno si el último año no está lleno:

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

Upd:

Esta solución realmente causa una excepción cuando entra en juego el 29 de febrero. Aquí está la verificación correcta:

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:

Llamar varias llamadas a now()un éxito en el rendimiento es ridículo, no importa en todos, sino en casos extremadamente especiales. La verdadera razón para usar una variable es el riesgo de incosistencia de datos.

Alexander Lebedev
fuente
Gracias, descubrí esto haciendo algunas pruebas y terminé un código similar encontrado en el enlace de AndiDog.
tkalve
3
Strike 1: estás usando datetime.datetime en lugar de datetime.date. Strike 2: tu código es feo e ineficiente. Llamando a datetime.now () TRES veces? Huelga 3: Fecha de nacimiento el 29 de febrero de 2004 y la fecha de hoy 28 de febrero de 2010 debería regresar a los 6 años, no morir gritando "ValueError: el día está fuera de rango para el mes". ¡Estás fuera!
John Machin
Lo sentimos, su código "Upd" es aún más barroco y roto que el primer intento, nada que ver con el 29 de febrero; falla en MUCHOS casos simples como el 15-06-2009 al 02/07/2010 ... la persona obviamente tiene poco más de 1 año, pero usted deduce un año porque la prueba en los días (2> = 15) falla. Y obviamente no lo ha probado, contiene un error de sintaxis.
John Machin
4

El clásico truco en este escenario es qué hacer con las personas nacidas el 29 de febrero. Ejemplo: debe tener 18 años para votar, conducir un automóvil, comprar alcohol, etc. Si nace el 29/02/2004, ¿cuál es el primer día en que se le permite hacer estas cosas? 2022-02 -28 o 2022-03-01? AFAICT, principalmente el primero, pero algunos killjoys podrían decir el último.

Aquí hay un código que atiende al 0.068% (aprox.) De la población nacida ese día:

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

Aquí está la salida:

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
fuente
Y recientemente acabo de enterarme del segundo salto .
Bobort el
3

Si está buscando imprimir esto en una página usando plantillas de django, entonces lo siguiente podría ser suficiente:

{{ birth_date|timesince }}
Anoyz
fuente
44
No use Django's |timesincepara calcular un timedelta durante varios años porque no tiene en cuenta los años bisiestos y, por lo tanto, produce resultados inexactos. Vea el boleto Django # 19210 para más información sobre esto.
jnns
No lo sabia. Gracias.
Anoyz
2

Aquí hay una solución para encontrar la edad de una persona, ya sea en años, meses o días.

Digamos que la fecha de nacimiento de una persona es 2012-01-17T00: 00: 00 Por lo tanto, su edad en 2013-01-16T00: 00: 00 será 11 meses

o si nació el 2012-12-17T00: 00: 00 , su edad el 2013-01-12T00: 00: 00 será 26 días

o si nació el 2000-02-29T00: 00: 00 , su edad el 2012-02-29T00: 00: 00 será 12 años

Deberá importar fecha y hora .

Aquí está el código:

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

Algunas funciones adicionales utilizadas en los códigos anteriores son:

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

Y

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")

Ahora, tenemos que alimentar get_date_format () con las cadenas como 2000-02-29T00: 00: 00

Lo convertirá en el objeto de tipo de fecha que se alimentará a get_person_age (date_birth, date_today) .

La función get_person_age (date_birth, date_today) devolverá la edad en formato de cadena.

Shubham L
fuente
2

Ampliando la solución de Danny , pero con todo tipo de formas de informar edades para personas más jóvenes (nota, hoy es 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'

Código de muestra:

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
fuente
1

Como no vi la implementación correcta, recodifiqué la mía de esta manera ...

    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)

La suposición de ser "18" el 28 de febrero cuando nació el 29 es simplemente errónea. Intercambiar los límites se puede omitir ... es solo una conveniencia personal para mi código :)

Thomas
fuente
1

Extienda a Danny W. Adair Respuesta , para obtener mes también

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
fuente
1
import datetime

Fecha

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

Tu fecha de nacimiento

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

Tu edad

age_years=int((td-bd).days /365.25)
Aseem
fuente
0

fecha y hora de importación

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

En tu caso:

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
fuente
0

La solución de Danny ligeramente modificada para facilitar la lectura y comprensión

    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
plaga de pecos
fuente