Python timedelta en años

Respuestas:

156

Necesita más que un timedeltapara saber cuántos años han pasado; También necesita saber la fecha de inicio (o finalización). (Es un año bisiesto).

Su mejor opción es usar el dateutil.relativedelta objeto , pero ese es un módulo de terceros. Si desea saber datetimeque pasaron naños desde alguna fecha (por defecto en este momento), puede hacer lo siguiente:

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)

Si prefiere seguir con la biblioteca estándar, la respuesta es un poco más compleja:

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 ValueError:
        # 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)

Si es 2/29, y hace 18 años no había 2/29, esta función devolverá 2/28. Si prefiere devolver 3/1, simplemente cambie la última returninstrucción para leer ::

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

Su pregunta originalmente decía que quería saber cuántos años han pasado desde alguna fecha. Suponiendo que desea un número entero de años, puede adivinar en base a 365.25 días por año y luego verificar usando cualquiera de las yearsagofunciones definidas anteriormente ::

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
fuente
26
Puede ser completamente preciso con 365.2425 (en lugar de 365.25), que tiene en cuenta la excepción de 400 años para el calendario gregoriano.
brianary
3
Su función se rompe legalmente para países como el Reino Unido y Hong Kong, porque se "redondean" al 1 de marzo por años bisiestos.
antihero
3
vea también esto y esto Ambos son excelentes listas de cosas que no son ciertas sobre el tiempo.
gvoysey
49

Si está tratando de verificar si alguien tiene 18 años de edad, el uso timedeltano funcionará correctamente en algunos casos extremos debido a los años bisiestos. Por ejemplo, alguien nacido el 1 de enero de 2000 cumplirá 18 años exactamente 6575 días después el 1 de enero de 2018 (5 años bisiestos incluidos), pero alguien nacido el 1 de enero de 2001 cumplirá 18 años exactamente 6574 días después el 1 de enero, 2019 (4 años bisiestos incluidos). Por lo tanto, si alguien tiene exactamente 6574 días, no puede determinar si tiene 17 o 18 años sin conocer un poco más de información sobre su fecha de nacimiento.

La forma correcta de hacer esto es calcular la edad directamente de las fechas, restando los dos años, y luego restando uno si el mes / día actual precede al mes / día de nacimiento.

Adam Rosenfield
fuente
9

En primer lugar, en el nivel más detallado, el problema no puede resolverse exactamente. Los años varían en duración, y no hay una clara "elección correcta" para la duración del año.

Dicho esto, obtenga la diferencia en cualquier unidad que sea "natural" (probablemente segundos) y divida por la relación entre eso y años. P.ej

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

...o lo que sea. Manténgase alejado de los meses, ya que están incluso menos definidos que años.

MarkusQ
fuente
2
Eso NO es lo que nadie quiere decir o usar cuando se trata de cuántos años de servicio o una persona ha alcanzado una edad en particular.
John Machin el
3
Su 365.25 debe ser 365.2425 para tener en cuenta la excepción de 400 años del calendario gregoriano.
brianary
1
Bueno, el problema se puede resolver correctamente: puede saber con anticipación qué años tienen días bisiestos y segundos bisiestos y todo eso. Es solo que no hay una forma extremadamente elegante de hacerlo sin restar los años, luego los meses, luego los días, etc. en las dos fechas formateadas
Litherum,
7

Aquí hay una función DOB actualizada, que calcula los cumpleaños de la misma manera que los humanos:

import datetime
import locale


# Source: https://en.wikipedia.org/wiki/February_29
PRE = [
    'US',
    'TW',
]
POST = [
    'GB',
    'HK',
]


def get_country():
    code, _ = locale.getlocale()
    try:
        return code.split('_')[1]
    except IndexError:
        raise Exception('Country cannot be ascertained from locale.')


def get_leap_birthday(year):
    country = get_country()
    if country in PRE:
        return datetime.date(year, 2, 28)
    elif country in POST:
        return datetime.date(year, 3, 1)
    else:
        raise Exception('It is unknown whether your country treats leap year '
                      + 'birthdays as being on the 28th of February or '
                      + 'the 1st of March. Please consult your country\'s '
                      + 'legal code for in order to ascertain an answer.')
