Python timedelta in anni


Risposte:


156

Ti serve più di un timedeltaper dire quanti anni sono passati; devi anche conoscere la data di inizio (o fine). (È un anno bisestile.)

La soluzione migliore è utilizzare l' dateutil.relativedelta oggetto , ma si tratta di un modulo di terze parti. Se vuoi sapere datetimeche sono ntrascorsi anni da qualche data (il default è adesso), puoi fare quanto segue:

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)

Se preferisci attenersi alla libreria standard, la risposta è un po 'più complessa:

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)

Se è il 29/02, e 18 anni fa non c'era il 29/02, questa funzione restituirà il 28/02. Se preferisci restituire 3/1, modifica l'ultima returnistruzione in read ::

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

Inizialmente la tua domanda diceva che volevi sapere da quanti anni è passato un po 'di tempo. Supponendo che desideri un numero intero di anni, puoi indovinare in base a 365,25 giorni all'anno e quindi verificare utilizzando una delle yearsagofunzioni sopra definite:

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

26
Puoi essere completamente accurato con 365.2425 (anziché 365.25), che tiene conto dell'eccezione di 400 anni per il calendario gregoriano.
brianary

3
La tua funzione si interrompe legalmente per paesi come il Regno Unito e Hong Kong, perché "radunano" fino al 1 marzo per gli anni bisestili.
antieroe


3
vedi anche questo e questo Entrambi sono ottimi elenchi di cose che non sono vere sul tempo.
gvoysey,

49

Se stai cercando di verificare se qualcuno ha 18 anni, l'utilizzo timedeltanon funzionerà correttamente su alcuni casi limite a causa degli anni bisestili. Ad esempio, qualcuno nato il 1 ° gennaio 2000 compirà 18 anni esattamente 6575 giorni dopo il 1 ° gennaio 2018 (inclusi 5 anni bisestili), ma qualcuno nato il 1 ° gennaio 2001 compirà 18 anni esattamente 6574 giorni dopo il 1 ° gennaio, 2019 (4 anni bisestili inclusi). Pertanto, se qualcuno ha esattamente 6574 giorni, non puoi determinare se ha 17 o 18 anni senza conoscere un po 'più di informazioni sulla sua data di nascita.

Il modo corretto per farlo è calcolare l'età direttamente dalle date, sottraendo i due anni e quindi sottraendo uno se il mese / giorno corrente precede il mese / giorno di nascita.


9

Innanzitutto, al livello più dettagliato, il problema non può essere risolto esattamente. Gli anni variano in lunghezza e non esiste una chiara "scelta giusta" per la durata dell'anno.

Detto questo, ottieni la differenza in qualunque unità sia "naturale" (probabilmente secondi) e dividi per il rapporto tra quello e gli anni. Per esempio

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

... o qualunque altra cosa. Stare lontano da mesi, poiché sono ancora meno ben definiti degli anni.


2
Questo NON è ciò che qualcuno intende o usa quando si tratta di quanti anni di servizio o una persona ha raggiunto un'età particolare.
John Machin,

3
Il tuo 365.25 dovrebbe essere 365.2425 per tenere conto dell'eccezione di 400 anni del calendario gregoriano.
brianary

1
Bene, il problema può essere risolto correttamente - puoi sapere in anticipo quali anni hanno giorni di salto e secondi di salto e tutto il resto. È solo che non esiste un modo estremamente elegante di farlo senza sottrarre gli anni, quindi i mesi, quindi i giorni, ecc ... nelle due date formattate
Litherum

7

Ecco una funzione DOB aggiornata, che calcola i compleanni allo stesso modo degli umani:

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

Questo si interrompe quando dob è il 29 febbraio e l'anno in corso non è un anno bisestile.
Trey Hunner

4

