Python timedelta depuis des années

140

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

Migol
la source
4
voir cette réponse: stackoverflow.com/a/9754466/65387
Adam
Related

Réponses:

156

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

Votre meilleur pari est d'utiliser l' dateutil.relativedelta objet , mais c'est un module tiers. Si vous voulez connaître la datetimequi a été des nannées d' une certaine date (par défaut à l' heure actuelle), 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 c'est 2/29, et il y a 18 ans il n'y avait pas 2/29, cette fonction renverra 2/28. Si vous préférez retourner 3/1, changez simplement la dernière returninstruction en read ::

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

Votre question indiquait à l'origine que vous vouliez savoir combien d'années cela faisait depuis une certaine date. En supposant que vous vouliez un nombre entier d'années, vous pouvez deviner sur la base de 365,25 jours par an, puis vérifier en utilisant l'une des yearsagofonctions définies ci-dessus:

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
la source
26
Vous pouvez être totalement précis avec 365,2425 (au lieu de 365,25), qui prend en compte l'exception de 400 ans pour le calendrier grégorien.
brianary
3
Votre fonction est légalement interrompue pour des pays comme le Royaume-Uni et Hong Kong, car ils «arrondissent» au 1er mars pour les années bissextiles.
antihero
3
voir aussi ceci et cela. Les deux sont d'excellentes listes de choses qui ne sont pas vraies sur le temps.
gvoysey
49

Si vous essayez de vérifier si quelqu'un a 18 ans, l'utilisation timedeltane fonctionnera pas correctement dans certains cas extrêmes 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 incluses), 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 incluses). Ainsi, vous si quelqu'un a exactement 6574 jours, vous ne pouvez pas déterminer s'il a 17 ou 18 ans sans connaître un peu plus d'informations sur sa date de naissance.

La bonne façon de faire est de calculer l'âge directement à partir des dates, en soustrayant les deux années, puis en soustrayant un si le mois / jour actuel précède le mois / jour de naissance.

Adam Rosenfield
la source
9

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 de l'année.

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

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

...ou peu importe. Évitez les mois, car ils sont encore moins bien définis que les années.

MarkusQ
la source
2
Ce n'est PAS ce que quiconque veut dire ou utiliser lorsqu'il s'agit de savoir combien d'années de service ou une personne a-t-elle atteint un âge particulier.
John Machin
3
Votre 365,25 doit être 365,2425 pour prendre en compte l'exception de 400 ans du calendrier grégorien.
brianary
1
Eh bien, le problème peut être résolu correctement - vous pouvez dire à l'avance quelles années ont des jours bissextiles et des secondes intercalaires et tout cela. C'est juste qu'il n'y a pas de manière extrêmement élégante de le faire sans soustraire les années, puis les mois, puis les jours, etc ... dans les deux dates formatées
Litherum
7

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)))
Anti Hero
la source
Cela se rompt lorsque dob est le 29 février et que l'année en cours n'est pas une année bissextile.
Trey Hunner
4

Obtenez le nombre de jours, puis divisez par 365,2425 (l'année grégorienne moyenne) pendant des années. Divisez par 30,436875 (le mois grégorien moyen) pendant des mois.

Brianary
la source
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
la source
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.
John Machin
D'accord. Corrigé pour tenir compte des 0,08% de la population née le 29 en inversant le test de «est anniversaire après aujourd'hui» à «est anniversaire avant aujourd'hui». Est-ce que cela résout le problème?
John Mee
Cela fonctionne correctement pour votre exemple!?! Si je mets «aujourd'hui» au 28 février 2009 et la date de naissance au 29 février 2008, cela renvoie zéro - du moins pour moi; pas 1 comme vous le suggérez (Python 2.5.2). Il n'y a pas besoin de grossier M. Machin. Avec quelles deux dates avez-vous des problèmes?
John Mee
Je vais réessayer: une personne née le 29 février sera considérée par la plupart des gens pour la plupart des raisons juridiques comme ayant atteint l'âge de 1 an le 28 février suivant. Votre code produit 0, à la fois avant et après votre "correction". En fait, les deux versions de votre code produisent EXACTEMENT la même sortie pour TOUTES les 9 possibilités d'entrée (mois <==> X jour <==>). BTW, 100,0 / (4 * 365 + 1) produit 0,068 et non 0,08.
John Machin
2
Soupir. (0) La résolution des problèmes du 29 février est essentielle dans toute arithmétique de date; vous l'avez simplement ignoré. (1) Votre code n'était pas mieux la première fois; que ne comprenez-vous pas dans «produire exactement le même résultat»? (2) Trois résultats atomiques possibles (<, ==,>) comparant today.month et dob.month; trois résultats atomiques possibles comparant today.day et dob.day; 3 * 3 == 9 (3) stackoverflow.com/questions/2217488/…
John Machin
1

Dans quelle mesure avez-vous besoin que ce soit exact? td.days / 365.25vous rapprochera, si vous vous inquiétez des années bissextiles.

Eduffy
la source
Je suis vraiment inquiet pour les années bissextiles. Il devrait vérifier si la personne a plus de 18 ans.
Migol
Ensuite, il n'y a pas de one-liner facile, vous allez devoir analyser les deux dates et déterminer si la personne a passé son 18e anniversaire ou non.
eduffy
1

Une autre bibliothèque tierce non mentionnée ici est mxDateTime (prédécesseur de python datetimeet de tierstimeutil ) pourrait être utilisée pour cette tâche.

Ce qui précède yearsagoserait:

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)