def age(dob):
    today = datetime.date.today()
    years = today.year - dob.year

    try:
        birthday = datetime.date(today.year, dob.month, dob.day)
    except ValueError as e:
        if dob.month == 2 and dob.day == 29:
            birthday = get_leap_birthday(today.year)
        else:
            raise e

    if today < birthday:
        years -= 1
    return years

print(age(datetime.date(1988, 2, 29)))
anti héroe
fuente
Esto se rompe cuando Dob es el 29 de febrero y el año actual no es bisiesto.
Trey Hunner
4

Obtenga el número de días, luego divida por 365.2425 (el año gregoriano medio) por años. Dividir por 30.436875 (el mes gregoriano medio) por meses.

brianary
fuente
2
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
fuente
Una persona nacida el 29 de febrero será tratada como haber cumplido 1 año el siguiente 28 de febrero.
John Machin el
Okay. Corregido para acomodar al 0.08% de la población nacida el 29 invirtiendo la prueba de "es cumpleaños después de hoy" a "es cumpleaños antes de hoy". ¿Eso lo resuelve?
John Mee el
¡Funciona correctamente para tu ejemplo! Si establezco "hoy" al 28 de febrero de 2009, y la fecha de nacimiento al 29 de febrero de 2008, devuelve Cero, al menos para mí; no 1 como sugiere (Python 2.5.2). No hay necesidad de grosero Sr. Machin. ¿Exactamente con qué dos fechas tienes problemas?
John Mee
Lo intentaré nuevamente: una persona nacida el 29 de febrero será tratada por la mayoría de las personas para la mayoría de los propósitos legales como haber cumplido 1 año el siguiente 28 de febrero. Su código produce 0, tanto antes como después de su "corrección". De hecho, las dos versiones de su código producen EXACTAMENTE la misma salida para TODAS las 9 posibilidades de entrada (mes <==> X día <==>). Por cierto, 100.0 / (4 * 365 + 1) produce 0.068, no 0.08.
John Machin
2
Suspiro. (0) Abordar los problemas del 29 de febrero es esencial en cualquier fecha aritmética; Simplemente lo ignoraste. (1) Su código no fue mejor la primera vez; ¿Qué no entiendes en "producir EXACTAMENTE la misma salida"? (2) Tres resultados atómicos posibles (<, ==,>) comparando today.month y dob.month; tres posibles resultados atómicos que comparan today.day y dob.day; 3 * 3 == 9 (3) stackoverflow.com/questions/2217488/…
John Machin
1

¿Qué tan exacto necesitas que sea? td.days / 365.25te acercará bastante, si te preocupan los años bisiestos.

eduffy
fuente
Estoy realmente preocupado por los años bisiestos. Debe verificar si la persona es mayor de 18 años.
Migol
Entonces no hay una frase fácil, tendrás que analizar las dos fechas y averiguar si la persona ha cumplido 18 años o no.
eduffy
1

Sin embargo, otra biblioteca de terceros que no se menciona aquí es mxDateTime (predecesora de python datetimey de terceros timeutil) podría usarse para esta tarea.

Lo anterior yearsagosería:

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)

Se espera que el primer parámetro sea una DateTimeinstancia.

Para convertir ordinario datetimea DateTimepodría usar esto para precisión de 1 segundo):

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

o esto para 1 microsegundo de precisión:

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)

Y sí, agregar la dependencia para esta tarea en cuestión definitivamente sería una exageración en comparación incluso con el uso de timeutil (sugerido por Rick Copeland).

Antony Hatchkins
fuente
1

Al final, lo que tienes es un problema de matemáticas. Si cada 4 años tenemos un día extra, dejemos el tiempo delta en días, no en 365 sino 365 * 4 + 1, eso le daría la cantidad de 4 años. Luego divídalo nuevamente por 4. timedelta / ((365 * 4) +1) / 4 = timedelta * 4 / (365 * 4 +1)

