web-dev-qa-db-fra.com

Différence pythonique entre deux dates en années?

Y at-il un moyen plus efficace de le faire ci-dessous? Je veux avoir la différence en années entre deux dates sous la forme d'un seul scalaire. Toutes les suggestions sont les bienvenues.

from datetime import datetime
start_date = datetime(2010,4,28,12,33)
end_date = datetime(2010,5,5,23,14)
difference  = end_date - start_date
difference_in_years = (difference.days + difference.seconds/86400)/365.2425
36
c00kiemonster

Si vous voulez des résultats précis, je vous recommande d’utiliser la bibliothèque dateutil .

from dateutil.relativedelta import relativedelta
difference_in_years = relativedelta(end_date, start_date).years

Ceci est pour les années complètes (par exemple, l'âge d'une personne). Si vous voulez des fractions d'années, ajoutez des mois, des jours, des heures, ... jusqu'à la précision souhaitée.

57
Karl Bartel

J'utilise l'un de ceux-ci pour calculer l'âge de la personne:

import datetime
dob = datetime.date(1980, 10, 10)

def age():
    today = datetime.date.today()
    years = today.year - dob.year
    if today.month < dob.month or (today.month == dob.month and today.day < dob.day):
        years -= 1
    return years

def age2():
    today = datetime.date.today()
    this_year_birthday = datetime.date(today.year, dob.month, dob.day)
    if this_year_birthday < today:
        years = today.year - dob.year
    else:
        years = today.year - dob.year - 1
    return years
10
Kostyantyn

Plus efficace? Non, mais plus correct probablement. Mais cela dépend de la pertinence de votre décision. Les dates ne sont pas des choses triviales.

Les années n'ont pas une longueur constante. Voulez-vous la différence en années bissextiles ou normales? :-) En calculant, vous obtiendrez toujours une réponse légèrement incorrecte. Et combien de temps dure une journée en années? Vous dites 1/365.2425. Eh bien, oui, en moyenne sur mille ans, oui. Mais sinon non.

Donc, la question n'a pas vraiment de sens. 

Pour être correct, vous devez faire ceci:

from datetime import datetime
from calendar import isleap
start_date = datetime(2005,4,28,12,33)
end_date = datetime(2010,5,5,23,14)
diffyears = end_date.year - start_date.year
difference  = end_date - start_date.replace(end_date.year)
days_in_year = isleap(end_date.year) and 366 or 365
difference_in_years = diffyears + (difference.days + difference.seconds/86400.0)/days_in_year

Dans ce cas, il s’agit d’une différence de 0,0012322917425568528 années, soit 0,662 jour, alors que ce n’est pas une année bissextile.

(et alors nous ignorons les microsecondes. Heh.)

7
Lennart Regebro

Pour donner un sens aux années bissextiles, vous êtes presque obligé de diviser cela en deux parties: un nombre entier d'années et une fraction. Les deux doivent faire face aux années bissextiles, mais de différentes manières: l'intégrale doit prendre en compte la date de début du 29 février et la fraction doit traiter du nombre de jours différent dans une année. Vous souhaitez que la fraction décroisse de manière égale jusqu'à atteindre 1.0 à la prochaine date anniversaire. Elle doit donc être basée sur le nombre de jours de l'année après la date de fin.

Voulez-vous que votre plage de dates comprenne 1900 ou 2100? Les choses deviennent un peu plus faciles si vous ne le faites pas .


Edit: Il m'a fallu beaucoup de temps pour le raisonner. Le problème de base est que les années civiles ne sont pas de taille constante, mais vous les obligez à rester constantes en leur attribuant la valeur 1,0. Pour cette raison, toute solution que vous proposez comportera des anomalies et vous devrez choisir les anomalies avec lesquelles vous pouvez vivre. John Machin avait raison.