Prendi il numero di giorni, quindi dividi per 365.2425 (l'anno gregoriano medio) per anni. Dividi per 30.436875 (il mese gregoriano medio) per mesi.


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

Una persona nata il 29 febbraio sarà trattata per aver raggiunto l'età di 1 anno il 28 febbraio successivo.
John Machin,

Ok. Corretto per accogliere lo 0,08% della popolazione nata il 29 invertendo il test da "è il compleanno dopo oggi" a "è il compleanno prima di oggi". Questo lo risolve?
John Mee,

Funziona correttamente per il tuo esempio!?! Se imposto "oggi" al 28 febbraio 2009 e la data di nascita al 29 febbraio 2008 restituisce Zero, almeno per me; non 1 come suggerisci (Python 2.5.2). Non è necessario maleducare il signor Machin. Esattamente con quali due date hai problemi?
John Mee,

Ci riproverò: una persona nata il 29 febbraio sarà trattata dalla maggior parte delle persone per la maggior parte degli scopi legali come se avesse raggiunto l'età di 1 anno il 28 febbraio successivo. Il tuo codice produce 0, sia prima che dopo la tua "correzione". In effetti, le due versioni del codice producono ESATTAMENTE lo stesso output per TUTTE le 9 possibilità di input (mese <==> X giorno <==>). A proposito, 100.0 / (4 * 365 + 1) produce 0,068, non 0,08.
John Machin,

2
Sospiro. (0) Affrontare le emissioni del 29 febbraio è essenziale in qualsiasi data aritmetica; l'hai appena ignorato. (1) Il tuo codice non era migliore la prima volta; cosa non capisci in "produce ESATTAMENTE lo stesso output"? (2) Tre possibili risultati atomici (<, ==,>) confrontando today.month e dob.month; tre possibili risultati atomici che confrontano oggi.day e dob.day; 3 * 3 == 9 (3) stackoverflow.com/questions/2217488/…
John Machin

1

Quanto esatto ti serve per essere? td.days / 365.25ti avvicinerà molto, se sei preoccupato per gli anni bisestili.


Sono davvero preoccupato per gli anni bisestili. Dovrebbe verificare se la persona ha più di 18 anni.
Migol,

Quindi non esiste una sola linea di copertina, dovrai analizzare le due date e capire se la persona ha compiuto o meno il 18 ° compleanno.
spigoloso

1

Ancora un'altra libreria di terze parti non menzionata qui è mxDateTime (predecessore sia di Python datetimeche di terze parti timeutil) potrebbe essere usato per questo compito.

Quanto sopra yearsagosarebbe:

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)

Il primo parametro dovrebbe essere DateTimeun'istanza.

Per convertire da ordinario datetimeaDateTime te potresti usare questo per una precisione di 1 secondo):

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

o questo per 1 microsecondo di precisione:

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)

E sì, aggiungere la dipendenza per questa singola attività in questione sarebbe sicuramente eccessivo rispetto all'uso di timeutil (suggerito da Rick Copeland).


1

Alla fine quello che hai è un problema di matematica. Se ogni 4 anni abbiamo un giorno in più permettiamo di immergere la timedelta in giorni, non per 365 ma 365 * 4 + 1, che ti darebbe la quantità di 4 anni. Quindi dividerlo di nuovo per 4. timedelta / ((365 * 4) +1) / 4 = timedelta * 4 / (365 * 4 +1)


La cosa dell'anno bisestile non si applica quando gli anni sono divisibili per 100, tranne quando sono divisibili per 400. Quindi, per l'anno 2000: - è divisibile per quattro, quindi dovrebbe essere un salto ma ... - è anche divisibile per cento, quindi non dovrebbe essere un salto ma ... - è divisibile per 400, quindi in realtà è stato un anno bisestile. Per 1900: - è divisibile per 4, quindi dovrebbe essere un salto. - è divisibile per 100, quindi non dovrebbe essere un salto. - NON è divisibile per 400, quindi questa regola non si applica. Il 1900 non è stato un anno bisestile.
Jblasco,

1

Questa è la soluzione che ho elaborato, spero possa aiutare ;-)

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

0

Anche se questo thread è già morto, potrei suggerire una soluzione funzionante per questo stesso problema che stavo affrontando. Eccola (la data è una stringa nel formato gg-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

0

questa funzione restituisce la differenza in anni tra due date (prese come stringhe in formato ISO, ma può essere facilmente modificata per assumere qualsiasi 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

0

Suggerirò Pyfdate

Che cos'è pyfdate?

Dato l'obiettivo di Python di essere un linguaggio di scripting potente e facile da usare, le sue funzionalità per lavorare con date e orari non sono così facili da usare come dovrebbero essere. Lo scopo di pyfdate è di rimediare a tale situazione fornendo funzionalità per lavorare con date e orari che sono potenti e facili da usare come il resto di Python.

il tutorial


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

Questo è il codice che l'operatore Carrousel stava eseguendo nel film di Logan's Run;)

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


0

Mi sono imbattuto in questa domanda e ho trovato Adams rispondere al più utile https://stackoverflow.com/a/765862/2964689

Ma non c'era alcun esempio di pitone del suo metodo, ma ecco quello che ho finito per usare.

input: oggetto datetime

output: età intera in anni interi

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

0

Mi è piaciuta la soluzione di John Mee per la sua semplicità, e non sono così preoccupato di come, il 28 febbraio o il 1 marzo, quando non è un anno bisestile, determinare l'età delle persone nate il 29 febbraio. Ma ecco una modifica del suo codice che penso affronti i reclami:

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
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.