Norberto
fuente
Lo del año bisiesto no se aplica cuando los años son divisibles por 100, excepto cuando son divisibles por 400. Entonces, para el año 2000: - es divisible por cuatro, por lo que debería ser bisiesto pero ... - también es divisible por cien, por lo que no debería ser bisiesto pero ... - es divisible por 400, por lo que en realidad fue un año bisiesto. Para 1900: - es divisible por 4, por lo que debería ser un salto. - es divisible por 100, por lo que no debe ser un salto. - NO es divisible por 400, por lo que esta regla no se aplica. 1900 no fue un año bisiesto.
Jblasco
1

Esta es la solución que resolví, espero que pueda ayudar ;-)

def menor_edad_legal(birthday):
    """ returns true if aged<18 in days """ 
    try:

        today = time.localtime()                        

        fa_divuit_anys=date(year=today.tm_year-18, month=today.tm_mon, day=today.tm_mday)

        if birthday>fa_divuit_anys:
            return True
        else:
            return False            

    except Exception, ex_edad:
        logging.error('Error menor de edad: %s' % ex_edad)
        return True
pvilas
fuente
0

Aunque este hilo ya está muerto, ¿puedo sugerir una solución de trabajo para este mismo problema que estaba enfrentando? Aquí está (la fecha es una cadena en el formato dd-mm-aaaa):

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

fuente
0

Esta función devuelve la diferencia en años entre dos fechas (tomadas como cadenas en formato ISO, pero puede modificarse fácilmente para tomar en cualquier formato)

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

Voy a sugerir Pyfdate

¿Qué es pyfdate?

Dado el objetivo de Python de ser un lenguaje de secuencias de comandos potente y fácil de usar, sus funciones para trabajar con fechas y horas no son tan fáciles de usar como deberían ser. El propósito de pyfdate es remediar esa situación al proporcionar funciones para trabajar con fechas y horas que son tan poderosas y fáciles de usar como el resto de Python.

el tutorial

Twils
fuente
0
import datetime

def check_if_old_enough(years_needed, old_date):

    limit_date = datetime.date(old_date.year + years_needed,  old_date.month, old_date.day)

    today = datetime.datetime.now().date()

    old_enough = False

    if limit_date <= today:
        old_enough = True

    return old_enough



def test_ages():

    years_needed = 30

    born_date_Logan = datetime.datetime(1988, 3, 5)

    if check_if_old_enough(years_needed, born_date_Logan):
        print("Logan is old enough")
    else:
        print("Logan is not old enough")


    born_date_Jessica = datetime.datetime(1997, 3, 6)

    if check_if_old_enough(years_needed, born_date_Jessica):
        print("Jessica is old enough")
    else:
        print("Jessica is not old enough")


test_ages()

Este es el código que el operador Carrousel estaba ejecutando en la película Logan's Run;)

https://en.wikipedia.org/wiki/Logan%27s_Run_(film)

Andres Hurtis
fuente
0

Me encontré con esta pregunta y encontré que Adams responde la más útil https://stackoverflow.com/a/765862/2964689

Pero no hubo un ejemplo de su método en Python, pero esto es lo que terminé usando.

entrada: objeto de fecha y hora

salida: edad entera en años enteros

def age(birthday):
    birthday = birthday.date()
    today = date.today()

    years = today.year - birthday.year

    if (today.month < birthday.month or
       (today.month == birthday.month and today.day < birthday.day)):

        years = years - 1

    return years
Sam
fuente
0

Me gustó la solución de John Mee por su simplicidad, y no me preocupa cómo, el 28 de febrero o el 1 de marzo, cuando no es un año bisiesto, determinar la edad de las personas nacidas el 29 de febrero. Pero aquí hay un ajuste de su código que creo aborda las quejas:

def age(dob):
    import datetime
    today = datetime.date.today()
    age = today.year - dob.year
    if ( today.month == dob.month == 2 and
         today.day == 28 and dob.day == 29 ):
         pass
    elif today.month < dob.month or \
      (today.month == dob.month and today.day < dob.day):
        age -= 1
    return age
Rick Graves
fuente