Quelle est la différence entre 2008-02-28 et 2009-02-28? La plupart des gens s'accorderaient pour dire que ce devrait être exactement un an. Qu'en est-il de la différence entre 2008-03-01 et 2009-03-01? Encore une fois, la plupart des gens seraient d’accord pour dire que ce devrait être exactement un an. Si vous choisissez de représenter une date sous forme d'année plus une fraction d'année en fonction du jour, il est impossible de rendre ces deux affirmations vraies. C’est le cas de votre code original qui supposait un jour de 1/365.2425 d’une année, ou bien de tout code supposant une fraction d’année constante par jour, même si la taille d’une journée rend compte des années années.

Mon affirmation selon laquelle vous deviez diviser ce nombre en années intégrales et fractionnaires était une tentative de contourner ce problème. Si vous traitez chacune des conditions précédentes comme une année intégrale, il vous suffit de choisir la fraction à affecter au nombre de jours restant. Le problème avec ce schéma est que vous ne pouvez toujours pas comprendre (date2-date1) + date3, car la fraction ne peut pas être résolue en un jour avec une cohérence quelconque.

Je propose donc un autre codage basé sur une année contenant 366 jours, qu’il s’agisse d’une année bissextile ou non. Les anomalies seront d’abord que la date ne peut pas être exactement un an (ou 2 ou 3) à partir du 29 février - "Désolé Johnny, tu ne reçois pas d’anniversaire cette année, il n’ya pas de jour précédent pas toujours acceptable. Deuxièmement, si vous essayez de contraindre un tel nombre à revenir à une date donnée, vous devrez tenir compte des années non bissextiles, vérifier le cas spécial du 29 février et le convertir, probablement au 1er mars.

from datetime import datetime
from datetime import timedelta
from calendar import isleap

size_of_day = 1. / 366.
size_of_second = size_of_day / (24. * 60. * 60.)

def date_as_float(dt):
    days_from_jan1 = dt - datetime(dt.year, 1, 1)
    if not isleap(dt.year) and days_from_jan1.days >= 31+28:
        days_from_jan1 += timedelta(1)
    return dt.year + days_from_jan1.days * size_of_day + days_from_jan1.seconds * size_of_second

start_date = datetime(2010,4,28,12,33)
end_date = datetime(2010,5,5,23,14)
difference_in_years = date_as_float(end_time) - date_as_float(start_time)

Je ne dis pas que c'est la solution {the}, parce que je ne pense pas qu'une solution parfaite soit possible. Mais il a certaines propriétés souhaitables:

  • La différence entre les dates ayant le même mois, le même jour et la même heure correspondra au nombre exact d'années.
  • Ajouter une différence à une autre date donnera une valeur pouvant être reconvertie en une date utile.
5
Mark Ransom

Faites juste ceci:

from dateutil.relativedelta import relativedelta

myBirthday = datetime.datetime(1983,5,20,0,0,0,0)
now = datetime.datetime.now()



difference = relativedelta(now, myBirthday)
print("My years: "+str(difference.years))
5
Michel Fernandes

Voici un extrait de ce que Kostyantyn a publié dans sa fonction "age2". Il est légèrement plus court/plus propre et utilise également le sens traditionnel/familier d'un "âge" ou d'une différence d'âge: 

def ageInYears( d ):
    today = datetime.date.today()
    currentYrAnniversary = datetime.date( today.year, d.month, d.day )
    return (today.year - d.year) - (1 if today < currentYrAnniversary else 0)
1
BuvinJ

Si vous voulez dire efficace en termes d'espace de code, alors non, c'est à peu près le moyen le plus efficace de le faire.

0
dan_waterworth

Avant d'installer la bibliothèque:

choco upgrade Python -y
python pip install python-dateutil

En une ligne en Python dans Cmder (windows):

python -c "import datetime; from dateutil.relativedelta import relativedelta; myBirthday = datetime.datetime(2019,2,6,11,0,0,0); now = datetime.datetime.utcnow(); diff = relativedelta(now, myBirthday); print ("'My'+'" "'+'year'+'" "'+':'+'" "'+'%d''" "''and''" "''%d''" "''microseconds'" % (diff.years, diff.microseconds))"

Pourcentage d'échappement par lots avec %%:

