184 votes

Convertir les timedelta en années ?

J'ai besoin de vérifier si un certain nombre d'années se sont écoulées depuis une certaine date. Actuellement, j'ai timedelta de datetime et je ne sais pas comment le convertir en années.

4 votes

Voir cette réponse : stackoverflow.com/a/9754466/65387

0 votes

192voto

Rick Copeland Points 5343

Vous avez besoin de plus qu'un timedelta pour savoir combien d'années se sont écoulées ; vous devez également connaître la date de début (ou de fin). (C'est une question d'année bissextile).

Votre meilleure chance est d'utiliser le dateutil.relativedelta objet mais il s'agit d'un module tiers. Si vous voulez connaître le datetime qui était n ans à partir d'une certaine date (par défaut, maintenant), vous pouvez faire ce qui suit: :

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 vous préférez vous en tenir à la bibliothèque standard, la réponse est un peu plus complexe: :

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 nous sommes le 29 février et qu'il y a 18 ans, le 29 février n'existait pas, cette fonction renverra le 28 février. Si vous préférez renvoyer 3/1, il suffit de modifier le dernier paramètre de la fonction return déclaration à lire : :

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

Votre question disait à l'origine que vous vouliez savoir combien d'années s'étaient écoulées depuis une date donnée. En supposant que vous vouliez un nombre entier d'années, vous pouvez deviner en vous basant sur 365,2425 jours par an, puis vérifier en utilisant l'une des méthodes suivantes yearsago fonctions définies ci-dessus: :

def num_years(begin, end=None):
    if end is None:
        end = datetime.now()
    num_years = int((end - begin).days / 365.2425)
    if begin > yearsago(num_years, end):
        return num_years - 1
    else:
        return num_years

37 votes

Vous pouvez être parfaitement exact avec 365.2425 (au lieu de 365.25), qui tient compte de l'exception de 400 ans pour le calendrier grégorien.

3 votes

Votre fonction ne fonctionne pas légalement pour des pays comme le Royaume-Uni et Hong Kong, car ils "arrondissent" au 1er mars pour les années bissextiles.

4 votes

61voto

Adam Rosenfield Points 176408

Si vous essayez de vérifier si quelqu'un a 18 ans, en utilisant timedelta ne fonctionnera pas correctement dans certains cas limites en raison des années bissextiles. Par exemple, une personne née le 1er janvier 2000 aura 18 ans exactement 6575 jours plus tard, le 1er janvier 2018 (5 années bissextiles comprises), mais une personne née le 1er janvier 2001 aura 18 ans exactement 6574 jours plus tard, le 1er janvier 2019 (4 années bissextiles comprises). Ainsi, si une personne a exactement 6574 jours, vous ne pouvez pas déterminer si elle a 17 ou 18 ans sans connaître un peu plus d'informations sur sa date de naissance.

La manière correcte de procéder est de calculer l'âge directement à partir des dates, en soustrayant les deux années, puis en soustrayant un si le jour/mois actuel précède le jour/mois de naissance.

2 votes

C'est la voie à suivre... sauf si vous travaillez dans un secteur qui aime les estimations au jugé : santé, géologie, etc.

0 votes

Soustraire uniquement les années est une solution tellement élégante et pourtant pas très évidente.

0 votes

T

11voto

MarkusQ Points 15612

Tout d'abord, au niveau le plus détaillé, le problème ne peut pas être résolu exactement. Les années varient en longueur, et il n'y a pas de "bon choix" clair pour la durée d'une année.

Cela dit, obtenez la différence dans les unités "naturelles" (probablement les secondes) et divisez-la par le rapport entre cette unité et les années. Par exemple

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

... ou autre. Restez à l'écart des mois, car ils sont encore moins bien définis que les années.

3 votes

Ce n'est PAS ce que l'on entend ou utilise lorsqu'il s'agit de savoir combien d'années de service ou si une personne a atteint un âge particulier.

4 votes

Votre 365,25 devrait être 365,2425 pour tenir compte de l'exception de 400 ans du calendrier grégorien.

2 votes

Eh bien, le problème peut être résolu correctement - vous pouvez savoir à l'avance quelles années ont des jours et des secondes bissextiles et tout cela. C'est juste qu'il n'y a pas de moyen extrêmement élégant de le faire sans soustraire les années, puis les mois, puis les jours, etc... dans les deux formats de dates.

9voto

antihero Points 262

Voici une fonction DOB mise à jour, qui calcule les anniversaires de la même manière que les humains :

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

0 votes

Cela se produit lorsque le dob est le 29 février et que l'année en cours n'est pas une année bissextile.

5voto

John Mee Points 12004
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

0 votes

Une personne née le 29 février sera considérée comme ayant atteint l'âge de 1 an le 28 février suivant.

0 votes

Ok. J'ai corrigé pour tenir compte des 0,08% de la population née le 29 en inversant le test de "date de naissance après aujourd'hui" à "date de naissance avant aujourd'hui". Cela résout-il le problème ?

0 votes

Cela fonctionne correctement pour votre exemple !?! Si je fixe la date "aujourd'hui" au 28 février 2009, et la date de naissance au 29 février 2008, il renvoie Zéro, du moins pour moi, et non 1 comme vous le suggérez (Python 2.5.2). Il n'est pas nécessaire d'être impoli, M. Machin. Quelles sont exactement les deux dates qui vous posent problème ?

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X