Le premier paramètre devrait être un DateTime instance.

Pour convertir ordinaire datetimeen DateTimevous pouvez utiliser ceci pour une précision d'une seconde):

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

ou ceci pour une précision de 1 microseconde:

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)

Et oui, ajouter la dépendance pour cette seule tâche en question serait certainement une exagération par rapport à l'utilisation de timeutil (suggéré par Rick Copeland).

Antony Hatchkins
la source
1

En fin de compte, vous avez un problème de mathématiques. Si tous les 4 ans nous avons un jour supplémentaire, puis plonger le chronomètre en jours, pas par 365 mais 365 * 4 + 1, cela vous donnerait le montant de 4 ans. Puis divisez-le à nouveau par 4. timedelta / ((365 * 4) +1) / 4 = timedelta * 4 / (365 * 4 +1)

Norberto
la source
La chose des années bissextiles ne s'applique pas lorsque les années sont divisibles par 100, sauf quand elles sont divisibles par 400. Donc, pour l'année 2000: - elle est divisible par quatre, donc elle devrait être bissextile mais ... - elle est également divisible par cent, donc ça ne devrait pas être bissextile mais ... - il est divisible par 400, donc c'était en fait une année bissextile. Pour 1900: - il est divisible par 4 donc ça devrait être bond. - il est divisible par 100, il ne doit donc pas être bondi. - il n'est PAS divisible par 400, donc cette règle ne s'applique pas. 1900 n'était pas une année bissextile.
Jblasco
1

C'est la solution que j'ai élaborée, j'espère pouvoir vous aider ;-)

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
la source
0

Même si ce fil est déjà mort, puis-je suggérer une solution de travail pour ce même problème auquel je faisais face. Le voici (la date est une chaîne au format jj-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

la source
0

cette fonction renvoie la différence en années entre deux dates (prise sous forme de chaînes au format ISO, mais elle peut facilement être modifiée pour prendre n'importe quel format)

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
la source
0

Je suggérerai Pyfdate

Qu'est-ce que pyfdate?

Étant donné l'objectif de Python d'être un langage de script puissant et facile à utiliser, ses fonctionnalités pour travailler avec les dates et les heures ne sont pas aussi conviviales qu'elles devraient l'être. Le but de pyfdate est de remédier à cette situation en fournissant des fonctionnalités pour travailler avec des dates et des heures qui sont aussi puissantes et faciles à utiliser que le reste de Python.

le tutoriel

twils
la source
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()

C'est le code que l'opérateur du Carrousel exécutait dans le film Logan's Run;)

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

Andres Hurtis
la source
0

Je suis tombé sur cette question et j'ai trouvé la réponse d'Adams la plus utile https://stackoverflow.com/a/765862/2964689

Mais il n'y avait pas d'exemple python de sa méthode mais voici ce que j'ai fini par utiliser.

entrée: objet datetime

sortie: âge entier en années entières

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
la source
0

J'ai aimé la solution de John Mee pour sa simplicité, et je ne suis pas très préoccupé par la façon dont, le 28 février ou le 1er mars, quand ce n'est pas une année bissextile, déterminer l'âge des personnes nées le 29 février. Mais voici un ajustement de son code qui, je pense, répond aux plaintes:

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
la source