python -c "import datetime; from dateutil.relativedelta import relativedelta; myBirthday = datetime.datetime(2019,2,6,11,0,0,0); now = datetime.datetime.utcnow(); diff = relativedelta(now, myBirthday); print ("'My'+'" "'+'year'+'" "'+':'+'" "'+'%%d''" "''and''" "''%%d''" "''microseconds'" %% (diff.years, diff.microseconds))"
0
jhonitalia

Depuis que nous arrivons à la fin de 2018 ...

from dateutil import parser
from dateutil.relativedelta import relativedelta

rip = [
    ["Tim Bergling\t\t",         " 8 Sep 1989", "20 Apr 2018"], # Avicii Swedish musician
    ["Stephen Hillenburg\t",     "21 Aug 1961", "26 Nov 2018"], # Creator of Spongebob
    ["Stephen Hawking\t\t",      " 8 Jan 1942", "14 Mar 2018"], # Theoretical physicist
    ["Stan Lee\t\t",             "28 Dec 1922", "12 Nov 2018"], # American comic book writer
    ["Stefán Karl Stefánsson\t", "10 Jul 1975", "21 Aug 2018"]  # Robbie Rotten from LazyTown
    ]

for name,born,died in rip:
    print("%s %s\t %s\t died at %i"%(name,born,died,relativedelta(parser.parse(died),parser.parse(born)).years))

sortie

Tim Bergling              8 Sep 1989     20 Apr 2018     died at 28
Stephen Hillenburg       21 Aug 1961     26 Nov 2018     died at 57
Stephen Hawking           8 Jan 1942     14 Mar 2018     died at 76
Stan Lee                 28 Dec 1922     12 Nov 2018     died at 95
Stefán Karl Stefánsson   10 Jul 1975     21 Aug 2018     died at 43
0
Puddle

Voici ce que je suis venu avec, sans utiliser une dépendance externe:

def year_diff(d1, d2):
    """Returns the number of years between the dates as a positive integer."""
    later = max(d1, d2)
    earlier = min(d1, d2)

    result = later.year - earlier.year
    if later.month < earlier.month or (later.month == earlier.month and later.day < earlier.day):
        result -= 1

    return result
0
Aidan Feldman

Fonction plus robuste - calcule la différence en années (âge) et en jours:

def get_diff_in_years_and_days(from_date, to_date):
    try:
        from_in_this_year = date(to_date.year, from_date.month, from_date.day)
    except:
        from_in_this_year = date(to_date.year, from_date.month, from_date.day-1) # today is feb in leap year

    if from_in_this_year <= to_date:
        years = to_date.year - from_date.year
        days = (to_date - from_in_this_year).days
    else:
        years = to_date.year - from_date.year - 1
        try:
            from_in_prev_year = date(to_date.year-1, from_date.month, from_date.day)
        except:
            from_in_prev_year = date(to_date.year-1, from_date.month, from_date.day-1) # today is feb in leap year
        days = (to_date - from_in_prev_year).days

    assert days>=0 and days<=365, days
    assert years>=0, years

    return years, days

quelques tests unitaires:

self.assertEqual((0,  0), get_diff_in_years_and_days(date(2018,1, 1), date(2018,1, 1)))
self.assertEqual((1,  0), get_diff_in_years_and_days(date(2017,1, 1), date(2018,1, 1)))
self.assertEqual((1,  1), get_diff_in_years_and_days(date(2017,1, 1), date(2018,1, 2)))
self.assertEqual((2,  0), get_diff_in_years_and_days(date(2016,2,29), date(2018,2,28)))
self.assertEqual((2,  1), get_diff_in_years_and_days(date(2014,2,28), date(2016,2,29)))
self.assertEqual((1,364), get_diff_in_years_and_days(date(2014,2,28), date(2016, 2,27)))
self.assertEqual((3,30) , get_diff_in_years_and_days(date(2015,10,1), date(2018,10,31)))
self.assertEqual((10,30), get_diff_in_years_and_days(date(2010,10,1), date(2020,10,31)))
self.assertEqual((3,31) , get_diff_in_years_and_days(date(2015,10,1), date(2018,11, 1)))
self.assertEqual((2,364), get_diff_in_years_and_days(date(2015,10,1), date(2018, 9,30)))
0
Robert Lujo